免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5056 | 回复: 3
打印 上一主题 下一主题

[网络子系统] 求用过TCP KEEPALIVE的大侠指点一下 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-05-04 10:41 |只看该作者 |倒序浏览
本帖最后由 frank529 于 2012-05-04 11:02 编辑

最近在做一个项目需要监测TCP连接非正常断开,不想添加额外消息所以不用心跳机制。网上查了关于TCP KEEPALIVE的设置,按我的理解如果TCP KEEPALIVE在发现tcp socket断连后,应该对它做一些处理,使得对它的操作select、read、write等产生错误(Windows就是这么干的)。但实际情况是,网络断连后,如果这个socket只做read操作,那么select不会阻塞,正常返回0,read返回值为0。但如果这个socket同时在做write操作,则select一直阻塞,这样在发送缓冲区溢出前我一直知道网络断连。网上的资料都是关于怎么开启keepalive,设置keepalive的时间、间隔和重复次数,并没有关于keepalive在发现断连后是怎样通知上层的,希望高手指点。

为了方便阅读,贴个测试代码的大致结构,详细测试代码见跟帖,大家可以试试,看情况是不是和我描述的一样,还是我的代码有问题。
  1. int main(void)
  2. {
  3.         int server_sockfd, client_sockfd;
  4.         fd_set        reader_fds;
  5.         int ret;

  6.         /* 创建server_sockfd绑定侦听 */
  7.         ...


  8.         FD_ZERO(&reader_fds);
  9.         FD_SET(server_sockfd, &reader_fds);       


  10.         while(1)
  11.         {
  12.                 ret = select(READER_FDSIZE, &reader_fds,(fd_set *)0, (fd_set *)0 , NULL);
  13.                 if(res < 0)
  14.                         perror("select");

  15.                 /* 有客户端连入 */
  16.                 if(FD_ISSET(server_sockfd, &reader_fds))
  17.                 {
  18.                         client_sockfd = accept...

  19.                         /* 配置client_sockfd非阻塞,开启TCP KEEPALIVE,设置KEEPALIVE时间、间隔和重发次数 */
  20.                         ...

  21.                         /* 创建发送线程,每隔几秒向client_sockfd write消息 */
  22.                         ...

  23.                         /* client_sockfd加入reader_fds */
  24.                         FD_SET(client_sockfd, &reader_fds);
  25.                         continue;
  26.                 }

  27.                 if(FD_ISSET(client_sockfd, &reader_fds))
  28.                 {
  29.                         ioctl(client_sockfd, FIONREAD, &nread);        // 判断缓冲区有多少数据可读
  30.                         /* 如果没有发送线程,网络断连后,select会不断返回,nread一直为0;*/
  31.                         /* 有发送线程,断连后select会阻塞,不会运行到此处 */
  32.                         /* nread不为0读取数据 */
  33.                 }
  34.         }
  35. }
复制代码
PS:虽然这个问题属于应用层范畴,但TCP KEEPALIVE与内核网络协议栈有关,CU内核版人气旺一些,所以发到这里,望版主手下留情。

论坛徽章:
0
2 [报告]
发表于 2012-05-04 10:57 |只看该作者
本帖最后由 frank529 于 2012-05-04 10:58 编辑

