背景:本文作为本人的一点心得,同时也供遇到类似问题的读者以供参考。
所需知识:简单的SOCKET编程、多线程编程知识
SOCKET编程作为当前网络编程的主要方式,在各种项目、科研、实验中多有使用,其通过套接字的方式完成端到端通信,大大方便了编程人员的网络搭建。
网络建立的模式多种多样,client-sever(用户-服务器)模式、peer-2-peer(对等)模式等等。而无论哪一种模式,在点到点链路建立后,不可避免的可能会出现多应用同时通信的情景。所以在SOCKET通信中,自然地,在多数情况下都会采用多线程通信。例如,在C-V(用户-服务器模式缩写)模式中,服务器大多不可能单一地为一个用户提供服务,所以在用户A连接建立时,服务器会自动的分配一个线程给该用户,而预留自身的监听状态,以保证后续的用户可以正常建立连接。
然而,只要实现过任何一个最基本的C-V通信的读者都会知道,SOCKET编程中有许多函数都有决定函数自身的执行状态的参数,比如接收数据函数:
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);
中的flag字段可以是MSG_NOWAIT、0等情况。再比如
int sendto ( socket s , const void * msg, int len, unsigned int flags, conststruct sockaddr * to , int tolen ) ;
中的flag字段。
flag字段的含义定义如下:
MSG_DONTWAIT:操作不会被阻塞。
MSG_ERRQUEUE: 指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供。
MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
MSG_EOR:指示记录的结束,返回的数据完成一个记录。
MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
而如何选择合适的flag直接影响了程序的性能。
比如在代码段中:
int UDPRcv(struct tunnel_T *tunnel)
{
int BufLength;
unsigned char msg[PLMsgMaxLength];
struct sockaddr_in clientaddr;
socklen_t len = (socklen_t) sizeof(clientaddr);
struct tunnel_T tunnelcsma;
int i =0,num;
while(1)
{
#ifndef DISABLE_ALL
----------
if((BufLength = recvfrom(tunnel->socket_descriptor, msg, PLMsgMaxLength, 0, (struct sockaddr *)&clientaddr, &len)) > 0)
----------
#else
if((BufLength = uart_recv_frame((u8 *)msg, PLMsgMaxLength)) >0)
#endif
{
full_ip_r += BufLength + 1;
#ifdef DEBUG
printf("udp received!\n");
for(;i" %x",msg[i]);
i=0;
printf("\n");
printf("length is %d\n",BufLength);
#endif
switch((int)msg[0])
{
case NOMAC:
...
case FDMAC:
...
case CSMA:
...
}
}
else
fprintf(stderr, "udpRcv :%s\a\n", strerror(errno));
}
return 0;
}
这段代码一直在不停的接收数据,如果flag参数为0,则代码运行到这里时会阻塞,直到有数据包到达时才会继续运行;而如果参数flag是MSG_NOWAIT的话,代码运行到recvfrom处发现没有数据包执行,则会打印else的语句,并继续执行。如果该UDPRcv函数是一个接收线程的话,那么flag为MSG_NOWAIT时,该线程则会不停地无效的占用资源,而如果flag为0时,该线程就会阻塞,知道有数据包到达为止。
与此类似的,在多线程编程中,不同线程间进行数据交换时所用的消息队列也有类似的机制。例如函数:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
中都有msgflg参数,该参数同样有阻塞、非阻塞等不同的情况,其功能与recvfrom和sendto类似,在此不赘述了。
下图展示了本人仅修改了多线程SOCKET通信代码中*一处***flag参数后,端到端ping业务时延的结果。
从图中可以看到,性能表现是十分明显的。