socket編程 —— 非阻塞socket (轉)---例子已上傳至文件中


在上一篇文章 《socket編程——一個簡單的例子》 http://blog.csdn.net/wind19/archive/2011/01/21/6156339.aspx 中寫了一個簡單的tcp socket通信程序,可以進行數據的交互,但有一個問題是這個程序是阻塞的,任何socket函數都要等返回后才能進行下一步動作,如果recv一直沒有數據,那么就一直不會返回,整個進程就阻塞在那。所以我們要進行改造一下,讓程序不再阻塞在那,而是在有數據到來的時候讀一下數據,有數據要寫的時候發送一下數據。

 

    設置阻塞模式的函數一般由兩個fcntl 和 ioctl

 

先放源程序,服務器端還是阻塞的,客服端改成非阻塞的,只是作為一個例子

  1. /*server.c*/ 
  2. #include <stdio.h> 
  3. #include <string.h> 
  4. #include <sys/socket.h> 
  5. #include <netinet/in.h> 
  6. #include <stdlib.h> 
  7. #include <syslog.h> 
  8. #include <errno.h> 
  9. #define MAX_LISTEN_NUM 5 
  10. #define SEND_BUF_SIZE 100 
  11. #define RECV_BUF_SIZE 100 
  12. #define LISTEN_PORT 1010 
  13. int main() 
  14.   int listen_sock = 0; 
  15.   int app_sock = 0; 
  16.   struct sockaddr_in hostaddr; 
  17.   struct sockaddr_in clientaddr; 
  18.   int socklen = sizeof(clientaddr); 
  19.   char sendbuf[SEND_BUF_SIZE] = {0}; 
  20.   char recvbuf[RECV_BUF_SIZE] = {0}; 
  21.   int sendlen = 0; 
  22.   int recvlen = 0; 
  23.   int retlen = 0; 
  24.   int leftlen = 0; 
  25.   char *ptr = NULL; 
  26.   int flags = 1; 
  27.   int flaglen = sizeof(flags); 
  28.   memset((void *)&hostaddr, 0, sizeof(hostaddr)); 
  29.   memset((void *)&clientaddr, 0, sizeof(clientaddr)); 
  30.   hostaddr.sin_family = AF_INET; 
  31.   hostaddr.sin_port = htons(LISTEN_PORT); 
  32.   hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
  33.   listen_sock = socket(AF_INET, SOCK_STREAM, 0); 
  34.   if(listen_sock < 0) 
  35.   { 
  36.       syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__); 
  37.       exit(1); 
  38.   } 
  39.   if(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flags, flaglen) < 0) 
  40.   { 
  41.       syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__); 
  42.       exit(1); 
  43.   } 
  44.   if(bind(listen_sock, (struct sockaddr *)&hostaddr, sizeof(hostaddr)) < 0) 
  45.   { 
  46.       syslog(LOG_ERR, "%s:%d, bind socket failed", __FILE__, __LINE__); 
  47.       exit(1); 
  48.   } 
  49.   if(listen(listen_sock, MAX_LISTEN_NUM) < 0) 
  50.   { 
  51.       syslog(LOG_ERR, "%s:%d, listen failed", __FILE__, __LINE__); 
  52.       exit(1); 
  53.   } 
  54.   while(1) 
  55.   { 
  56.       app_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &socklen); 
  57.       if(app_sock < 0) 
  58.      { 
  59.         syslog(LOG_ERR, "%s:%d, accept failed", __FILE__, __LINE__); 
  60.         exit(1); 
  61.      } 
  62.      sprintf(sendbuf, "welcome %s:%d here!/n", inet_ntoa(clientaddr.sin_addr.s_addr), clientaddr.sin_port); 
  63.      //send data 
  64.      sendlen = strlen(sendbuf) +1; 
  65.      retlen = 0; 
  66.      leftlen = sendlen; 
  67.      ptr = sendbuf; 
  68.      //while(leftlen) 
  69.      { 
  70.          syslog(LOG_ERR, "%s:%d, before send", __FILE__, __LINE__); 
  71.          retlen = send(app_sock, ptr, sendlen, 0); 
  72.       if(retlen < 0) 
  73.       { 
  74.           if(errno == EINTR) 
  75.             retlen = 0; 
  76.         else 
  77.             exit(1); 
  78.       } 
  79.       leftlen -= retlen; 
  80.       ptr += retlen; 
  81.       syslog(LOG_ERR, "%s:%d, after send, retlen = %d", __FILE__, __LINE__, retlen); 
  82.      } 
  83.      //receive data 
  84.      recvlen = 0; 
  85.      retlen = 0; 
  86.      ptr = recvbuf; 
  87.      leftlen = RECV_BUF_SIZE -1; 
  88.      //do 
  89.      { 
  90.          retlen = recv(app_sock, ptr, leftlen, 0) ; 
  91.       if(retlen < 0) 
  92.       { 
  93.           if(errno == EINTR) 
  94.             retlen = 0; 
  95.         else 
  96.             exit(1); 
  97.       } 
  98.       recvlen += retlen; 
  99.       leftlen -= retlen; 
  100.       ptr += retlen; 
  101.      } 
  102.      //while(recvlen && leftlen); 
  103.      printf("receive data is : %s", recvbuf); 
  104.     close(app_sock); 
  105.   } 
  106.   close(listen_sock); 
  107.    
  108.   return 0; 
  109.    
  110.    

 

 

  1. /*clent.c*/ 
  2. #include <stdio.h> 
  3. #include <string.h> 
  4. #include <sys/socket.h> 
  5. #include <netinet/in.h> 
  6. #include <syslog.h> 
  7. #include <errno.h> 
  8. #include <stdlib.h> 
  9. #include <fcntl.h> 
  10. #include <stdbool.h> 
  11. #include <sys/select.h>  
  12. #include <sys/times.h>  
  13. #define MAX_LISTEN_NUM 5 
  14. #define SEND_BUF_SIZE 100 
  15. #define RECV_BUF_SIZE 100 
  16. #define SERVER_PORT 1010 
  17. #define MAX_CONNECT_TIMES 5 
  18. bool Connect(int sock_fd, struct sockaddr* pser_addr, int* paddrlen) 
  19.     if(connect(sock_fd, pser_addr, *paddrlen) < 0) 
  20.     { 
  21.         if(errno == EISCONN) 
  22.         { 
  23.             syslog(LOG_ERR, "%s:%d, connect socket completed", __FILE__, __LINE__); 
  24.          return true; 
  25.         } 
  26.         if(errno != EINPROGRESS && errno != EALREADY && errno != EWOULDBLOCK) 
  27.         { 
  28.             syslog(LOG_ERR, "%s:%d, connect socket failed", __FILE__, __LINE__); 
  29.             return false; 
  30.         } 
  31.     else 
  32.         { 
  33.             syslog(LOG_ERR, "%s:%d, connect socket does not completed", __FILE__, __LINE__); 
  34.         } 
  35.     } 
  36.     else 
  37.     { 
  38.         syslog(LOG_ERR, "%s:%d, connect socket completed", __FILE__, __LINE__); 
  39.     return true; 
  40.     } 
  41.     fd_set fds_red, fds_write; 
  42.     struct timeval tval; 
  43.     int selret = 0; 
  44.     tval.tv_sec = 3; 
  45.     tval.tv_usec = 0; 
  46.     int ntrytimes = 0; 
  47.      
  48.     while(1 && ntrytimes < MAX_CONNECT_TIMES) 
  49.     { 
  50.         FD_ZERO(&fds_red); 
  51.         FD_SET(sock_fd, &fds_red); 
  52.          
  53.         FD_ZERO(&fds_write); 
  54.         FD_SET(sock_fd, &fds_write); 
  55.         syslog(LOG_ERR, "%s:%d, before select", __FILE__, __LINE__); 
  56.          
  57.         selret = select(sock_fd + 1, &fds_red, &fds_write, NULL, &tval); 
  58.          
  59.         syslog(LOG_ERR, "%s:%d, after select", __FILE__, __LINE__); 
  60.         if(selret < 0) 
  61.         { 
  62.           if(errno == EINTR) 
  63.           { 
  64.                ntrytimes++; 
  65.             continue; 
  66.           } 
  67.           else 
  68.           { 
  69.                  syslog(LOG_ERR, "%s:%d, select failed", __FILE__, __LINE__); 
  70.                  return false; 
  71.           } 
  72.         } 
  73.         else if(selret == 0) 
  74.            { 
  75.               syslog(LOG_ERR, "%s:%d, connect socket timeout", __FILE__, __LINE__); 
  76.               ntrytimes++; 
  77.            continue; 
  78.         } 
  79.         else 
  80.         { 
  81.              syslog(LOG_ERR, "%s:%d, select default", __FILE__, __LINE__); 
  82.               
  83.             if(FD_ISSET(sock_fd, &fds_red) || FD_ISSET(sock_fd, &fds_write)) 
  84.             { 
  85.                 int error = 0; 
  86.                 int len = sizeof(error); 
  87.                 int rc = getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, (void *) &error, &len); 
  88.                 if(rc == -1) 
  89.                 { 
  90.                     syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__); 
  91.                     return false; 
  92.                 } 
  93.                 else if(error) 
  94.                 { 
  95.                     syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__); 
  96.                     return false; 
  97.                 } 
  98.                 else 
  99.                 { 
  100.                     syslog(LOG_ERR, "%s:%d, connection is ok", __FILE__, __LINE__); 
  101.               return true; 
  102.                 } 
  103.                  
  104.             } 
  105.             else 
  106.             { 
  107.                 syslog(LOG_ERR, "%s:%d, no descriptor is ready", __FILE__, __LINE__); 
  108.              continue; 
  109.             } 
  110.         } 
  111.     } 
  112.      
  113.     return false; 
  114.      
  115. //return value, -1 means Recv happs error; 0 means timeout or be interupted; > 0 means ok 
  116. int Recv(int sock_fd, char * recvbuf, int recvbuflen) 
  117.     fd_set fds_red; 
  118.     struct timeval tval; 
  119.     int selret = 0; 
  120.     tval.tv_sec = 3; 
  121.     tval.tv_usec = 0; 
  122.    //while(1) 
  123.    { 
  124.        //we must clear fds for every loop, otherwise can not check the change of descriptor 
  125.        FD_ZERO(&fds_red); 
  126.     FD_SET(sock_fd, &fds_red); 
  127.     syslog(LOG_ERR, "%s:%d, before select", __FILE__, __LINE__); 
  128.      
  129.        selret = select(sock_fd + 1, &fds_red, NULL, NULL, &tval); 
  130.         
  131.     syslog(LOG_ERR, "%s:%d, after select", __FILE__, __LINE__); 
  132.     if(selret < 0) 
  133.        { 
  134.             if(errno == EINTR) 
  135.          { 
  136.                 return 0; 
  137.          } 
  138.          else 
  139.          { 
  140.              syslog(LOG_ERR, "%s:%d, select failed", __FILE__, __LINE__); 
  141.                 return -1; 
  142.          } 
  143.        } 
  144.     else if(selret == 0) 
  145.     { 
  146.              syslog(LOG_ERR, "%s:%d, select timeout, no descriptors can be read or written", __FILE__, __LINE__); 
  147.           return 0; 
  148.     } 
  149.     else 
  150.        { 
  151.             syslog(LOG_ERR, "%s:%d, select default", __FILE__, __LINE__); 
  152.           
  153.              if(FD_ISSET(sock_fd, &fds_red)) 
  154.              { 
  155.                    syslog(LOG_ERR, "%s:%d, receive data", __FILE__, __LINE__); 
  156.                 bool brecvres = true; 
  157.                     //receive data 
  158.              int recvlen = 0; 
  159.              int retlen = 0; 
  160.              char *ptr = recvbuf; 
  161.              int leftlen = recvbuflen -1; 
  162.              do 
  163.              { 
  164.                  syslog(LOG_ERR, "%s:%d, before  recv", __FILE__, __LINE__); 
  165.                  retlen = recv(sock_fd, ptr, leftlen, 0) ; 
  166.               syslog(LOG_ERR, "%s:%d, after recv, and retlen is %d, errno is %d", __FILE__, __LINE__, retlen, errno); 
  167.               if(retlen < 0) 
  168.               { 
  169.                   if(errno == EAGAIN || errno == EWOULDBLOCK) 
  170.                     { 
  171.                     break; 
  172.                     } 
  173.                   else if(errno == EINTR ) 
  174.                   { 
  175.                     retlen = 0; 
  176.                   } 
  177.                   else 
  178.                   { 
  179.                       syslog(LOG_ERR, "%s:%d, recv data error is %d", __FILE__, __LINE__, errno); 
  180.                       return -1; 
  181.                   } 
  182.               } 
  183.               else if(retlen == 0) 
  184.               { 
  185.                   syslog(LOG_ERR, "%s:%d, socket is closed", __FILE__, __LINE__); 
  186.                   return -1; 
  187.               } 
  188.               recvlen += retlen; 
  189.               leftlen -= retlen; 
  190.               ptr += retlen; 
  191.              } 
  192.              while(leftlen); 
  193.               
  194.              syslog(LOG_ERR, "%s:%d, reveive data is %s", __FILE__, __LINE__, recvbuf); 
  195.              printf("receive data is : %s", recvbuf); 
  196.              return recvlen; 
  197.     
  198.              } 
  199.           else 
  200.           { 
  201.               return -1; 
  202.           } 
  203.        } 
  204.     } 
  205.  
  206. int Send(int sock_fd, char * sendbuf, int snebuflen) 
  207.      sprintf(sendbuf, "hello server/n"); 
  208.      //send data 
  209.      int sendlen = strlen(sendbuf) +1; 
  210.      int retlen = 0; 
  211.      int leftlen = sendlen; 
  212.      char *ptr = sendbuf; 
  213.      fd_set fds_write; 
  214.     struct timeval tval; 
  215.     int selret = 0; 
  216.     tval.tv_sec = 3; 
  217.     tval.tv_usec = 0; 
  218.     FD_ZERO(&fds_write); 
  219.     FD_SET(sock_fd, &fds_write); 
  220.        retlen = send(sock_fd, ptr, sendlen, 0); 
  221.     if(retlen < sendlen) 
  222.     { 
  223.         if(retlen < 0) 
  224.         { 
  225.             if(errno != EWOULDBLOCK && errno != ENOBUFS && errno != EAGAIN && errno != EINTR) 
  226.             return -1; 
  227.         else 
  228.             retlen = 0; 
  229.         } 
  230.         while(1) 
  231.         { 
  232.             FD_ZERO(&fds_write); 
  233.             FD_SET(sock_fd, &fds_write); 
  234.              
  235.         selret = select(sock_fd + 1, NULL, &fds_write, NULL, &tval); 
  236.         if(selret < 0) 
  237.            { 
  238.                 if(errno == EINTR) 
  239.              { 
  240.                     continue; 
  241.              } 
  242.              else 
  243.              { 
  244.                  syslog(LOG_ERR, "%s:%d, select failed", __FILE__, __LINE__); 
  245.                     return -1; 
  246.              } 
  247.            } 
  248.         else if(selret == 0) 
  249.         { 
  250.                  syslog(LOG_ERR, "%s:%d, select timeout, no descriptors can be read or written", __FILE__, __LINE__); 
  251.               continue; 
  252.         } 
  253.         else 
  254.         { 
  255.             if(FD_ISSET(sock_fd, &fds_write) ) 
  256.             { 
  257.                    leftlen -= retlen; 
  258.                 sendlen = leftlen; 
  259.                 ptr += retlen; 
  260.                      
  261.                 syslog(LOG_ERR, "%s:%d, send data", __FILE__, __LINE__); 
  262.                 do 
  263.                 { 
  264.                      retlen = send(sock_fd, ptr, sendlen, 0); 
  265.                     if(retlen < 0) 
  266.                     { 
  267.                         if(errno == EAGAIN || errno == EWOULDBLOCK) 
  268.                         break; 
  269.                         else  if(errno == EINTR) 
  270.                         retlen = 0; 
  271.                     else 
  272.                         syslog(LOG_ERR, "%s:%d, recv data error is %d", __FILE__, __LINE__, errno); 
  273.                     } 
  274.                     leftlen -= retlen; 
  275.                     sendlen = leftlen; 
  276.                     ptr += retlen; 
  277.                 }while(leftlen); 
  278.             } 
  279.             else 
  280.             { 
  281.                 return -1; 
  282.             } 
  283.         } 
  284.         } 
  285.     } 
  286.     return sendlen; 
  287. int main() 
  288.     int sock_fd = 0; 
  289.     char recvbuf[RECV_BUF_SIZE] = {0}; 
  290.     char sendbuf[SEND_BUF_SIZE] = {0}; 
  291.     int recvlen = 0; 
  292.     int retlen = 0; 
  293.     int sendlen = 0; 
  294.     int leftlen = 0; 
  295.     char *ptr = NULL; 
  296.     struct sockaddr_in ser_addr; 
  297.     int fdflags = 0; 
  298.     bool bIsconnected = false; 
  299.     int addrlen = sizeof(ser_addr); 
  300.      
  301.     memset(&ser_addr, 0, sizeof(ser_addr)); 
  302.     ser_addr.sin_family = AF_INET; 
  303.     inet_aton("127.0.0.1", (struct in_addr *)&ser_addr.sin_addr); 
  304.     ser_addr.sin_port = htons(SERVER_PORT); 
  305.     sock_fd = socket(AF_INET, SOCK_STREAM, 0); 
  306.     if(sock_fd < 0) 
  307.     { 
  308.         syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__); 
  309.      close(sock_fd); 
  310.         exit(1); 
  311.     } 
  312.     fdflags = fcntl(sock_fd, F_GETFL, 0); 
  313.     if(fcntl(sock_fd, F_SETFL, fdflags | O_NONBLOCK) < 0) 
  314.     { 
  315.         syslog(LOG_ERR, "%s:%d, fcntl set nonblock failed", __FILE__, __LINE__); 
  316.      close(sock_fd); 
  317.         exit(1); 
  318.     } 
  319.     if(Connect(sock_fd, (struct sockaddr *)&ser_addr, &addrlen) == false) 
  320.     { 
  321.      syslog(LOG_ERR, "%s:%d, fcntl set nonblock failed", __FILE__, __LINE__); 
  322.         close(sock_fd); 
  323.      exit(1); 
  324.     } 
  325.    while(1) 
  326.    { 
  327.        int recvlen = Recv(sock_fd, recvbuf, RECV_BUF_SIZE) ; 
  328.        if(recvlen < 0) 
  329.         break; 
  330.     else if( recvlen > 0) 
  331.     { 
  332.         int senlen = Send(sock_fd, sendbuf, RECV_BUF_SIZE); 
  333.         if(sendlen < 0) 
  334.         break; 
  335.     } 
  336.    } 
  337.      close(sock_fd); 
  338.      
  339. }  

 

 

在服務器端,要關注的一個東西是O_REUSEADDR,在程序里調用了(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flags, flaglen)對socket進行設置。

1.   可以對一個端口進行多次綁定,一般這個是不支持使用的;  2.   對於監聽套接字,比較特殊。如果你定義了SO_REUSEADDR,並且讓兩個套接字在同一個端口上進行接聽,那么對於由誰來ACCEPT,就會出現歧義。如果你定義個SO_REUSEADDR,只定義一個套接字在一個端口上進行監聽,如果服務器出現意外而導致沒有將這個端口釋放,那么服務器重新啟動后,你還可以用這個端口,因為你已經規定可以重用了,如果你沒定義的話,你就會得到提示,ADDR已在使用中。
  在多播的時候,也經常使用SO_REUSEADDR,也是為了防止機器出現意外,導致端口沒有釋放,而使重啟后的綁定失敗~。一般是用來防止服務器在發生意外時,端口未被釋放~可以重新使用~

 

關於errno值的定義在errno.h中

 

  1. #ifndef _I386_ERRNO_H 
  2. #define _I386_ERRNO_H 
  3. #define EPERM           1 /* Operation not permitted */ 
  4. #define ENOENT          2 /* No such file or directory */ 
  5. #define ESRCH           3 /* No such process */ 
  6. #define EINTR           4 /* Interrupted system call */ 
  7. #define EIO             5 /* I/O error */ 
  8. #define ENXIO           6 /* No such device or address */ 
  9. #define E2BIG           7 /* Arg list too long */ 
  10. #define ENOEXEC         8 /* Exec format error */ 
  11. #define EBADF           9 /* Bad file number */ 
  12. #define ECHILD          10 /* No child processes */ 
  13. #define EAGAIN          11 /* Try again */ 
  14. #define ENOMEM          12 /* Out of memory */ 
  15. #define EACCES          13 /* Permission denied */ 
  16. #define EFAULT          14 /* Bad address */ 
  17. #define ENOTBLK         15 /* Block device required */ 
  18. #define EBUSY           16 /* Device or resource busy */ 
  19. #define EEXIST          17 /* File exists */ 
  20. #define EXDEV           18 /* Cross-device link */ 
  21. #define ENODEV          19 /* No such device */ 
  22. #define ENOTDIR         20 /* Not a directory */ 
  23. #define EISDIR          21 /* Is a directory */ 
  24. #define EINVAL          22 /* Invalid argument */ 
  25. #define ENFILE          23 /* File table overflow */ 
  26. #define EMFILE          24 /* Too many open files */ 
  27. #define ENOTTY          25 /* Not a typewriter */ 
  28. #define ETXTBSY         26 /* Text file busy */ 
  29. #define EFBIG           27 /* File too large */ 
  30. #define ENOSPC          28 /* No space left on device */ 
  31. #define ESPIPE          29 /* Illegal seek */ 
  32. #define EROFS           30 /* Read-only file system */ 
  33. #define EMLINK          31 /* Too many links */ 
  34. #define EPIPE           32 /* Broken pipe */ 
  35. #define EDOM            33 /* Math argument out of domain of func */ 
  36. #define ERANGE          34 /* Math result not representable */ 
  37. #define EDEADLK         35      /* Resource deadlock would occur */ 
  38. #define ENAMETOOLONG    36      /* File name too long */ 
  39. #define ENOLCK          37      /* No record locks available */ 
  40. #define ENOSYS          38      /* Function not implemented */ 
  41. #define ENOTEMPTY       39      /* Directory not empty */ 
  42. #define ELOOP           40      /* Too many symbolic links encountered */ 
  43. #define EWOULDBLOCK     EAGAIN  /* Operation would block */ 
  44. #define ENOMSG          42      /* No message of desired type */ 
  45. #define EIDRM           43      /* Identifier removed */ 
  46. #define ECHRNG          44      /* Channel number out of range */ 
  47. #define EL2NSYNC        45      /* Level 2 not synchronized */ 
  48. #define EL3HLT          46      /* Level 3 halted */ 
  49. #define EL3RST          47      /* Level 3 reset */ 
  50. #define ELNRNG          48      /* Link number out of range */ 
  51. #define EUNATCH         49      /* Protocol driver not attached */ 
  52. #define ENOCSI          50      /* No CSI structure available */ 
  53. #define EL2HLT          51      /* Level 2 halted */ 
  54. #define EBADE           52      /* Invalid exchange */ 
  55. #define EBADR           53      /* Invalid request descriptor */ 
  56. #define EXFULL          54      /* Exchange full */ 
  57. #define ENOANO          55      /* No anode */ 
  58. #define EBADRQC         56      /* Invalid request code */ 
  59. #define EBADSLT         57      /* Invalid slot */ 
  60. #define EDEADLOCK       EDEADLK 
  61. #define EBFONT          59      /* Bad font file format */ 
  62. #define ENOSTR          60      /* Device not a stream */ 
  63. #define ENODATA         61      /* No data available */ 
  64. #define ETIME           62      /* Timer expired */ 
  65. #define ENOSR           63      /* Out of streams resources */ 
  66. #define ENONET          64      /* Machine is not on the network */ 
  67. #define ENOPKG          65      /* Package not installed */ 
  68. #define EREMOTE         66      /* Object is remote */ 
  69. #define ENOLINK         67      /* Link has been severed */ 
  70. #define EADV            68      /* Advertise error */ 
  71. #define ESRMNT          69      /* Srmount error */ 
  72. #define ECOMM           70      /* Communication error on send */ 
  73. #define EPROTO          71      /* Protocol error */ 
  74. #define EMULTIHOP       72      /* Multihop attempted */ 
  75. #define EDOTDOT         73      /* RFS specific error */ 
  76. #define EBADMSG         74      /* Not a data message */ 
  77. #define EOVERFLOW       75      /* Value too large for defined data type */ 
  78. #define ENOTUNIQ        76      /* Name not unique on network */ 
  79. #define EBADFD          77      /* File descriptor in bad state */ 
  80. #define EREMCHG         78      /* Remote address changed */ 
  81. #define ELIBACC         79      /* Can not access a needed shared library */ 
  82. #define ELIBBAD         80      /* Accessing a corrupted shared library */ 
  83. #define ELIBSCN         81      /* .lib section in a.out corrupted */ 
  84. #define ELIBMAX         82      /* Attempting to link in too many shared libraries */ 
  85. #define ELIBEXEC        83      /* Cannot exec a shared library directly */ 
  86. #define EILSEQ          84      /* Illegal byte sequence */ 
  87. #define ERESTART        85      /* Interrupted system call should be restarted */ 
  88. #define ESTRPIPE        86      /* Streams pipe error */ 
  89. #define EUSERS          87      /* Too many users */ 
  90. #define ENOTSOCK        88      /* Socket operation on non-socket */ 
  91. #define EDESTADDRREQ    89      /* Destination address required */ 
  92. #define EMSGSIZE        90      /* Message too long */ 
  93. #define EPROTOTYPE      91      /* Protocol wrong type for socket */ 
  94. #define ENOPROTOOPT     92      /* Protocol not available */ 
  95. #define EPROTONOSUPPORT 93      /* Protocol not supported */ 
  96. #define ESOCKTNOSUPPORT 94      /* Socket type not supported */ 
  97. #define EOPNOTSUPP      95      /* Operation not supported on transport endpoint */ 
  98. #define EPFNOSUPPORT    96      /* Protocol family not supported */ 
  99. #define EAFNOSUPPORT    97      /* Address family not supported by protocol */ 
  100. #define EADDRINUSE      98      /* Address already in use */ 
  101. #define EADDRNOTAVAIL   99      /* Cannot assign requested address */ 
  102. #define ENETDOWN        100     /* Network is down */ 
  103. #define ENETUNREACH     101     /* Network is unreachable */ 
  104. #define ENETRESET       102     /* Network dropped connection because of reset */ 
  105. #define ECONNABORTED    103     /* Software caused connection abort */ 
  106. #define ECONNRESET      104     /* Connection reset by peer */ 
  107. #define ENOBUFS         105     /* No buffer space available */ 
  108. #define EISCONN         106     /* Transport endpoint is already connected */ 
  109. #define ENOTCONN        107     /* Transport endpoint is not connected */ 
  110. #define ESHUTDOWN       108     /* Cannot send after transport endpoint shutdown */ 
  111. #define ETOOMANYREFS    109     /* Too many references: cannot splice */ 
  112. #define ETIMEDOUT       110     /* Connection timed out */ 
  113. #define ECONNREFUSED    111     /* Connection refused */ 
  114. #define EHOSTDOWN       112     /* Host is down */ 
  115. #define EHOSTUNREACH    113     /* No route to host */ 
  116. #define EALREADY        114     /* Operation already in progress */ 
  117. #define EINPROGRESS     115     /* Operation now in progress */ 
  118. #define ESTALE          116     /* Stale NFS file handle */ 
  119. #define EUCLEAN         117     /* Structure needs cleaning */ 
  120. #define ENOTNAM         118     /* Not a XENIX named type file */ 
  121. #define ENAVAIL         119     /* No XENIX semaphores available */ 
  122. #define EISNAM          120     /* Is a named type file */ 
  123. #define EREMOTEIO       121     /* Remote I/O error */ 
  124. #define EDQUOT          122     /* Quota exceeded */ 
  125. #define ENOMEDIUM       123     /* No medium found */ 
  126. #define EMEDIUMTYPE     124     /* Wrong medium type */ 
  127. #define ECANCELED       125     /* Operation Canceled */ 
  128. #define ENOKEY          126     /* Required key not available */ 
  129. #define EKEYEXPIRED     127     /* Key has expired */ 
  130. #define EKEYREVOKED     128     /* Key has been revoked */ 
  131. #define EKEYREJECTED    129     /* Key was rejected by service */ 
  132. /* for robust mutexes */ 
  133. #define EOWNERDEAD      130     /* Owner died */ 
  134. #define ENOTRECOVERABLE 131     /* State not recoverable */ 
  135. #endif 

 

 