测试代码,服务器端,环境Linux2.6.30
  1. #include <string.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <sys/ioctl.h>
  5. #include <netdb.h>
  6. #include <net/if.h>
  7. #include <netinet/in.h>
  8. #include <net/if_arp.h>
  9. #include <sys/socket.h>
  10. #include <arpa/inet.h>
  11. #include <ifaddrs.h>
  12. #include <errno.h>
  13. #include <fcntl.h>
  14. #include <unistd.h>
  15. #include <termios.h>
  16. #include <pthread.h>
  17. #include <netinet/tcp.h>

  18. #define READER_FDSIZE 10

  19. int client_sockfd;

  20. void *sendmsg_thread(void *arg)
  21. {
  22.         int netcnt;
  23.         while(1)
  24.         {
  25.                 netcnt = send(client_sockfd,"hello client",strlen("hello client")+1,0);
  26.                 if(netcnt != strlen("hello client")+1)
  27.                         printf("netcnd = %d\n",netcnt);
  28.                 sleep(2);
  29.         }
  30. }

  31. int main(void)
  32. {
  33.         int server_sockfd;
  34.         struct sockaddr_in server_sockaddr,client_sockaddr;
  35.         fd_set        reader_fds, error_fds;
  36.         int res = 0;
  37.         int sin_size;
  38.         int nread;
  39.         char *rcvbuffer;
  40.         pthread_t thread_send_msg;
  41.         int optval;
  42.         socklen_t optlen = sizeof(optval);



  43.         /*创建socket连接*/
  44.         if((server_sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
  45.         {
  46.                 perror("socket");
  47.                 server_sockfd = -1;
  48.                 goto exit;
  49.         }               

  50.         /*enable address reuse */
  51.         int on;
  52.         int ret;
  53.         on = 1;
  54.         ret = setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
  55.        
  56.         /*设置sockaddr_in 结构体中相关参数*/
  57.         server_sockaddr.sin_family = AF_INET;
  58.         server_sockaddr.sin_port = htons(7086);
  59.         server_sockaddr.sin_addr.s_addr = INADDR_ANY;
  60.         bzero(&(server_sockaddr.sin_zero),8);

  61.         /*绑定函数bind*/
  62.         if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))== -1)
  63.         {
  64.                 perror("bind");
  65.                 server_sockfd = -1;       
  66.                 goto exit;               
  67.         }

  68.         /*调用listen函数*/
  69.         if(listen(server_sockfd,10)== -1)
  70.         {
  71.                 perror("listen");       
  72.                 server_sockfd = -1;       
  73.                 goto exit;               
  74.         }

  75.         FD_ZERO(&reader_fds);
  76.         FD_SET(server_sockfd, &reader_fds);       

  77.         while(1)
  78.         {
  79.                 res = select(READER_FDSIZE, &reader_fds,(fd_set *)0, (fd_set *)0 , NULL);
  80.                 if(res < 0)
  81.                         perror("select");

  82.                 /* 有客户端连入 */
  83.                 if(FD_ISSET(server_sockfd, &reader_fds))
  84.                 {       
  85.                         sin_size = sizeof(struct sockaddr);
  86.                         client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_sockaddr,&sin_size);
  87.                         if(client_sockfd < 0)
  88.                         {                               
  89.                                 perror("accept");
  90.                                 goto exit;               
  91.                         }

  92.                         /* 开启KEEPALIVE */
  93.                         optval = 1;
  94.                         optlen = sizeof(optval);
  95.                         if(setsockopt(client_sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0)
  96.                         {
  97.                                 perror("setsockopt()");
  98.                                 close(client_sockfd);
  99.                                 goto exit;               
  100.                         }
  101.                         printf("SO_KEEPALIVE set on socket\n");

  102.                         /* 设置KEEPALIVE时间为1s */
  103.                         optval = 1;
  104.                         res = setsockopt(client_sockfd, SOL_TCP, TCP_KEEPIDLE, &optval, optlen);
  105.                         if(res < 0)
  106.                                 perror("setsockopt TCP_KEEPIDLE");

  107.                         /* 设置KEEPALIVE间隔为1s */
  108.                         res = setsockopt(client_sockfd, SOL_TCP, TCP_KEEPINTVL, &optval, optlen);
  109.                         if(res < 0)
  110.                                 perror("setsockopt TCP_KEEPIDLE");

  111.                         /* 设置KEEPALIVE重试次数为3次 */
  112.                         optval = 3;
  113.                         res = setsockopt(client_sockfd, SOL_TCP, TCP_KEEPCNT, &optval, optlen);
  114.                         if(res < 0)
  115.                                 perror("setsockopt TCP_KEEPIDLE");

  116.                         fcntl(client_sockfd,F_SETFL,O_NONBLOCK);        // 设置非阻塞
  117.                         FD_SET(client_sockfd, &reader_fds);        // client_sockfd加入reader_fds

  118.                         /* 创建发送线程 */
  119.                         ret = pthread_create(&thread_send_msg, NULL, sendmsg_thread, NULL);
  120.                         continue;
  121.                 }


  122.                 if(FD_ISSET(client_sockfd, &reader_fds))
  123.                 {
  124.                         /* 如果没有发送线程,网络断连后,select会不断返回,nread一直为0;*/
  125.                         /* 有发送线程,断连后select会阻塞,不会运行到此处 */
  126.                         ioctl(client_sockfd, FIONREAD, &nread);
  127.                         if(nread <= 0)
  128.                         {
  129.                                 printf("nread = %d\n",nread);
  130.                         }
  131.                         else
  132.                         {
  133.                                 rcvbuffer = malloc(nread);
  134.                                 res = read(client_sockfd, rcvbuffer, nread);         
  135.                                 if(nread > 0)
  136.                                 {
  137.                                         printf("receive %s\n",rcvbuffer);
  138.                                 }
  139.                                 free(rcvbuffer);
  140.                         }
  141.                 }
  142.                
  143.         }
  144. exit:
  145.         return -1;
  146. }
