《TCP IP网络编程》第六章 基于UDP的服务器端客户端

    技术2022-07-10  139

    第六章 基于UDP的服务器端/客户端

    6.1 理解UDP

    TCP像电话一样可靠,UDP像寄信一样不可靠。

    UDP可靠性略弱,但性能更强。

    TCP为了提供可靠的数据传输服务,在不可靠的IP层进行流控制,而UDP缺少这种流控制机制。

    每次交换的数据量越大,TCP的传输速率就越接近UDP的传输速率。

    UDP内部工作原理

    IP的作用:让离开主机B的UDP数据包准确传递给主机A UDP的作用:根据端口号将传到主机的数据包交付给最终的UDP套接字

    UDP的高效使用

    UDP也是具有一定的可靠性的,不要认为它完全不可靠。

    针对压缩数据包,丢一个包都可能导致最终无法解压缩成功,这种一般用TCP。 针对网络实时传输的视频、音频等多媒体数据,丢失一小部分没啥问题;但因为要提供实时的服务,速度就是非常重要的因素,此时需要用UDP。

    TCP比UDP慢的原因:

    收发数据前后进行的连接设置及清除过程收发数据过程中为保证可靠性而添加的流控制

    如果收发的数据量小,但需要频繁连接时,UDP比TCP更高效。

    6.2 实现基于UDP的服务器端/客户端

    UDP中的服务器端和客户端没有连接

    无需listen和accept,只有创建套接字过程和数据交换过程。

    UDP服务器端和客户端均只需1个套接字

    TCP中套接字是一对一关系,除了守门的服务器套接字,还需要10个服务器端套接字。

    UDP中,不管是服务器端还是客户端,都只需要一个套接字。

    一个UDP套接字能和多态主机通信。只需要一个套接字就可以向任意主机传送数据。

    基于UDP的数据I/O函数

    UDP每次传输数据都要添加目标地址信息。

    发送UDP数据的函数:

    #include<sys/socket.h> ssize_t sendto(int sock, void * buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen); sock 用于传输数据的UDP套接字文件描述符 buff 保存待传输数据的缓冲地址值 nbytes 待传输的数据长度,以字节为单位 flags 可选项参数,若没有则传递0 to 存有目标地址信息的sockaddr结构体变量的地址值 addrlen 传递给参数to的地址值结构体变量长度 成功时返回传输的字节数,失败时返回-1

    接收UDP数据的函数:

    #include<sys/socket.h> ssize_t recvfrom(int sock, void * buff, size_t nbytes, int flags, struct sockaddr* from, socklen_t * addrlen); sock 用于接收数据的UDP套接字文件描述符 buff 保存接收数据的缓冲地址值 nbytes 可接收的最大字节数,故无法超过参数buff所指的缓冲大小 flags 可选项参数,若没有则传入0 from 存有发送端地址信息的sockaddr结构体变量的地址值 addrlen 保存参数from的结构体变量长度的变量地址值 成功时返回接收的字节数,失败时返回-1

    基于UDP的回声服务器端/客户端

    服务器端:

    #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUF_SIZE 30 void error_handling(char *message); int main(int argc,char * argv[]){ int serv_sock; char message[BUF_SIZE]; int str_len; socklen_t clnt_adr_sz; int str_len; socklen_t clnt_adr_sz; struct sockaddr_in serv_adr,clnt_adr; if(argc != 2){ printf("Usage:%s <port>\n",argv[0]); exit(1); } serv_sock = socket(PF_INET,SOCK_DGRAM,0); if(serv_sock == -1){ error_handling("UDP socket creation error"); } memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1) error_handling("bind() error"); while(1){ clnt_adr_sz = sizeof(clnt_adr); str_len = recvfrom(serv_sock,message,BUF_SIZE,0,(struct sockaddr*)&clnt_adr,&clnt_adr_sz); sendto(serv_sock,message,str_len,0,(struct sockaddr*)&clnt_adr,clnt_adr_sz); } close(serv_sock); return 0; } void error_handling(char * message){ fputs(message,stderr); fputc('\n',stderr); exit(1); }

    客户端:

    #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUF_SIZE 30 void error_handling(char *message); int main(int argc,char* argv[]){ int sock; char message[BUF_SIZE]; int str_len; socklen_t adr_sz; struct sockaddr_in serv_adr,from_adr; if(argc != 3){ printf("Usage: %s <IP> <port>\n",argv[0]); exit(1); } sock = socket(PF_INET, SOCK_DGRAM,0); if(sock == -1){ error_handling("socket() error"); } memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port = htons(atoi(argv[2])); while(1){ fputs("Insert message(q to quit):",stdout); fgets(message,sizeof(message),stdin); if(!strcmp(message,"q\n")|| !strcmp(message,"Q\n")) break; sendto(sock,message,strlen(message),0,(struct sockaddr*)&serv_adr,sizeof(serv_adr)); adr_sz = sizeof(from_adr); str_len = recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr*)&from_adr,&adr_sz); message[str_len] = 0; printf("Message from server:%s",message); } close(sock); return 0; } void error_handling(char * message){ fputs(message,stderr); fputc('\n',stderr); exit(1); }

    运行结果:

    [root@VM_0_10_centos udp]# ./uclient 127.0.0.1 9190 Insert message(q to quit):Hi UDP Server? Message from server:Hi UDP Server? Insert message(q to quit):Nice to meet you! Message from server:Nice to meet you! Insert message(q to quit):Good Bye~ Message from server:Good Bye~ Insert message(q to quit):q

    UDP客户端套接字的地址分配

    调用sendto函数的时候,自动分配IP和端口号 UDP客户端中通常无需额外的地址分配过程

    6.3 UDP的数据传输特性和调用connect函数

    UDP数据传输中存在数据边界。

    调用I/O的次数非常重要,输入函数的调用次数应该等于输出函数的调用次数。

    已连接UDP套接字 与 未连接UDP套接字

    通过sendto函数传输数据的过程大致可以分为以下3个阶段:

    第一阶段:向UDP套接字注册目标IP和端口号第二阶段:传输数据第三阶段:删除UDP套接字中注册的目标地址信息

    因此可以重复利用同一套UDP套接字向不同的目标传输数据。

    未注册目标地址信息的套接字——>未连接套接字 注册了目标地址信息的套接字——>已连接套接字

    但是,如果要与同一台主机进行长时间通信时,将UDP套接字变成已连接套接字会提高效率。

    创建已连接UDP套接字

    很简单,只需针对UDP套接字调用connect函数:

    sock = socket(PF_INET,SOCK_DGRAM,0); 这里确实是在建立UDP的套接字 memset(&adr,0,sizeof(adr)); adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(atoi(argv[1])); connect(sock,(struct sockaddr*)&adr,sizeof(adr)); 关键的一句话

    对UDP套接字调用connect函数并非意味着要与对方UDP套接字连接,只是向UDP套接字注册目标IP和端口信息罢了。

    调用connect之后,每次调用sendto只需传输数据,因为已经指定了收发对象,所以不仅可以使用sendto和recvfrom函数,还可以使用write和read函数进行通信。

    6.4 习题

    (1)UDP为什么比TCP速度快?为什么TCP数据传输可靠而UDP数据传输不可靠?

    UDP和TCP不同,不进行流控制。由于该控制涉及到套接字的连接和结束,以及整个数据收发过程,因此,TCP传输的数据是可以信赖的。相反,UDP不进行这种控制,因此无法信任数据的传输,但正因UDP不进行流量控制,所以比TCP更快。

    (2)UDP的特点(不是很全) 不存在连接概念; 有两个目标,也可以只用一个UDP套接字实现连接; UDP套接字中可以使用已经分配给TCP的同一端口号; 可以在同一台主机进行TCP和UDP的数据传输;

    (3)UDP数据包向对方主机的UDP套接字传递过程中,IP和UDP分别负责哪些部分?

    IP负责链路选择。UDP负责端到端的传输。

    (4)UDP一般比TCP快,但根据交换数据的特点,其差异可大可小。请说明何种情况下UDP的性能优于TCP。

    UDP与TCP不同,不经过连接以及断开SOCKET的过程,因此,在频繁的连接及断开的情况下,UDP的数据收发能力会凸显出更好的性能。

    (5)客户端TCP套接字调用connect函数时自动分配IP和端口号。UDP中不调用bind函数,那何时分配IP和端口号?

    首次调用sendto函数时,发现尚未分配信息,则给相应的套接字自动分配IP和端口号

    (6)TCP客户端必须调用connect函数,而UDP中可以选择性调用。请问,在UDP中调用connect函数有哪些好处?

    每当以UDP套接字为对象调用sendto函数时,都要经过以下过程:

    第一阶段:为目标UDP注册端口和IP 第二阶段:数据传输 第三阶段:删除UDP注册的IP和端口信息 其中,只要调用connect函数,就可以忽略每次传输数据时反复进行的第一阶段和第三阶段。然而,调用connect函数并不意味着经过连接过程,只是将IP地址和端口号指定在UDP的发送对象上。这样connect函数使用后,还可以用write、read函数进行数据处理,而不必使用sendto、recvfrom。

    Processed: 0.016, SQL: 9