接下來我們關注client.c

1. 把socket設置為非阻塞模式

    fdflags = fcntl(sock_fd, F_GETFL, 0);     if(fcntl(sock_fd, F_SETFL, fdflags | O_NONBLOCK) < 0)     {         syslog(LOG_ERR, "%s:%d, fcntl set nonblock failed", __FILE__, __LINE__);         close(sock_fd);         exit(1);     }

當然ioctl也可以,這個函數更為強大,這里不做詳細說明。

 

2. 對於connect的處理

首先我們看一下非阻塞模式的I/O模型

對於一個系統調用來說,如果不能馬上完成會返回-1(一般都是-1,具體的函數可以看詳細說明),並設置errno,不同的系統會不一樣,一般是EWOULDBLOCK, EAGAIN等。如果系統調用被中斷,則返回EINTR錯誤。

 

那么對於connect來說,如果是返回值 <0,那么就需要對errno進行判斷和處理,這里有幾種情況

1)errno == EISCONN,說明這個socket已經連接上了

 

2)(errno == EINPROGRESS || errno == EALREADY || errno == EWOULDBLOCK), 表明connect正在進行但沒有完成,因為connect需要花費一點時間,而socket又被設置成了非阻塞,所以這些錯誤時正常的。但如果不是這些錯誤(errno != EINPROGRESS && errno != EALREADY && errno != EWOULDBLOCK),那么connect就出錯了。

 

3)接下來就是用select對connect進行等待

對於conncet來說,如果是阻塞的,那么它會一直等到連接成功或失敗,這個時間一般是75秒到幾分鍾之間,這個時間對於我們的程序來說太長了,所以我們用selcet。

int select(int maxfdp1,fd_set *readset, fd_set *writeset,fd_set *exceptset, const struct timeval *timeout);

函數返回值Returns: positive count of ready descriptors, 0 on timeout, –1 on error。其中的參數

maxfdp1表示我們關注的所有套接字的最大值+1, 如果這個值是5,那么select只關注0~4的描述符,這樣可以減少范圍提高效率。

readset, writeset 和exceptset是selcet關注的可讀,可寫和異常錯誤的描述符集合

timeout是超時時間,如果設為NULL則永遠不超時,直到有感興趣的描述符在I/O上准備好;如果設為0則馬上返回;如果是其他值,則如果在這個時間段里還沒有感興趣的描述符在I/O上准備好則返回,且返回值為0

這里還要說明的一點是每次select之后,都會把readset, writeset 和exceptset除了准備好I/O的描述符清掉,所以如果循環select的話每次都要重新設置描述符集合。

 

對於select如果返回值<0,並且errno == EINTR,說明系統調用被中斷;返回值 ==0,說明超時,這兩種情況都繼續select。如果返回值 >0,說明有描述符的I/O准備好了,進行處理,在這里我們要看sock_fd是否可讀或可寫。connect連接成功則可寫,如果在select之前連接成功並收到數據則又可讀。但是connect異常也會出現可讀(socket 對應的連接讀關閉(也就是說對該socket 不能再讀了。比如,該socket 收到 FIN ))或可寫(socket 對應的連接寫關閉(也就是說對該socket不能再寫。比如,該socket 收到 RST))的情況。我們可以通過

getsockopt來區分正常情況和異常情況。

  1. int error = 0; 
  2. int len = sizeof(error); 
  3. int rc = getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, (void *) &error, &len); 
  4. if(rc == -1) 
  5.     syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__); 
  6.     return false; 
  7. else if(error) 
  8.     syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__); 
  9.     return false; 

 

除了getsockopt,也可以用一下方法區分異常和正常情況,但不同的系統不一樣,一般unix上是可以的,但linux是否可以沒有嘗試過。

  (1).調用getpeername獲取對端的socket地址.如果getpeername返回ENOTCONN,表示連接建立失敗,然后用SO_ERROR調用getsockopt得到套接口描述符上的待處理錯誤;   (2).調用read,讀取長度為0字節的數據.如果read調用失敗,則表示連接建立失敗,而且read返回的errno指明了連接失敗的原因.如果連接建立成功,read應該返回0;   (3).再調用一次connect.它應該失敗,如果錯誤errno是EISCONN,就表示套接口已經建立,而且第一次連接是成功的;否則,連接就是失敗的;

 

有的時候connect會馬上成功,特別是當服務器和客戶端都在同一台機器上的話,那么這種情況也是需要處理的,就不需要select了,在我們的代碼里面是直接return了。

 

connect總結:

TCP socket 被設為非阻塞后調用 connect ,connect 函數如果沒有馬上成功,會立即返回 EINPROCESS(如果被中斷返回EINTR) ,但 TCP 的 3 次握手繼續進行。之后可以用 select 檢查連接是否建立成功(但不能再次調用connect,這樣會返回錯誤EADDRINUSE)。非阻塞 connect 有3 種用途: (1). 在3 次握手的同時做一些其他的處理。 (2). 可以同時建立多個連接。 (3). 在利用 select 等待的時候,可以給 select 設定一個時間,從而可以縮短 connect 的超時時間。
使用非阻塞 connect 需要注意的問題是: (1). 很可能 調用 connect 時會立即建立連接(比如,客戶端和服務端在同一台機子上),必須處理這種情況。 (2). Posix 定義了兩條與 select 和 非阻塞 connect 相關的規定:      連接成功建立時,socket 描述字變為可寫。(連接建立時,寫緩沖區空閑,所以可寫)      連接建立失敗時,socket 描述字既可讀又可寫。 (由於有未決的錯誤,從而可讀又可寫)

 另外對於無連接的socket類型(SOCK_DGRAM),客戶端也可以調用connect進行連接,此連接實際上並不建立類似SOCK_STREAM的連接,而僅僅是在本地保存了對端的地址,這樣后續的讀寫操作可以默認以連接的對端為操作對象。

 

3. recv 和 send數據

這里的處理方式也是用select,並對其中的一些錯誤進行處理,和connect大同小異,不做詳細的說明。這里有一個問題是,既然用了select,只有在有數據可讀的時候才會調用recv,那么函數也就不會阻塞在那里,還有必要把它設置為非阻塞嗎。這個問題我也沒有想明白,有人這么解釋:select 只能說明 socket 可讀或者可寫,不能說明能讀入或者能寫出多少數據。比如,socket 的寫緩沖區有 10 個字節的空閑空間,這時監視的 select 返回,然后在該 socket 上進行寫操作。但是如果要寫入 100 字節,如果 socket 沒有設置非阻塞,調用 write 就會阻塞在那里。

 

 

4. accept

我們雖然沒有把服務器的socket設置為非阻塞模式,但我們可以說一下非阻塞的accept。

 

在select模式下,listening socket設置為非阻塞的原因是什么??

當用 select 監視 listening socket 時, 如果有新連接到來,select 返回, 該 listening socket 變為可讀。然后我們 accept 接收該連接。

首先說明一下 已完成3次握手的連接在 accept 之前 被 異常終止(Aborted )時發生的情況,如下圖:

一個連接被異常終止時執行的動作取決於實現: (1). 基於 Berkeley 的實現完全由內核處理該異常終止的連接, 應用進程看不到。 (2). 基於 SVR4 的實現,在連接異常終止后調用 accept 時,通常會給應用進程返回 EPROTO 錯誤。但是 Posix 指出應該返回 ECONNABORTED 。Posix 認為當發生致命的協議相關的錯誤時,返回 EPROTO 錯誤。而 異常終止一個連接並非致命錯誤,從而返回 ECONNABORTED ,與 EPROTO 區分開來,這樣隨后可以繼續調用 accept 。

 

