基于这段时间udp图像总是卡在数据重放过程中,发现recv Q满了,但不知道具体原因是什么,先在这里进行udp接收测试。
一. 正常测试
-
send端每次发送1k,发送频率50hz;
- recv端每次接受1k,接受频率5hz;
- watch -n 1 netstat -anu 显示recv对应8888端口的过程recv Q波动在207000到162000之间,不会卡死。recv Q 上限在207000左右。通过分析结果可以发现,当recv q满了之后,recv从队列中拿出1k之后,从接收发送端k进入队列,而是recv继续从队列中获取数据,直到recv Q 降至162000左右,即队列占用率达到80%后,才会被接受send端发数据。
二. 当recv端cnt==800时,执行socket重新初始化
if(cnt == 800)
{
serfd=socket(AF_INET,SOCK_DGRAM,0);
printf("serfd = %d\n",serfd);
// ret=bind(serfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
}
无论是否执行bind操作,接收端卡住,接收队列满。proc/pid/fd下面会增加一个socket文件,对应fd为4。如果执行bind,绑定失败将返回,端口将被占用。
原因:通过以上if语句后,serfd已经变成4,对应的是新的socket文件,也就是说recv端会有一个新的接收队列,但是没有数据发送到这个新的队列。send端发送的数据仍然发送到serfd=在3对应的队列中,但没有人读取队列,导致recv Q 满了。表象就是卡住了,实际上是因为serfd=在相应的队列中没有数据。
除上述修改外,如果是recvfrom的serfd换成常量3,虽然新生成4对应fd,然而,数据仍然从3对应的队列中获取,不会卡住,但4对应的队列中没有数据。
三. 当recv端cnt==800时,执行socket重新初始化
if(cnt == 800)
{
if(serfd>0) ret = close(serfd);
printf("close serfd=%d\n",ret);
serfd=socket(AF_INET,SOCK_DGRAM,0);
sleep(1);
printf("serfd = %d\n",serfd);
ret=bind(serfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
}
原因:
在cnt 原来是800点fd会关闭,/proc/pid/fd/以下文件3也将被删除;之后,重新执行 serfd=socket(AF_INET,SOCK_DGRAM,0);之后,将申请可用性fd,而这个时候因为3号fd已释放,因此重新申请fd还是3。但此时对应inode从2130436到2132215。8888端口对应recv Q直接清除100多k数据,然后开始新的数据积累。
测试程序
- send端:
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <iostream> //创建UDP实现服务器和客户端的通信 //创建socket连接 int main() { //创建socket连接 int clifd = 0; int cnt = 0; clifd = socket(AF_INET, SOCK_DGRAM, 0); if (clifd < 0) { perror("socke failed"); return -1; } printf("socket success\n"); ////向服务器发送消息 int tolen = 0; int ret = 0; char buf[1024] = {0}; while (1) { // std::cin.getline(buf,1023); memset(buf, 0, 1024); cnt ; memcpy(buf, &cnt, sizeof(int)); struct sockaddr_in seraddr = {0}; seraddr.sin_family = AF_INET; seraddr.sin_addr.s_addr = inet_addr("192.168.61.6"); seraddr.sin_port = htons(8888); tolen = sizeofseraddr);
ret = sendto(clifd, buf, strlen(buf), 0, (struct sockaddr *)&seraddr, tolen);
if (ret < 0)
{
perror("sendto failed");
close(clifd);
return -1;
}
printf("sendto success, %d\n", cnt);
//接收发送自服务器的消息
// ret=recvfrom(clifd,buf,sizeof(buf),0,NULL,NULL);
usleep(20000);
// if(ret<0)
// {
// perror("recvfrom failed");
// close(clifd);
// return -1;
// }
// printf("recvfrom success\n");
// printf("receive: %s\n",buf);
}
close(clifd);
return 0;
}
- recv端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
//创建UDP实现服务器和客户端的通信
int main()
{
//创建socket连接
int serfd = 0;
int cnt = 0;
serfd = socket(AF_INET, SOCK_DGRAM, 0);
printf("serfd = %d\n", serfd);
if (serfd < 0)
{
perror("socke failed");
return -1;
}
printf("socket success\n");
//绑定IP地址和端口信息
int ret = 0;
struct sockaddr_in seraddr = {0};
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = inet_addr("192.168.61.6");
seraddr.sin_port = htons(8888);
ret = bind(serfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind failed");
close(serfd);
return -1;
}
printf("bind success\n");
//接收发送自客户端的消息
unsigned int addrlen = 0;
char buf[1024] = {0};
struct sockaddr_in clientaddr = {0};
addrlen = sizeof(clientaddr);
while (1)
{
memset(buf, 0, 1024);
ret = recvfrom(serfd, buf, sizeof(buf), 0, (struct sockaddr *)&clientaddr, &addrlen);
if (ret < 0)
{
perror("recvfrom failed");
close(serfd);
return -1;
}
memcpy(&cnt, buf, sizeof(int));
printf("IP=%s,port=%u\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
printf("recvfrom success\n");
printf("receive: %d\n", cnt);
if (cnt == 800)
{
if (serfd > 0)
ret = close(serfd);
printf("close serfd=%d\n", ret);
sleep(5);
serfd = socket(AF_INET, SOCK_DGRAM, 0);
sleep(1);
printf("serfd = %d\n", serfd);
ret = bind(serfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
}
//向客户端发送消息
memset(buf, 0, sizeof(buf));
//gets(buf);
// std::cin.getline(buf,1023);
//cnt++;
//memcpy(buf,&cnt,sizeof(int));
// ret=sendto(serfd,buf,strlen(buf),0,(struct sockaddr *)&clientaddr,addrlen);
// if(ret<0)
// {
// printf("%ret = %d\n",ret);
// perror("sendto failed");
// close(serfd);
// return -1;
// }
// printf("sendto success\n");
usleep(200000);
}
close(serfd);
return 0;
}
编译:
g++ recv.cpp -o recv
g++ send.cpp -o send
五. 运行顺序
1. 准备工作
启动6个命令窗口,进入到上述文件目录下;
2. 先在terminal#1运行recv程序,可以将之重定向到其它文件;
./recv > r3.txt
3. 在terminal#2运行send程序
./send
4. 在terminal#3查看rtxt的最后10行,每秒更新一次,主要用于确认cnt=800大概在什么时候;
watch -n 1 tail -n10 r3.txt
5. 在terminal#4查看recv对应的pid,然后进入到/proc/pid/fd查看socket文件,并每秒更新一次,查看socket文件是否变化;
6. 查看udp丢包情况以及socket对应的文件的inode是否变化;
watch -n 1 "cat /proc/net/udp |grep 22B8" 其中22B8是端口8888的十六进制表示。
目前看丢包6121个;2705799就是目前8888端口对应socket文件的inode号。和/proc/pid/fd下面的文件一致;
7. 查看udp的接收队列情况
watch -n 1 netstat -anu
可以看到目前recv Q保持高位运行。