keepalive机制的自定义 远端socket关闭造成send操作崩溃 非阻塞式的connect操作 实时检测socket上是否有错误
server.c
点击(此处)折叠或打开
- /*
- * 监听, 收到内容变大写后回写
- */
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <errno.h>
- #include <unistd.h>
- #include <signal.h>
- #include <sys/wait.h>
- #include <netdb.h>
- void sig_handler(int signo)
- {
- pid_t pid;
- int stat;
- pid=waitpid(-1,&stat,WNOHANG);
- while(pid>0){
- printf("child process terminated (PID: %ld)\n",(long)getpid());
- pid=waitpid(-1,&stat,WNOHANG);
- }
- return;
- }
- int main(int argc,char *argv[])
- {
- socklen_t clt_addr_len;
- int listen_fd;
- int com_fd;
- int ret;
- int i;
- static char recv_buf[1024];
- int len;
- int port;
- int sleep_cnt;
- pid_t pid;
- char sys_cmd[20];
- struct sockaddr_in clt_addr;
- struct sockaddr_in srv_addr;
- port=5000;
- if(signal(SIGCHLD,sig_handler)<0){
- perror("cannot set the signal");
- return 1;
- }
- listen_fd=socket(PF_INET,SOCK_STREAM,0);
- if(listen_fd<0){
- perror("cannot create listening socket");
- return 1;
- }
- int flag=1;
- len=sizeof(int);
- setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &flag, len);
- memset(&srv_addr,0,sizeof(srv_addr));
- srv_addr.sin_family=AF_INET;
- srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
- srv_addr.sin_port=htons(port);
- ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
- if(ret==-1){
- perror("cannot bind server socket");
- close(listen_fd);
- return 1;
- }
- ret=listen(listen_fd,5);
- if(ret==-1){
- perror("cannot listen the client connect request");
- close(listen_fd);
- return 1;
- }
-
-
- while(1)
- {
- len=sizeof(clt_addr);
- com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);
- if(com_fd<0){
- if(errno==EINTR){
- continue;
- }else{
- perror("cannot accept client connect request");
- close(listen_fd);
- return 1;
- }
- }
- pid=fork();
- if(pid<0){
- perror("cannot create the child process");
- close(listen_fd);
- return 1;
- }else if(pid==0)
- {
-
- printf("the child child process is %ld \n",(long)getpid());
- while((len=recv(com_fd,recv_buf,1024,0))>0)
- {
- printf("Message from client(%d): %s\n",len,recv_buf);
- if(recv_buf[0]=='@')
- {
- close(com_fd);
- return 0;
- }
- for(i=0;i<len;i++)
- recv_buf[i]=toupper(recv_buf[i]);
- write(com_fd,recv_buf,len);
- }
- close(com_fd);
- printf("return while(1) \n");
- return 0;
- }else
- {
- close(com_fd);
- printf("the child parent process is %ld \n",(long)getpid());
- }
- }
-
- return 0;
- }
使用非阻塞式的connect操作, 对网络异常和对端socket断开的情况有简单的处理。
点击(此处)折叠或打开
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/ioctl.h>
- #include <fcntl.h>
- #include <sys/select.h>
- #include <arpa/inet.h>
- #include <sys/socket.h>
- #include <netinet/tcp.h>
- #include <signal.h>
- int sockfd = -1;
- static int SockSelect(int sockfd, fd_set *readfs, fd_set *writefs, fd_set *excepfs, struct timeval *vtimeout)
- {
- int iret;
- while ((iret=select(sockfd+1, readfs, writefs, excepfs, vtimeout)) < 0)
- {
- if (errno==EINTR) continue;
- else break;
- }
- return iret;
- }
- int check_sock_error(int s)
- {/*
- fd_set exceptSet;
- struct timeval vtimeout;
-
- FD_ZERO(&exceptSet);
- FD_SET(s, &exceptSet);
-
- vtimeout.tv_sec = 0;
- vtimeout.tv_usec = 100*1000;
-
- int iRet =SockSelect(s , NULL, NULL, &exceptSet, &vtimeout);
- if (iRet <= 0) return iRet;
- if (FD_ISSET(s, &exceptSet)) return 1;
- */
- int error;
- int len = sizeof(int);
- getsockopt(s, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
- if(error != 0) {
- fprintf(stderr, "check_sock_error = [%d] \n", error);
- }
- return error;
- }
- int check_sock_send(int s)
- {
- fd_set sendSet;
- struct timeval vtimeout;
-
- FD_ZERO(&sendSet);
- FD_SET(s, &sendSet);
-
- vtimeout.tv_sec = 1;
- vtimeout.tv_usec = 0;
-
- int iRet =SockSelect(s , NULL, &sendSet, NULL, &vtimeout);
- if (iRet <= 0) return iRet;
- if (FD_ISSET(s, &sendSet))
- return 1;
- }
- int check_sock_recv(int s)
- {
- fd_set sendSet;
- struct timeval vtimeout;
-
- FD_ZERO(&sendSet);
- FD_SET(s, &sendSet);
-
- vtimeout.tv_sec = 5;
- vtimeout.tv_usec = 0;
-
- int iRet =SockSelect(s , &sendSet, NULL, NULL, &vtimeout);
- if (iRet <= 0) return iRet;
- if (FD_ISSET(s, &sendSet))
- return 1;
- }
- int set_keepalive(int s)
- {
- int keepAlive = 1;//设定KeepAlive
- int keepIdle = 1;//开始首次KeepAlive探测前的TCP空闭时间
- int keepInterval = 1;//两次KeepAlive探测间的时间间隔
- int keepCount = 1;//判定断开前的KeepAlive探测次数
- int flag=1;
- int len=sizeof(int);
- setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &flag, len);
- struct timeval timeout={3,0};//3s
- setsockopt(s,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));
- setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
- if(setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1)
- {
- fprintf(stderr, " SO_KEEPALIVE failed .\n");
- }
- if(setsockopt(s,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1)
- {
- fprintf(stderr, " TCP_KEEPIDLE failed .\n");
- }
- if(setsockopt(s,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1)
- {
- fprintf(stderr, " TCP_KEEPINTVL failed .\n");
- }
- if(setsockopt(s,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1)
- {
- fprintf(stderr, " TCP_KEEPCNT failed .\n");
- }
- }
- void closesocket(int s)
- {
- if (s != -1) {
- shutdown(s, SHUT_RDWR);
- close(s);
- }
- }
- static void proc_sig_pipe(int signo)
- {
- fprintf(stderr, "*** maybe Opposite Side socket closed. ***\n");
- //closesocket(sockfd);
- //sockfd = -1;
- }
- /*初始化信号句柄*/
- void regSigProcHandle(void)
- {
- struct sigaction sigact;
- sigset_t block_mask;
- //屏蔽本App之外所有的信号
- sigfillset(&block_mask);
- sigdelset(&block_mask, SIGPIPE);
- sigprocmask(SIG_BLOCK, &block_mask, NULL);
- //信号处理
- sigfillset(&sigact.sa_mask);
- sigact.sa_handler=proc_sig_pipe;
- sigaction(SIGPIPE, &sigact, NULL);
- }
- int main()
- {
- struct sockaddr_in servaddr;
- int ret;
- /*register some signals */
- regSigProcHandle();
- while (1) {
- if (sockfd == -1) {
- //1. 连接服务器
- //此处不应当使用阻塞式调用,因为当网络持续不通时,此处可能会变得无限长时间( <= 2 * MSL = (1-4分钟))的阻塞。
- //建议使用 sockfd 设置成非阻塞状态。
- //然后connect 调用完毕后,使用select检测sockfd的可发送状态,
- //若如select超时,则说明connect超时,
- //若成功,则把sockfd从新设置成阻塞状态,进行下一步操作。
- fprintf(stderr, "------- reconnected.-------------- \n");
- if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
- {
- fprintf(stderr, "socket create error. \n");
- sleep(1);
- continue;
- }
- //set non-blocking
- int flags;
- flags = fcntl(sockfd, F_GETFL); //if (flags<0) { error();}
- flags |= O_NONBLOCK;
- fcntl(sockfd, F_SETFL, flags);
- /*
- int nonblock = 1;
- if (ioctl(sockfd, FIONBIO, &nonblock) < 0) {
- close(sockfd);
- perror("ioctl FIONBIO");
- }
- */
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = inet_addr("192.168.10.147");
- servaddr.sin_port = htons(5000);
- if ((ret = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) == -1) {
- if (errno == EINPROGRESS) {
- int error;
- int len = sizeof(int);
- fd_set set;
- struct timeval tv_timeout;
- tv_timeout.tv_sec = 10;
- tv_timeout.tv_usec = 0;
- FD_ZERO(&set);
- FD_SET(sockfd, &set);
- if(select(sockfd + 1, NULL, &set, NULL, &tv_timeout) > 0) {
- getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
- if(error != 0) {
- fprintf(stderr, "socket error. [%d] \n", error);
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- } else { //timeout or select error
- fprintf(stderr, "connect timeout. \n");
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- }
- else { //exception
- fprintf(stderr, "connect exception. \n");
- sockfd = -1;
- sleep(1);
- continue;
- }
- }
- else if (ret == 0) { // connect success.
- fprintf(stderr, "connect success. \n");
- }
- // reconfig to block mode
- int nonblock = 0;
- if (ioctl(sockfd, FIONBIO, &nonblock) < 0) {
- closesocket(sockfd);
- perror("ioctl FIONBIO");
- }
- set_keepalive(sockfd);
- }
-
- //2. 发送数据
- //此处应当先判断当前连接是否有异常。
- //1. 异常判断 getsockopt(s, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
- //2. 规避远程对端的sock关闭,此处会跑出 SIGPIPE异常造成崩溃。
- //3. 尽量改用select方式先进行超时处理。
- /*
- //error
- //fprintf(stderr, "socket check error. \n");
- //getc(stdin);
- ret = check_sock_error(sockfd);
- if (ret > 0) {
- fprintf(stderr, "socket has error. \n");
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- else {
- fprintf(stderr, "socket is OK.\n");
- }
- //*/
- //send
- //fprintf(stderr, "socket send check. \n");
- //getc(stdin);
- ret = check_sock_send(sockfd);
- if (ret <= 0) {
- fprintf(stderr, "socket has error for send. \n");
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- else {
- fprintf(stderr, "socket no error for send. \n");
- }
- const char pSend[]="iibull";
- char pRecv[256];
- ret = send(sockfd, pSend, strlen(pSend), 0);
- if (ret <= 0) {
- fprintf(stderr, "send error. [%d]\n", ret);
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- else {
- fprintf(stderr, "send success. [%d]\n", ret);
- }
- //3. 收取数据
- //1. 首先当检测socket上是否有错误。
- //2. 尽量改用select方式先进行分段超时处理,以节省系统资源。
- /*
- //error
- fprintf(stderr, "socket error check. \n");
- getc(stdin);
- ret = check_sock_error(sockfd);
- if (ret > 0) {
- fprintf(stderr, "socket has error. \n");
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- else {
- fprintf(stderr, "socket no error for recv. \n");
- }
- //*/
- //recv
- //fprintf(stderr, "socket recv check. \n");
- //getc(stdin);
- ret = check_sock_recv(sockfd);
- if (ret <= 0) {
- fprintf(stderr, "socket has error for recv. \n");
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- else {
- fprintf(stderr, "socket is OK for recv. \n");
- }
- memset(pRecv, sizeof(pRecv), 0);
- ret = recv(sockfd, pRecv, sizeof(pRecv)-1, 0);
- if (ret <= 0) {
- fprintf(stderr, "recv error. [%d]\n", ret);
- closesocket(sockfd);
- sockfd = -1;
- sleep(1);
- continue;
- }
- else {
- fprintf(stderr, "### recv OK [%s]###\n", pRecv);
- }
- sleep(1);
- }
- }
参考:
http://blog.patpig.com/2012/06/c-socket-%E5%85%B3%E4%BA%8Econnect%E8%B6%85%E6%97%B6%E8%AE%BE%E7%BD%AE/
http://www.doc88.com/p-902850355716.html
http://www.cnblogs.com/jason-jiang/archive/2006/11/03/549337.html
http://dongxicheng.org/network/non-block-connect-implemention/
linux和windows下用setsockopt设置SO_SNDTIMEO,SO_RCVTIMEO的参数的一点区别
linux和windows下用setsockopt设置SO_SNDTIMEO,SO_RCVTIMEO的参数的一点区别
UDP的socket在某些情况:如对方关闭时,本地可能sendto不出去数据,然后recvfrom就会被阻塞,这时就需要设置 这两个参数的值提高程序质量。
linux:struct timeval timeout={3,0};//3s
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
如果ret==0 则为成功,-1为失败,这时可以查看errno来判断失败原因
int recvd=recv(sock_fd,buf,1024,0);
if(recvd==-1&&errno==EAGAIN)
{
printf("timeout\n");
}
windows:
int timeout = 3000; //3s
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
而solaris,则不支持。