現在假設是基於 Berkeley 的實現,在 select 返回后,accept 調用之前,如果連接被異常終止,這時 accept 調用可能會由於沒有已完成的連接而阻塞,直到有新連接建立。對於服務進程而言,在被 accept 阻塞的這一段時間內,將不能處理其他已就緒的 socket 。
解決上面這個問題有兩種方法: (1). 在用 select 監視 listening socket 時,總是將 listening socket 設為非阻塞模式。 (2). 忽略 accept 返回的以下錯誤:     EWOULDBLOCK(基於 berkeley 實現,當客戶端異常終止連接時)、ECONNABORTED(基於 posix 實現,當客戶端異常終止連接時)、EPROTO(基於 SVR4 實現,當客戶端異常終止連接時)以及 EINTR 。

 

5. 異常情況處理

  當對端機器crash或者網絡連接被斷開(比如路由器不工作,網線斷開等),此時發送數據給對端然后讀取本端socket會返回ETIMEDOUT或者EHOSTUNREACH 或者ENETUNREACH(后兩個是中間路由器判斷服務器主機不可達的情況)。
  當對端機器crash之后又重新啟動,然后客戶端再向原來的連接發送數據,因為服務器端已經沒有原來的連接信息,此時服務器端回送RST給客戶端,此時客戶端讀本地端口返回ECONNRESET錯誤。
  當服務器所在的進程正常或者異常關閉時,會對所有打開的文件描述符進行close,因此對於連接的socket描述符則會向對端發送FIN分節進行正常關閉流程。對端在收到FIN之后端口變得可讀,此時讀取端口會返回0表示到了文件結尾(對端不會再發送數據)。 
  當一端收到RST導致讀取socket返回ECONNRESET,此時如果再次調用write發送數據給對端則觸發SIGPIPE信號,信號默認終止進程,如果忽略此信號或者從SIGPIPE的信號處理程序返回則write出錯返回EPIPE。
  可以看出只有當本地端口主動發送消息給對端才能檢測出連接異常中斷的情況,搭配select進行多路分離的時候,socket收到RST或者FIN時候,select返回可讀(心跳消息就是用於檢測連接的狀態)。也可以使用socket的KEEPLIVE選項,依賴socket本身偵測socket連接異常中斷的情況。

 

