博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于UDP的服务器端/客户端
阅读量:2177 次
发布时间:2019-05-01

本文共 7644 字,大约阅读时间需要 25 分钟。

理解UDP

UDP套接字的特点

下面通过信件说明UDP的工作原理。寄信前先在信封上填好寄信人和收信人的地址,之后贴上邮票放进邮筒即可。

无法确认对方是否收到信件,并且在邮寄过程中可能发生信件丢失的情况。也就是说,UDP是不可靠的数据传输服务。


TCP为了提供可靠的数据传输服务,在不可靠的IP层进行流控制,所以TCP比UDP可靠,但UDP在结构上比TCP更简洁。

流控制是区分UDP和TCP的最重要的标志。


UDP内部工作原理

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


UDP的高效使用

UDP具有一定的可靠性。网络传输特性导致信息丢失频发,可若要传输压缩文件(发送1W个数据包,丢失一个就会产生问题),则必须使用TCP。对于多媒体数据而言,丢失一部分没问题,只会引起短暂的画面抖动,或细微的杂音。

TCP比UDP慢的原因通常有以下两点:

-收发数据前后进行的连接设置及清除过程。

-收发数据过程中为保证可靠性而添加的流控制。


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

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

UDP只有创建套接字的过程和数据交换过程。不必调用TCP连接过程中调用的listen函数和accept函数。


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

TCP中套接字之间是一对一的关系。但在UDP中,不管服务器端还是客户端都只需要一个套接字。

(收发信件时使用的邮筒可以比喻为UDP套接字。只要附近有一个邮筒,就可以通过它向任意地址寄出信件。


下图展示1个UDP套接字和2个不同的主机数据交换的过程:

                        


基于UDP的数据I/O函数

UDP套接字每次传输数据都要添加目标地址信息,相当于寄信前在信件中填写地址。


填写地址并传输数据时调用的函数:


接受UDP数据的函数:



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


服务器端:

/* UDP回声服务器端uecho_server.c */#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc,char *argv[]){ int serv_sock; char message[BUF_SIZE]; int str_len; struct sockaddr_in serv_adr, clnt_adr; socklen_t clnt_adr_sz; if (argc != 2) { printf("Usage : %s
\n",argv[0]); exit(1); } serv_sock = socket(PF_INET,SOCK_DGRAM,0); //创建UDP套接字 if (serv_sock == -1) error_handling("UDP socket() 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); //通过47行的函数调用同时获取数据传输端的地址,并将接受的数据传会该地址 } close(serv_sock); return 0;}

客户端:

/* UDP回声客户端 */#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}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
\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("Input message(Q to quit): ",stdout); fgets(message,sizeof(message),stdin); if (!strcmp(message,"q\n") || !strcmp(message,"Q\n")) // 输入q/Q退出 break; sendto(sock,message,strlen(message),0,(struct sockaddr*) &serv_adr,sizeof(serv_adr)); // 首次sendto函数时给相应套接字自动分配IP和端口,分配的地址一直保存到程序结束为止 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;}

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

UDP客户端缺少把IP和端口分配给套接字的过程。TCP客户端是调用connect函数自动完成此过程。那UDP中何时分配IP和端口号?

UDP程序中,调用sendto函数传输数据前完成套接字的地址分配工作,因此调用bind函数。如果调用sendto函数时发现尚未分配地址信息,则在首次调用sendto函数时给相应套接字自动分配IP和端口。而且分配的地址一直保存到程序结束。


综上所述,调用sendto函数时自动分配IP和端口号,UDP客户端无需额外的地址分配过程。


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

存在数据边界的UDP套接字

UDP是具有数据边界的协议,传输中调用I/O函数的次数非常重要。输入函数的调用次数和输出函数的调用次数完全一致,才能保证接收全部已发送数据。例如调用3次输出函数发送的数据必须调用3次输入函数才能接受完。


示例:

host1(服务器端):

#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc,char *argv[]){ int sock; char message[BUF_SIZE]; struct sockaddr_in my_adr, your_adr; socklen_t adr_sz; int str_len , i ; if (argc != 2) { printf("Usage : %s
\n",argv[0]); exit(1); } sock = socket(PF_INET,SOCK_DGRAM,0); if (sock == -1) error_handling("socket() error!"); memset(&my_adr,0,sizeof(my_adr)); my_adr.sin_family = AF_INET; my_adr.sin_addr.s_addr = htonl(INADDR_ANY); my_adr.sin_port = htons(atoi(argv[1])); if(bind(sock,(struct sockaddr*) &my_adr,sizeof(my_adr)) == -1) error_handling("bind() error!"); for ( i = 0; i < 3; i++ ) //循环3次,接收3次数据 { sleep(5); //delay 5 sec adr_sz = sizeof(your_adr); str_len = recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr*) &your_adr, &adr_sz); printf("Message %d: %s \n", i+1,message); } close(sock); return 0;}

host2(客户端):

#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc,char *argv[]){ int sock; char msg1[] = "Hi!"; char msg2[] = "I'm another UDP host!"; char msg3[] = "Nice to meet you"; struct sockaddr_in your_adr; socklen_t your_adr_sz; if (argc != 3) { printf("Usage : %s
\n",argv[0]); exit(1); } sock = socket(PF_INET,SOCK_DGRAM,0); if (sock == -1) error_handling("socket() error!"); memset(&your_adr,0,sizeof(your_adr)); your_adr.sin_family = AF_INET; your_adr.sin_addr.s_addr = inet_addr(argv[1]); your_adr.sin_port = htons(atoi(argv[2])); /* 分三次传送数据 */ sendto(sock,msg1,sizeof(msg1),0,(struct sockaddr*) &your_adr,sizeof(your_adr)); sendto(sock,msg2,sizeof(msg2),0,(struct sockaddr*) &your_adr,sizeof(your_adr)); sendto(sock,msg3,sizeof(msg3),0,(struct sockaddr*) &your_adr,sizeof(your_adr)); close(sock); return 0;}

UDP通信过程中使I/O函数调用次数保持一致


已连接(connected) UDP套接字与为连接UDP套接字

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

-向UDP套接字注册目标IP和端口号

-传输数据

-删除UDP套接字中注册的目标地址信息


每次调用sendto函数时重复上述过程,每次都变更目标地址。因此可以重复利用同一UDP套接字向不同目标传输数据。

UDP这种未注册目标地址信息的套接字称为未连接套接字。反之,注册了目标地址的套接字称为连接connected套接字。


当向同一个端口多次传输数据时,就会多次重复以上过程。因此,要与同一主机进行长时间通信时,将UDP套接字变成已连接套接字会提高效率。


创建已连接UDP套接字

创建已连接UDP套接字的过程很简单,针对UDP套接字调用connect函数

sock = socket(PF_INET,SOCK_DGRAM,0);memset(&adr,0,sizeof(adr));adr.sin_family = AF_INET;adr.sin_addr.s_addr = ....adr.sin_port = ....connect(sock,(struct sockaddr*) &adr, sizeof(adr));

与TCP套接字创建过程一致,只是socket函数第二个参数变为SOCK_DGRAM。

之后就与TCP套接字一样,每次调用sendto函数时只需传输数据,因为已经指定了收发对象。

此时也可以使用write,read函数进行通信。


下面将之前的uecho.client.c改为基于已连接UDP套接字的程序。

/* UDP已连接客户端 */#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}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
\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])); connect(sock,(struct sockaddr*) &serv_adr,sizeof(serv_adr)); while (1) { fputs("Input message(Q to quit): ",stdout); fgets(message,sizeof(message),stdin); if (!strcmp(message,"q\n") || !strcmp(message,"Q\n")) // 输入q/Q退出 break; /* sendto(sock,message,strlen(message),0,(struct sockaddr*) &serv_adr,sizeof(serv_adr)); */ write(sock,message,strlen(message)); //可以使用write传输数据 /* adr_sz = sizeof(from_adr); str_len = recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr*) &from_adr, &adr_sz); */ str_len = read(sock,message,sizeof(message)-1); //可以使用read读取数据 message[str_len] = 0; printf("Message from server: %s",message); } close(sock); return 0;}
你可能感兴趣的文章
【Python练习】文件引用用户名密码登录系统
查看>>
学习网站汇总
查看>>
【Python】用Python打开csv和xml文件
查看>>
【Loadrunner】性能测试报告实战
查看>>
【自动化测试】自动化测试需要了解的的一些事情。
查看>>
【selenium】selenium ide的安装过程
查看>>
【手机自动化测试】monkey测试
查看>>
【英语】软件开发常用英语词汇
查看>>
Fiddler 抓包工具总结
查看>>
【雅思】雅思需要购买和准备的学习资料
查看>>
【雅思】雅思写作作业(1)
查看>>
【雅思】【大作文】【审题作业】关于同不同意的审题作业(重点)
查看>>
【Loadrunner】通过loadrunner录制时候有事件但是白页无法出来登录页怎么办?
查看>>
【English】【托业】【四六级】写译高频词汇
查看>>
【托业】【新东方全真模拟】01~02-----P5~6
查看>>
【托业】【新东方全真模拟】03~04-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST05~06-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST09~10-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST07~08-----P5~6
查看>>
solver及其配置
查看>>