Linux下TCP/IP编程--UDP实战

2019-07-13 04:54发布

本文参考《嵌入式Linux编程与实践教程》一书,以及http://www.cnblogs.com/skyfsm/p/6287787.html
给出一个简单的UDP模型代码并分析UDP一些高级特性,特此感谢。
本人已经发表了Linux下TCP实战的代码,如有参考请点击:TCP实战

一、概念

UDP是一个简单的传输层协议,是一种非连接的、不可靠的数据报文协议,和TCP有较大区别。虽然UDP有很多不足,但还是有很多网络程序使用它,比如DNS(域名解析服务)、NFS(网络文件系统)、SNMP等。 UDP客户端和服务器端不建立连接,而是直接使用sento发送数据,使用recvfrom阻塞等待接收数据。 这里写图片描述
基于UDP服务器程序设计步骤如下:
  1. 函数socket创建一个Socket。
  2. 函数bind绑定IP地址,端口等信息到Socket上。
  3. 函数recvfrom循环接收数据。
  4. 关闭网络连接。
基于UDP客户端程序设计步骤如下:
  1. 函数socket创建一个Socket。
  2. 函数bind绑定IP地址,端口等信息到Socket上。
  3. 设置要连接服务器IP地址、端口等信息。
  4. 函数sendto发送数据。
  5. 关闭网络连接。

二、程序设计实例

基于UDP服务器程序设计,服务器负责接收数据: #include #include #include #include #include #include #include #define SERVER_PORT 8888 #define BUFF_LEN 1024 void handle_udp_msg(int fd) { char buf[BUFF_LEN]; //接收缓冲区,1024字节 socklen_t len; int count; struct sockaddr_in clent_addr; //clent_addr用于记录发送方的地址信息 while(1) { memset(buf, 0, BUFF_LEN); len = sizeof(clent_addr); count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, &len); //recvfrom是拥塞函数,没有数据就一直拥塞 if(count == -1) { printf("recieve data fail! "); return; } printf("client:%s ",buf); //打印client发过来的信息 memset(buf, 0, BUFF_LEN); sprintf(buf, "I have recieved %d bytes data! ", count); //回复client printf("server:%s ",buf); //打印自己发送的信息 sendto(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, len); //发送信息给client,注意使用了clent_addr结构体指针 } } /* server: socket-->bind-->recvfrom-->sendto-->close */ int main(int argc, char* argv[]) { int server_fd, ret; struct sockaddr_in ser_addr; //AF_INET:IPV4;SOCK_DGRAM:UDP server_fd = socket(AF_INET, SOCK_DGRAM, 0); if(server_fd < 0) { printf("create socket fail! "); return -1; } memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; /* 作为服务器,你要绑定【bind】到本地的IP地址上进行监听【listen】,但是你的机器上可能有多块网卡, 也就有多个IP地址,这时候你要选择绑定在哪个IP上面,如果指定为INADDR_ANY,那么系统将绑定默认的网卡【即IP地址】 */ ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); ser_addr.sin_port = htons(SERVER_PORT); //端口号,需要网络序转换 ret = bind(server_fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr)); if(ret < 0) { printf("socket bind fail! "); return -1; } handle_udp_msg(server_fd); //处理接收到的数据 close(server_fd); return 0; } 基于UDP客户端程序设计client.c,客户端负责发送数据: #include #include #include #include #include #include #define SERVER_PORT 8888 #define BUFF_LEN 512 #define SERVER_IP "127.0.0.1" void udp_msg_sender(int fd, struct sockaddr* dst) { socklen_t len; struct sockaddr_in src; while(1) { char buf[BUFF_LEN] = "TEST UDP MSG! "; len = sizeof(*dst); printf("client:%s ",buf); //打印自己发送的信息 sendto(fd, buf, BUFF_LEN, 0, dst, len); memset(buf, 0, BUFF_LEN); recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&src, &len); //接收来自server的信息 printf("server:%s ",buf); sleep(1); //一秒发送一次消息 } } /* client: socket-->sendto-->revcfrom-->close */ int main(int argc, char* argv[]) { int client_fd; struct sockaddr_in ser_addr; client_fd = socket(AF_INET, SOCK_DGRAM, 0); if(client_fd < 0) { printf("create socket fail! "); return -1; } memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; //作为客户端,要指定远端服务器的(ip, port) ser_addr.sin_addr.s_addr = inet_addr(SERVER_IP);//仅仅连接服务器IP ser_addr.sin_port = htons(SERVER_PORT); //注意网络序转换 udp_msg_sender(client_fd, (struct sockaddr*)&ser_addr); close(client_fd); return 0; } 参考文章开头给出的链接:
UDP高级特性:
1.UDP套接字
UDP套接字有以下区分:
1)未连接的UDP套接字
2)已连接的UDP套接字 对于未连接的套接字,也就是我们常用的的UDP套接字,我们使用的是sendto/recvfrom进行信息的收发,目标主机的IP和端口是在调用sendto/recvfrom时确定的; 在一个未连接的UDP套接字上给两个数据报调用sendto函数内核将执行以下六个步骤:
1)连接套接字
2)输出第一个数据报
3)断开套接字连接
4)连接套接字
5)输出第二个数据报
6)断开套接字连接 对于已连接的UDP套接字,必须先经过connect来向目标服务器进行指定,然后调用read/write进行信息的收发,目标主机的IP和端口是在connect时确定的,也就是说,一旦conenct成功,我们就只能对该主机进行收发信息了。 已连接的UDP套接字给两个数据报调用write函数内核将执行以下三个步骤:
1)连接套接字
2)输出第一个数据报
3)输出第二个数据报 由此可以知道,当应用进程知道给同一个目的地址的端口号发送多个数据报时,显示套接字效率更高。
2.udp报文丢失问题
因为UDP自身的特点,决定了UDP会相对于TCP存在一些难以解决的问题。第一个就是UDP报文缺失问题。
在UDP服务器客户端的例子中,如果客户端发送的数据丢失,服务器会一直等待,直到客户端的合法数据过来。如果服务器的响应在中间被路由丢弃,则客户端会一直阻塞,直到服务器数据过来。 防止这样的永久阻塞的一般方法是给客户的recvfrom调用设置一个超时,大概有这么两种方法:
1)使用信号SIGALRM为recvfrom设置超时。首先我们为SIGALARM建立一个信号处理函数,并在每次调用前通过alarm设置一个5秒的超时。如果recvfrom被我们的信号处理函数中断了,那就超时重发信息;若正常读到数据了,就关闭报警时钟并继续进行下去。 2)使用select为recvfrom设置超时
设置select函数的第五个参数即可。
3. udp报文乱序问题
所谓乱序就是发送数据的顺序和接收数据的顺序不一致,例如发送数据的顺序为A、B、C,但是接收到的数据顺序却为:A、C、B。产生这个问题的原因在于,每个数据报走的路由并不一样,有的路由顺畅,有的却拥塞,这导致每个数据报到达目的地的顺序就不一样了。UDP协议并不保证数据报的按序接收。 解决这个问题的方法就是发送端在发送数据时加入数据报序号,这样接收端接收到报文后可以先检查数据报的序号,并将它们按序排队,形成有序的数据报。
4. udp流量控制问题
总所周知,TCP有滑动窗口进行流量控制和拥塞控制,反观UDP因为其特点无法做到。UDP接收数据时直接将数据放进缓冲区内,如果用户没有及时将缓冲区的内容复制出来放好的话,后面的到来的数据会接着往缓冲区放,当缓冲区满时,后来的到的数据就会覆盖先来的数据而造成数据丢失(因为内核使用的UDP缓冲区是环形缓冲区)。因此,一旦发送方在某个时间点爆发性发送消息,接收方将因为来不及接收而发生信息丢失。 解决方法一般采用增大UDP缓冲区,使得接收方的接收能力大于发送方的发送能力。 int n = 220 * 1024; //220kB
setsocketopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); 这样我们就把接收方的接收队列扩大了,从而尽量避免丢失数据的发生。