复制代码

论坛徽章:
0
3 [报告]
发表于 2012-05-04 10:59 |只看该作者
本帖最后由 frank529 于 2012-05-04 11:01 编辑

客户端代码,编译环境VC6.0,运行环境Window XP
  1. #include "stdafx.h"

  2. #include <Winsock2.h>
  3. #pragma comment(lib, "ws2_32.lib")

  4. int main()
  5. {
  6.         WORD wVersionRequested;    WSADATA wsaData;   
  7.         int err;        
  8.         wVersionRequested = MAKEWORD( 1, 1 );        
  9.         err = WSAStartup( wVersionRequested, &wsaData );
  10.         char recvBuf[1024] = {'\0'};
  11.         if ( err != 0 )
  12.                 return -1;

  13.         SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
  14.         SOCKADDR_IN addrSrv;
  15.         addrSrv.sin_addr.S_un.S_addr=inet_addr("192.168.1.213");    // 设置服务器IP
  16.         addrSrv.sin_family=AF_INET;
  17.         addrSrv.sin_port=htons(7086);
  18.        
  19.         err = connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
  20.         if(err < 0)
  21.         {
  22.                 printf("connect error\n");
  23.                 return -1;
  24.         }

  25.         while(1)
  26.         {
  27.                 #if 服务器开启了发送线程
  28.                 if(recv(sockClient,recvBuf,1024,0) > 0)
  29.                 {
  30.                         printf("%s\n",recvBuf);
  31.                         memset(recvBuf,0,1024);

  32.                         err = send(sockClient,"hello server",strlen("hello server")+1,0);
  33.                         if(err != strlen("hello server")+1)
  34.                         {
  35.                                 printf("send error\n");
  36.                                 return -1;       
  37.                         }
  38.                 }
  39.                 #else 服务器只接收
  40.                 err = send(sockClient,"hello server",strlen("hello server")+1,0);
  41.                 if(err != strlen("hello server")+1)
  42.                 {
  43.                         printf("send error\n");
  44.                         return -1;       
  45.                 }

  46.                 Sleep(2000);
  47.                 #endif
  48.         }

  49.         return 0;
  50. }
复制代码

论坛徽章:
0
4 [报告]
发表于 2012-05-04 11:37 |只看该作者
擦,http://www.cnblogs.com/liuweijian/archive/2009/12/27/1633142.html这篇文章里说:

如果心搏函数要维护客户端的存活,即服务器必须每隔一段时间必须向客户段发送一定的数据,那么使用SO_KEEPALIVE是有很大的不足的。因为SO_KEEPALIVE选项指"此套接口的任一方向都没有数据交换",我不知道大家是怎么理解这个实现的。在Linux 2.6系列上,上面话的理解是只要打开SO_KEEPALIVE选项的套接口端检测到数据发送或者数据接受就认为是数据交换。

因此在这种情况下使用 SO_KEEPALIVE选项 检测对方是否非正常连接是完全没有作用的,在每隔一段时间发包的情况, keep-alive的包是不可能被发送的。上层程序在非正常端开的情况下是可以正常发送包到缓冲区的。非正常端开的情况是指服务器没有收到"FIN" 或者 "RST"包。

也就是说,只要你在发数据,keepalive包就不会发送。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP