欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

socket实现TCP/UDP通信协议设计

程序员文章站 2022-07-07 15:40:34
...

基于套接字编程的TCP/UDP通信协议设计

tcp提供客户与服务器的连接,一个TCP客户建立一个与服务器的连接,并与能够服务器交换数据,然后终止连接,提供可靠性。当TCP向另一端发送数据时,它要求对端返回一个确认,如果确认没有收到,TCP自动重传数据,并等待更长时间。

UDP是一个简单的传输层协议,提供无连接的服务,不需要与客户端建立连接。UDP客户端与服务器不必存在长期的关系,缺乏可靠性。协议不保证分组能够最终到达目的地,不保证各个分组按先后顺序跨网络保持不变,也不保证每个分组只到达一次。

Socket编程通常称为“套接字”,用于描述IP地址,端口和系统资源,是通信链的句柄。其使用与文件操作类似。
socket实现TCP/UDP通信协议设计
socket实现TCP/UDP通信协议设计

tcp_receiver.c文件代码

#include "net_exp.h"

int main(int argc, char **argv) {

    /* 建立服务端套接字 */
    int server_sockfd;
    if ((server_sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {               /*(协议族、网络类型,数据传送方式,协议0)*/
        perror("socket error");
        return 1;
    }

    /* 监听端口 */
    struct sockaddr_in server_addr;
    memset(&server_addr, sizeof(server_addr),0);                                       /*(s,c,n)将s所指地址前n比特用常数c填充*/
    server_addr.sin_family =AF_INET;                   /*地址族AF_INET*/
    server_addr.sin_addr.s_addr =htonl(INADDR_ANY);                              /*ip地址*/
    server_addr.sin_port =htons(TCP_SERVER_PORT);                                 /*端口号*/
    
    if (bind(server_sockfd,(struct sockaddr_in*)&server_addr,sizeof(server_addr)) == -1) {                       
           /*(sockfd,指针指向要绑定给sockfd的协议地址,长度)*/
        perror("bind error");
        return 1;
    }

    if (listen(server_sockfd,1) == -1) {                                                           /*(sockfd,指定此套接口排队最大连接数目)*/
        perror("listen error");
        return 1;
    };

    /* 建立tcp连接 */
    int client_sockfd;
    struct sockaddr_in client_addr;
    unsigned int client_addr_len = sizeof(struct sockaddr_in);
    if ((client_sockfd = accept(server_sockfd,(struct sockaddr_in*)&client_addr,&client_addr_len)) == -1) {
       /*(sockfd,客户端协议地址信息,长度)*/
        perror("accept error");
        return 1;
    }
    printf("accept client %s:%d\n", inet_ntoa(client_addr.sin_addr), client_addr.sin_port);

    /* 接收数据 */
while(1){
    size_t pkt_len;
    char recv_buf[TCP_BUF_LENGTH];
    pkt_len = recv(client_sockfd,recv_buf,sizeof(recv_buf),0);                         /*(socktfd,缓冲区地址,长度,标志)*/

    if (pkt_len == -1) {
        perror("recv error");
        return 1;
    }

    if (pkt_len == 0) {
        /* 连接被远端关闭 */
        printf("finish\n");
        return 0;
    }


    /* 输出接收到的信息 */
    recv_buf[pkt_len] = '\0';
    printf("[TCP RECEIVER] receive msg[%d bytes]\n", pkt_len);
    printf("\t%s\n", recv_buf);

    /* 发送信息 */
    
    
    char xia[255];
    gets(xia);
  
    if (send(client_sockfd,xia,sizeof(xia),0) == -1) {                                 /*(socktfd,缓冲区地址,长度,标志)*/
     perror("send error");
      return 1;
    }
}
    /* 关闭套接字 */
    close(client_sockfd);                                  /*(socktfd)*/
    close(server_sockfd);
    return 0;
}

tcp_sender.c文件代码

//tcp.sender
#include "net_exp.h"
int main(int argc, char **argv) {

    /* 建立套接字 */
    int sockfd;
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
        perror("socket error");
        return 1;
    }

    /* 建立tcp连接 */
    struct sockaddr_in server_addr;
    memset(&server_addr,sizeof(server_addr),0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr =inet_addr("192.168.245.128");
    server_addr.sin_port =htons(TCP_SERVER_PORT);

    if (connect(sockfd,(struct sockaddr_in*)&server_addr,sizeof(server_addr))) {
        perror("connect error");
        return 1;
    }

    /* 发送数据 */
while(1){
    char msg[255];
    gets(msg);
    send(sockfd,msg,strlen(msg)+1,0);

    /* 接收数据 */
    char recv_buf[TCP_BUF_LENGTH];
    size_t pkt_len = 0;
    pkt_len = recv(sockfd,recv_buf,sizeof(recv_buf),0);

    if (pkt_len == -1) {
        perror("recv error");
        return 1;
    }

    if (pkt_len == 0) {
        /* 连接被远端关闭 */
        printf("finish\n");
        return 0;
    }

    /* 输出接收到的信息 */
    recv_buf[pkt_len] = '\0';
    printf("[TCP SENDER] receive echo msg[%d bytes]\n", pkt_len);
    printf("\t%s\n", recv_buf);
}
    /* 关闭套接字 */
    close(sockfd);

    return 0;
}

面向连接的Client/Server结构中,服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听准备。并规定它的请求队列长度。之后调用accept()来接收连接。客户在建立套接口后就可以调用connect()和服务器建立连接。一旦连接建立,客户机就可以与服务器之间通过调用send()和recv()来发送和接收数据。最后,传输完成后,双方调用close()关闭套接口。

udp_receiver.c文件代码

//udp.receiver
#include "net_exp.h"
int main(int argc, char **argv) {

    /* 建立套接字 */
    int sockfd;
    if ((sockfd = socket(AF_INET,SOCK_DGRAM,0)) == -1) {
        perror("socket error");
        return 1;
    }

    /* 绑定端口 */
    struct sockaddr_in server_addr;
    memset(&server_addr, sizeof(server_addr),0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(UDP_SERVER_PORT);

    if (bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)) == -1) {
        perror("bind error");
        return 1;
    }

    /* 接收数据 */
    struct sockaddr_in client_addr;
    int client_addr_len;
    char recv_buf[UDP_BUF_LENGTH];
    size_t pkt_len;
    while (1) {
        memset(recv_buf, sizeof(recv_buf), 0);
        client_addr_len = sizeof(client_addr);
        pkt_len = recvfrom(sockfd,recv_buf,sizeof(recv_buf)+1,0,(struct sockaddr*)&client_addr,client_addr_len);
        recv_buf[pkt_len] = '\0';
        printf("[UDP_RECEIVER] receive msg[%d bytes]\n", pkt_len);
        printf("\t%s\n", recv_buf);
    }

    /* 关闭套接字 */
    close(sockfd);

    return 0;
}

udp_sender.c文件代码

//udp.sender
#include "net_exp.h"
int main(int argc, char **argv) {

    /* 建立套接字 */
    int socket_fd;
    if ((socket_fd = socket(AF_INET,SOCK_DGRAM,0)) == -1) {
        perror("socket error");
        return 1;
    }

    /* 发送数据 */
    struct sockaddr_in server_addr;
    memset(&server_addr, sizeof(server_addr),0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr =inet_addr("192.168.245.128");
    server_addr.sin_port = htons(UDP_SERVER_PORT);

    int counter = 0;
    char send_buf[UDP_BUF_LENGTH];

    while (1) {
        memset(send_buf, sizeof(send_buf), 0);
        printf("sending data packet with #: %d\n", counter);
        sprintf(send_buf, "data packet with #: %d.", counter);
        sendto(socket_fd,send_buf,sizeof(send_buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));

        counter++;
        if (counter > 10)
            break;

        sleep(1);
    }

    /* 关闭套接字 */
    close(socket_fd);

    return 0;
}

在无连接的Client/Server结构中,服务器使用socket()和bind()建立和联系socket。由于此时socket是无连接的,服务器使用recvfrom()从socket接口接收数据。客户端也调用bind()而不调用connect()。因为并未在两个端口建立点到点的连接,因此sendto()要求程序提供一个参数指明目的地址信息。secvfrom()不需要建立连接,它对到达相连接的协议端口的任何数据做出响应。取出数据报的同时,它将保存发送此数据包的进程的网络地址及包本身。

头文件内容:

#ifndef NETEXP_H
#define NETEXP_H


#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>


#define		TCP_SERVER_ADDRESS		"127.0.0.1"
#define 	TCP_SERVER_PORT			8001
#define		TCP_BUF_LENGTH			1000
#define 	CONN_NUM			10

#define		UDP_SERVER_ADDRESS		"127.0.0.1"
#define		UDP_SERVER_PORT			8002 
#define		UDP_BUF_LENGTH			1000

#define		RDT_SERVER_ADDRESS		"127.0.0.1"
#define		RDT_SEND_LFILE_NAME		"data_long_send.mp4"
#define 	RDT_RECV_LFILE_NAME		"data_long_recv.mp4"
#define		RDT_SEND_SFILE_NAME		"abc_send.pdf"
#define 	RDT_RECV_SFILE_NAME		"abc_recv.pdf"
#define		RDT_ACK_MSG				"SUCC"
#define		RDT_NACK_MSG			"FAIL"		
#define 	RDT_RECV_PORT			8003
#define		RDT_SEND_PORT			8004
#define		RDT_BEGIN_SEQ			1
#define		RDT_SENDWIN_LEN			10

#define		RDT_PKT_LOSS_RATE		10
#define		RDT_TIME_OUT			50000
#define		RDT_HEADER_LEN			(4 + 4)
#define 	RDT_DATA_LEN			1000
#define		RDT_PKT_LEN			( RDT_DATA_LEN + RDT_HEADER_LEN )

#define		RDT_CTRL_BEGN			0
#define		RDT_CTRL_DATA			1
#define 	RDT_CTRL_ACK			2
#define		RDT_CTRL_END			3



typedef struct _STATE_PKT
{
	struct timeval send_time;
	int pkt_seq;
	int pkt_len;
	int state; //init 0 , sent 1 , acked 2 , timeout 3 , empty 4
	char rdt_pkt[RDT_PKT_LEN];
}STATE_PKT;

typedef struct _SLD_WIN
{
	//[send_left, send_right) sequence number
	int win_len;
	int send_left;
	int send_right;
	STATE_PKT rdt_pkts[RDT_SENDWIN_LEN];
	pthread_mutex_t lock;
}SLD_WIN;


int pack_rdt_pkt( char *data_buf, char *rdt_pkt, int data_len, int seq_num, int flag );
int unpack_rdt_pkt( char *data_buf, char *rdt_pkt, int pkt_len, int *seq_num, int *flag );
void udt_sendto( int sock_fd, char *pkt, int pkt_len, int flags, struct sockaddr *recv_addr, int addr_len );
int time_out( struct timeval time_1, struct timeval time_2 );


#endif

linux下在文件夹下打开端口运行make命令,会生成四个可执行文件在build子目录下: tcp_receiver,tcp_sender,udp_receiver,udp_sender
然后在子目录下打开两个端口分别运行receiver和sender,可以实现两机聊天功能。
如果make命令不存在,可以尝试使用gcc。