6. 描述符的I/O什么時候准備好

這個問題在unix network programing中有詳細說明

We have been talking about waiting for a descriptor to become ready for I/O (reading or writing) or to have an exception condition pending on it (out-of-band data). While readability and writability are obvious for descriptors such as regular files, we must be more specific about the conditions that cause select to return "ready" for sockets (Figure 16.52 of TCPv2).
   1. A socket is ready for reading if any of the following four conditions is true:          a. The number of bytes of data in the socket receive buffer is greater than or equal to the current size of the low-water mark for the socket receive buffer. A read operation on the socket will not block and will return a value greater than 0 (i.e., the data that is ready to be read). We can set this low-water mark using the SO_RCVLOWAT socket option. It defaults to 1 for TCP and UDP sockets.(也就是說如果讀緩沖區有大於等於設定的最低刻度線時可讀,一般最低刻度線是1,也就是說只要有數據就可讀,我們也可以通過設置改變這個值)          b. The read half of the connection is closed (i.e., a TCP connection that has received a FIN). A read operation on the socket will not block and will return 0 (i.e., EOF).          c. The socket is a listening socket and the number of completed connections is nonzero. An accept on the listening socket will normally not block, although we will describe a timing condition in Section 16.6 under which the accept can block.          d. A socket error is pending. A read operation on the socket will not block and will return an error (–1) with errno set to the specific error condition. These pending errors can also be fetched and cleared by calling getsockopt and specifying the SO_ERROR socket option.

   2. A socket is ready for writing if any of the following four conditions is true:          a. The number of bytes of available space in the socket send buffer is greater than or equal to the current size of the low-water mark for the socket send buffer and either: (i) the socket is connected, or (ii) the socket does not require a connection (e.g., UDP). This means that if we set the socket to nonblocking (Chapter 16), a write operation will not block and will return a positive value (e.g., the number of bytes accepted by the transport layer). We can set this low-water mark using the SO_SNDLOWAT socket option. This low-water mark normally defaults to 2048 for TCP and UDP sockets.          b. The write half of the connection is closed. A write operation on the socket will generate SIGPIPE (Section 5.12).          c. A socket using a non-blocking connect has completed the connection, or the connect has failed.          d. A socket error is pending. A write operation on the socket will not block and will return an error (–1) with errno set to the specific error condition. These pending errors can also be fetched and cleared by calling getsockopt with the SO_ERROR socket option.

   3. A socket has an exception condition pending if there is out-of-band data for the socket or the socket is still at the out-of-band mark. (We will describe out-of-band data in Chapter 24.)
          Our definitions of "readable" and "writable" are taken directly from the kernel's soreadable and sowriteable macros on pp. 530–531 of TCPv2. Similarly, our definition of the "exception condition" for a socket is from the soo_select function on these same pages. Notice that when an error occurs on a socket, it is marked as both readable and writable by select.

 

用更形象的圖表來表示為

 

 

 

 

 

參考

http://hi.baidu.com/motadou/blog/item/02d506ef941421232df534fc.html

http://www.cnitblog.com/zouzheng/archive/2010/11/25/71711.html

《unix network programing volume 1》

http://blog.csdn.net/wind19/article/details/6157122#


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM