赞
踩
在TCP网络编程模型中,无论是客户端还是服务端,在网络编程的过程中都需要判断连接的对方网络状态是否正常。在linux系统中,有很多种方式可以判断连接的对方网络是否已经断开。
在写TCP连接数据的时候,如果对方连接已经正常断开,那么写数据端将会收到一个SIGPIPE信号,可以通过这个信号知道对方连接已经断开。该信号信号会终止当前进程,如果不在对方连接断开不退出进程,那么就应该注册信号函数。
同时,如果对方连接已经正常断开,那么write写数据端将会返回写错误。返回的写长度为-1,此时的错误码为:32,对应错误值为EPIPE;因此可以写数据时write的返回值和错误码来判断对方连接是否已经断开了。
如果当前是默认的阻塞模式读取,那么此时read读取返回的长度为0,错误码也是为0,其实表示读取成功。这里需要注意read 和recv接口的默认返回值是不一样的,使用recv接口也会返回EPIPE错误码。
- /************************************************************
- *Copyright (C),lcb0281at163.com lcb0281atgmail.com
- *FileName: 01_client_tcp.c
- *BlogAddr: caibiao-lee.blog.csdn.net
- *Description: TCP 客户端收发数据
- *Date: 2020-01-04
- *Author: Caibiao Lee
- *Version: V1.0
- *Others:
- 通过read write 函数的返回值和错误码判断对方连接是否已经断开
- *History:
- ***********************************************************/
- #include <sys/uio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <unistd.h>
- #include <arpa/inet.h>
- #include <signal.h>
- #include <errno.h>
- #include <netinet/ip.h>
- #include <netinet/tcp.h>
-
- #define SERVER_IP_ADDR "192.168.1.111"
- #define PORT 8888 /* 侦听端口地址 */
-
- void sig_proccess(int signo)
- {
- printf("Catch a exit signal\n");
- exit(0);
- }
-
- void sig_pipe(int sign)
- {
- printf("Catch a SIGPIPE signal\n");
-
- /* 释放资源 */
- }
-
-
- void process_conn_client(int s32SocketFd)
- {
- int size = 0;
- char buffer[1024] = {0};
- char *sendData = "I am client";
-
- for(;;)
- {
- size = write(s32SocketFd, sendData, strlen(sendData)+1);
- if(size!=strlen(sendData)+1)
- {
- printf("write data error size=%d errno=%d \n",size,errno);
- //return ;
- }
-
- size = read(s32SocketFd, buffer, 1024);
- if(size<=0)
- {
- printf("read data error size=%d errno=%d \n",size,errno);
- //return ;
- }else
- {
- printf("recv Data: %s\n",buffer);
- }
- sleep(1);
-
- }
- }
-
- int main(int argc, char *argv[])
- {
-
- struct sockaddr_in server_addr;
- int l_s32SocketFd = 0;
-
-
- signal(SIGINT, sig_proccess);
- signal(SIGPIPE, sig_pipe);
-
- /* 建立一个流式套接字 */
- l_s32SocketFd = socket(AF_INET, SOCK_STREAM, 0);
- if(l_s32SocketFd < 0)
- {/* 出错 */
- printf("socket error\n");
- return -1;
- }
-
- /* 设置服务器地址 */
- bzero(&server_addr, sizeof(server_addr)); /* 清0 */
- server_addr.sin_family = AF_INET; /* 协议族 */
- server_addr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);/*服务器IP地址*//* 本地地址 */
- server_addr.sin_port = htons(PORT); /* 服务器端口 */
-
- /* 连接服务器 */
- connect(l_s32SocketFd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
- process_conn_client(l_s32SocketFd); /* 客户端处理过程 */
-
- close(l_s32SocketFd); /* 关闭连接 */
-
- return 0;
- }
-
- /************************************************************
- *Copyright (C),lcb0281at163.com lcb0281atgmail.com
- *FileName: 01_server_tcp.c
- *BlogAddr: caibiao-lee.blog.csdn.net
- *Description: TCP 客户端收发数据
- *Date: 2020-01-04
- *Author: Caibiao Lee
- *Version: V1.0
- *Others:
- 通过read write 函数的返回值和错误码判断对方连接是否已经断开
- *History:
- ***********************************************************/
- #include <sys/uio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <unistd.h>
- #include <arpa/inet.h>
- #include <signal.h>
- #include <errno.h>
- #include <netinet/ip.h>
- #include <netinet/tcp.h>
-
- #define SERVER_IP_ADDR "192.168.1.111"
- #define PORT 8888 /* 侦听端口地址 */
- #define BACKLOG 2 /* 侦听队列长度 */
-
- void sig_proccess(int signo)
- {
- printf("Catch a exit signal\n");
- exit(0);
- }
-
- void sig_pipe(int sign)
- {
- printf("Catch a SIGPIPE signal\n");
-
- /* 释放资源 */
- }
-
- /* 服务器对客户端的处理 */
- void process_conn_server(int s32SocketFd)
- {
- int size = 0;
- char buffer[1024]; /* 数据的缓冲区 */
-
- for(;;)
- {
- /* 从套接字中读取数据放到缓冲区buffer中 */
- size = read(s32SocketFd, buffer, 1024);
- if(size==0)
- {/* 没有数据 */
- printf("read size = %d, error %d \n",size,errno);
- //return;
- }else if(size<0)
- {
- printf("read size = %d, error %d \n",size,errno);
- //return ;
- }else
- {
- printf("recv data:%s \n",buffer);
-
- }
- memset(buffer,0,sizeof(buffer));
- /* 构建响应字符,为接收到客户端字节的数量 */
- strcpy(buffer,"I am server");
- size = write(s32SocketFd, buffer, strlen(buffer)+1);/* 发给客户端 */
- if((strlen(buffer)+1)==size)
- {
-
- }else
- {
- printf("write data error size = %d, errno=%d\n",size,errno);
- //return ;
- }
- sleep(1);
- }
- }
-
- int main(int argc, char *argv[])
- {
- int l_s32ServerFd = -1;
- int l_s32ClientrFd = -1;
- struct sockaddr_in server_addr; /* 服务器地址结构 */
- struct sockaddr_in client_addr; /* 客户端地址结构 */
- int l_s32Ret = 0; /* 返回值 */
- pid_t pid; /* 分叉的进行id */
-
- signal(SIGINT, sig_proccess);
- signal(SIGPIPE, sig_pipe);
-
-
- /* 建立一个流式套接字 */
- l_s32ServerFd = socket(AF_INET, SOCK_STREAM, 0);
- if(l_s32ServerFd < 0)
- {/* 出错 */
- printf("socket error\n");
- return -1;
- }
-
- /* 设置服务器地址 */
- bzero(&server_addr, sizeof(server_addr)); /* 清0 */
- server_addr.sin_family = AF_INET; /* 协议族 */
- server_addr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);/*服务器IP地址*/
- server_addr.sin_port = htons(PORT); /* 服务器端口 */
-
-
- /*设置IP地址可以重复绑定*/
- int l_s32UseAddr = 1;
- if(setsockopt(l_s32ServerFd, SOL_SOCKET, SO_REUSEADDR, &l_s32UseAddr, sizeof(int)) < 0)
- {
- printf("%s %d\tsetsockopt error! Error code: %d,Error message: %s\n",
- __FUNCTION__, __LINE__, errno, strerror(errno));
- return -2;
- }
-
- /* 绑定地址结构到套接字描述符 */
- l_s32Ret = bind(l_s32ServerFd, (struct sockaddr*)&server_addr, sizeof(server_addr));
- if(l_s32Ret < 0)
- {/* 出错 */
- printf("bind error\n");
- return -1;
- }
-
- /* 设置侦听 */
- l_s32Ret = listen(l_s32ServerFd, BACKLOG);
- if(l_s32Ret < 0)
- {/* 出错 */
- printf("listen error\n");
- return -1;
- }
-
- /* 主循环过程 */
- for(;;)
- {
- int addrlen = sizeof(struct sockaddr);
- /* 接收客户端连接 */
- l_s32ClientrFd = accept(l_s32ServerFd, (struct sockaddr*)&client_addr, &addrlen);
- if(l_s32ClientrFd < 0)
- { /* 出错 */
- continue; /* 结束本次循环 */
- }
-
- /* 建立一个新的进程处理到来的连接 */
- pid = fork(); /* 分叉进程 */
- if( pid == 0 )
- { /* 子进程中 */
- close(l_s32ServerFd); /* 在子进程中关闭服务器的侦听 */
- process_conn_server(l_s32ClientrFd);/* 处理连接 */
- }else
- {
- close(l_s32ClientrFd); /* 在父进程中关闭客户端的连接 */
- }
- }
- }
-
select实际是IO复用的一个接口,它可以同时检测多个连接是否有数据可读写操作,并且可以设置检测的超时时间。
在点对点的连接中如果select超时,它返回值为0;
- /********************************************************
- Function: process_conn_server
- Description: 服务器对客户端的处理
- Input: s32SocketFd :服务端接收到客户端连接的ID;
- OutPut: none
- Return: 0: success,none 0:error
- Others: 通过select判断客户端的连接状态
- Author: Caibiao Lee
- Date: 2020-01-04
- *********************************************************/
- void process_conn_server(int s32SocketFd)
- {
- int size = 0;
- int l_s32Ret = 0;
- char buffer[1024]; /* 数据的缓冲区 */
- fd_set l_stReadfd;
- struct timeval l_stTimeout={0};
-
- for(;;)
- {
- l_stTimeout.tv_sec=0;
- l_stTimeout.tv_usec=10000;
- FD_ZERO(&l_stReadfd);
- FD_SET(s32SocketFd ,&l_stReadfd);
- l_s32Ret = select(s32SocketFd+1, &l_stReadfd,NULL,NULL, &l_stTimeout);
- if (l_s32Ret<=0)
- {
- printf("select error l_s32Ret=%d errno=%d\n",l_s32Ret,errno);
- usleep(100000);
- }
- else if(FD_ISSET(s32SocketFd,&l_stReadfd))
- {
- printf("l_s32Ret = %d \n",l_s32Ret);
- /* 从套接字中读取数据放到缓冲区buffer中 */
- size = read(s32SocketFd, buffer, 1024);
- if(size==0)
- {/* 没有数据 */
- printf("read size = %d, error %d \n",size,errno);
- //return;
- }else if(size<0)
- {
- printf("read size = %d, error %d \n",size,errno);
- //return ;
- }else
- {
- printf("recv data:%s \n",buffer);
- }
- }
-
- memset(buffer,0,sizeof(buffer));
- /* 构建响应字符,为接收到客户端字节的数量 */
- strcpy(buffer,"I am server");
- size = write(s32SocketFd, buffer, strlen(buffer)+1);/* 发给客户端 */
- if((strlen(buffer)+1)==size)
- {
-
- }else
- {
- printf("write data error size = %d, errno=%d\n",size,errno);
- //return ;
- }
- sleep(1);
- }
- }
通过getsockopt函数可以获取TCP连接的连接状态,当状态为ESTABLISHED的时候表示该连接正常。TCP的其它状态还有:
功能代码如下:
- /********************************************************
- Function: check_tcp_alive
- Description: 通过TCP_INFO查询网络状态
- Input: s32SocketFd :服务端接收到客户端连接的ID;
- OutPut: none
- Return: 0: success,none 0:error
- Others:
- Author: Caibiao Lee
- Date: 2020-01-04
- *********************************************************/
- int check_tcp_alive(int s32SocketFd)
- {
- while(1)
- {
- printf("alive s32SocketFd = %d \n",s32SocketFd);
- if(s32SocketFd>0)
- {
- struct tcp_info info;
- int len = sizeof(info);
-
- getsockopt(s32SocketFd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
-
- printf("info.tcpi_state = %d\n",info.tcpi_state);
- if(info.tcpi_state == TCP_ESTABLISHED)
- {
- printf("connect ok \r\n");
- //return 0;
- }
- else
- {
- printf("connect error\r\n");
- //return -1;
- }
- }
- sleep(1);
- printf("\n\n");
- }
- }
选项SO_KEEPALIVE用于设置TCP连接的保持,当设置此项后,连接会测试连接的状态。这个选项用于可能长时间没有数据交流的连接,通常在服务器端进行设置。
当设置SO_KEEPALIVE选项后,如果在两个小时内没有数据通信时,TCP会自动发送一个活动探测数据报文,对方必须对此进行响应,通常有如下3种情况。
SO_KEEPALIVE的使用场景主要是在可能发送长时间无数据响应的TCP连接,例如Telnet会话,经常会出现打开一个telnet客户端后,长时间不用的情况,这需要服务器或 者客户端有一个探测机制知道对方是否仍然活动。根据探测结果服务器会释放己经失效的客户端,保证服务器资源的有效性,例如有的telnet客户端没有按照正常步骤进行关闭。
网上有不少资料介绍不推荐使用SO_KEEPALIVE来判断网络连接是否断开,具体原因没有去追踪,这里不再介绍它的使用。
这个是通过套接字的SO_RCVTIMEO、SO_SNDTIMEO来设置收发数据超时。对于前面的前面的几种判断方式,都是基于对方正常网络断开后,主机才能够正常的判断到网络状态。如果连接的某一方突然断电,主机并不能知道对方设备突然断电,通过TCP_INFO查询到的也是网络正常,但实际情况是这是网络连接已经断开了。
这时,可以使用收发数据超时来判断:
如果设置的时间没有收到数据,read时会返回-1,同时有错误码EAGAIN产生,这时是可以判断出对连接已经断开了。
这种方式的确定就是,如果设定的一段时间没有收发数据,就会被判断为超时断开连接。
- /********************************************************
- Function: process_conn_server
- Description: 通过设置收发操作判断对方连接已经断开了
- Input: s32SocketFd :服务端接收到客户端连接的ID;
- OutPut: none
- Return: 0: success,none 0:error
- Others:
- Author: Caibiao Lee
- Date: 2020-01-04
- *********************************************************/
- void process_conn_server(int s32SocketFd)
- {
- int size = 0;
- char buffer[1024]; /* 数据的缓冲区 */
- int optlen = -1; /* 整型的选项类型值 */
- int l_s32Ret = 0;
-
- /* 设置发送和接收超时时间 */
- struct timeval tv;
- tv.tv_sec = 10; /* 1秒 */
- tv.tv_usec = 200000;/* 200ms */
- optlen = sizeof(tv);
- l_s32Ret = setsockopt(s32SocketFd, SOL_SOCKET, SO_RCVTIMEO, &tv, optlen); /* 设置接收超时时间 */
- if(l_s32Ret == -1){/* 设置接收超时时间失败 */
- printf("设置接收超时时间失败\n");
- }
-
- l_s32Ret = setsockopt(s32SocketFd, SOL_SOCKET, SO_SNDTIMEO, &tv, optlen);/* 设置发送超时时间 */
- if(l_s32Ret == -1){
- printf("设置发送超时时间失败\n");
- }
-
- for(;;)
- {
- /* 从套接字中读取数据放到缓冲区buffer中 */
- size = read(s32SocketFd, buffer, 1024);
- if(size==0)
- {/* 没有数据 */
- printf("read size = %d, error %d \n",size,errno);
- //return;
- }else if(size<0)
- {
- printf("read size = %d, error %d \n",size,errno);
- //return ;
- }else
- {
- printf("recv data:%s \n",buffer);
-
- }
- memset(buffer,0,sizeof(buffer));
- /* 构建响应字符,为接收到客户端字节的数量 */
- strcpy(buffer,"I am server");
- size = write(s32SocketFd, buffer, strlen(buffer)+1);/* 发给客户端 */
- if((strlen(buffer)+1)==size)
- {
-
- }else
- {
- printf("write data error size = %d, errno=%d\n",size,errno);
- //return ;
- }
- sleep(1);
- }
- }
在一些比较重要的命令收发链接中,一般是客户端和服务端会建立心跳机制,心跳时间间隔根据不同的业务需求而不同。当约定的时间段内没有收到心跳数据包,就可以判断对方是否已经断开了连接。
这种方式非常简单,对于嵌入式设备而言,主要的缺点是心跳会耗费流量,同时会增加一点点系统负载,并且不适合并发连接的情况。
以上就是现在比较常用的判断网络连接的方法。 如有错误,欢迎指出!
----------------------------------------------------------------2022.08.28----------------------------------------------------------------
|公|_新的文章内容和附件工程文件
|众|_已更新在博客首页和:
|号|:liwen01
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。