在上一篇文章 《socket編程——一個簡單的例子》 http://blog.csdn.net/wind19/archive/2011/01/21/6156339.aspx 中寫了一個簡單的tcp socket通信程序,可以進行數據的交互,但有一個問題是這個程序是阻塞的,任何socket函數都要等返回后才能進行下一步動作,如果recv一直沒有數據,那么就一直不會返回,整個進程就阻塞在那。所以我們要進行改造一下,讓程序不再阻塞在那,而是在有數據到來的時候讀一下數據,有數據要寫的時候發送一下數據。
設置阻塞模式的函數一般由兩個fcntl 和 ioctl
先放源程序,服務器端還是阻塞的,客服端改成非阻塞的,只是作為一個例子
- /*server.c*/
- #include <stdio.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <stdlib.h>
- #include <syslog.h>
- #include <errno.h>
- #define MAX_LISTEN_NUM 5
- #define SEND_BUF_SIZE 100
- #define RECV_BUF_SIZE 100
- #define LISTEN_PORT 1010
- int main()
- {
- int listen_sock = 0;
- int app_sock = 0;
- struct sockaddr_in hostaddr;
- struct sockaddr_in clientaddr;
- int socklen = sizeof(clientaddr);
- char sendbuf[SEND_BUF_SIZE] = {0};
- char recvbuf[RECV_BUF_SIZE] = {0};
- int sendlen = 0;
- int recvlen = 0;
- int retlen = 0;
- int leftlen = 0;
- char *ptr = NULL;
- int flags = 1;
- int flaglen = sizeof(flags);
- memset((void *)&hostaddr, 0, sizeof(hostaddr));
- memset((void *)&clientaddr, 0, sizeof(clientaddr));
- hostaddr.sin_family = AF_INET;
- hostaddr.sin_port = htons(LISTEN_PORT);
- hostaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- listen_sock = socket(AF_INET, SOCK_STREAM, 0);
- if(listen_sock < 0)
- {
- syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__);
- exit(1);
- }
- if(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flags, flaglen) < 0)
- {
- syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__);
- exit(1);
- }
- if(bind(listen_sock, (struct sockaddr *)&hostaddr, sizeof(hostaddr)) < 0)
- {
- syslog(LOG_ERR, "%s:%d, bind socket failed", __FILE__, __LINE__);
- exit(1);
- }
- if(listen(listen_sock, MAX_LISTEN_NUM) < 0)
- {
- syslog(LOG_ERR, "%s:%d, listen failed", __FILE__, __LINE__);
- exit(1);
- }
- while(1)
- {
- app_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &socklen);
- if(app_sock < 0)
- {
- syslog(LOG_ERR, "%s:%d, accept failed", __FILE__, __LINE__);
- exit(1);
- }
- sprintf(sendbuf, "welcome %s:%d here!/n", inet_ntoa(clientaddr.sin_addr.s_addr), clientaddr.sin_port);
- //send data
- sendlen = strlen(sendbuf) +1;
- retlen = 0;
- leftlen = sendlen;
- ptr = sendbuf;
- //while(leftlen)
- {
- syslog(LOG_ERR, "%s:%d, before send", __FILE__, __LINE__);
- retlen = send(app_sock, ptr, sendlen, 0);
- if(retlen < 0)
- {
- if(errno == EINTR)
- retlen = 0;
- else
- exit(1);
- }
- leftlen -= retlen;
- ptr += retlen;
- syslog(LOG_ERR, "%s:%d, after send, retlen = %d", __FILE__, __LINE__, retlen);
- }
- //receive data
- recvlen = 0;
- retlen = 0;
- ptr = recvbuf;
- leftlen = RECV_BUF_SIZE -1;
- //do
- {
- retlen = recv(app_sock, ptr, leftlen, 0) ;
- if(retlen < 0)
- {
- if(errno == EINTR)
- retlen = 0;
- else
- exit(1);
- }
- recvlen += retlen;
- leftlen -= retlen;
- ptr += retlen;
- }
- //while(recvlen && leftlen);
- printf("receive data is : %s", recvbuf);
- close(app_sock);
- }
- close(listen_sock);
- return 0;
- }
- /*clent.c*/
- #include <stdio.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <syslog.h>
- #include <errno.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <stdbool.h>
- #include <sys/select.h>
- #include <sys/times.h>
- #define MAX_LISTEN_NUM 5
- #define SEND_BUF_SIZE 100
- #define RECV_BUF_SIZE 100
- #define SERVER_PORT 1010
- #define MAX_CONNECT_TIMES 5
- bool Connect(int sock_fd, struct sockaddr* pser_addr, int* paddrlen)
- {
- if(connect(sock_fd, pser_addr, *paddrlen) < 0)
- {
- if(errno == EISCONN)
- {
- syslog(LOG_ERR, "%s:%d, connect socket completed", __FILE__, __LINE__);
- return true;
- }
- if(errno != EINPROGRESS && errno != EALREADY && errno != EWOULDBLOCK)
- {
- syslog(LOG_ERR, "%s:%d, connect socket failed", __FILE__, __LINE__);
- return false;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, connect socket does not completed", __FILE__, __LINE__);
- }
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, connect socket completed", __FILE__, __LINE__);
- return true;
- }
- fd_set fds_red, fds_write;
- struct timeval tval;
- int selret = 0;
- tval.tv_sec = 3;
- tval.tv_usec = 0;
- int ntrytimes = 0;
- while(1 && ntrytimes < MAX_CONNECT_TIMES)
- {
- FD_ZERO(&fds_red);
- FD_SET(sock_fd, &fds_red);
- FD_ZERO(&fds_write);
- FD_SET(sock_fd, &fds_write);
- syslog(LOG_ERR, "%s:%d, before select", __FILE__, __LINE__);
- selret = select(sock_fd + 1, &fds_red, &fds_write, NULL, &tval);
- syslog(LOG_ERR, "%s:%d, after select", __FILE__, __LINE__);
- if(selret < 0)
- {
- if(errno == EINTR)
- {
- ntrytimes++;
- continue;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, select failed", __FILE__, __LINE__);
- return false;
- }
- }
- else if(selret == 0)
- {
- syslog(LOG_ERR, "%s:%d, connect socket timeout", __FILE__, __LINE__);
- ntrytimes++;
- continue;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, select default", __FILE__, __LINE__);
- if(FD_ISSET(sock_fd, &fds_red) || FD_ISSET(sock_fd, &fds_write))
- {
- int error = 0;
- int len = sizeof(error);
- int rc = getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, (void *) &error, &len);
- if(rc == -1)
- {
- syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__);
- return false;
- }
- else if(error)
- {
- syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__);
- return false;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, connection is ok", __FILE__, __LINE__);
- return true;
- }
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, no descriptor is ready", __FILE__, __LINE__);
- continue;
- }
- }
- }
- return false;
- }
- //return value, -1 means Recv happs error; 0 means timeout or be interupted; > 0 means ok
- int Recv(int sock_fd, char * recvbuf, int recvbuflen)
- {
- fd_set fds_red;
- struct timeval tval;
- int selret = 0;
- tval.tv_sec = 3;
- tval.tv_usec = 0;
- //while(1)
- {
- //we must clear fds for every loop, otherwise can not check the change of descriptor
- FD_ZERO(&fds_red);
- FD_SET(sock_fd, &fds_red);
- syslog(LOG_ERR, "%s:%d, before select", __FILE__, __LINE__);
- selret = select(sock_fd + 1, &fds_red, NULL, NULL, &tval);
- syslog(LOG_ERR, "%s:%d, after select", __FILE__, __LINE__);
- if(selret < 0)
- {
- if(errno == EINTR)
- {
- return 0;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, select failed", __FILE__, __LINE__);
- return -1;
- }
- }
- else if(selret == 0)
- {
- syslog(LOG_ERR, "%s:%d, select timeout, no descriptors can be read or written", __FILE__, __LINE__);
- return 0;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, select default", __FILE__, __LINE__);
- if(FD_ISSET(sock_fd, &fds_red))
- {
- syslog(LOG_ERR, "%s:%d, receive data", __FILE__, __LINE__);
- bool brecvres = true;
- //receive data
- int recvlen = 0;
- int retlen = 0;
- char *ptr = recvbuf;
- int leftlen = recvbuflen -1;
- do
- {
- syslog(LOG_ERR, "%s:%d, before recv", __FILE__, __LINE__);
- retlen = recv(sock_fd, ptr, leftlen, 0) ;
- syslog(LOG_ERR, "%s:%d, after recv, and retlen is %d, errno is %d", __FILE__, __LINE__, retlen, errno);
- if(retlen < 0)
- {
- if(errno == EAGAIN || errno == EWOULDBLOCK)
- {
- break;
- }
- else if(errno == EINTR )
- {
- retlen = 0;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, recv data error is %d", __FILE__, __LINE__, errno);
- return -1;
- }
- }
- else if(retlen == 0)
- {
- syslog(LOG_ERR, "%s:%d, socket is closed", __FILE__, __LINE__);
- return -1;
- }
- recvlen += retlen;
- leftlen -= retlen;
- ptr += retlen;
- }
- while(leftlen);
- syslog(LOG_ERR, "%s:%d, reveive data is %s", __FILE__, __LINE__, recvbuf);
- printf("receive data is : %s", recvbuf);
- return recvlen;
- }
- else
- {
- return -1;
- }
- }
- }
- }
- int Send(int sock_fd, char * sendbuf, int snebuflen)
- {
- sprintf(sendbuf, "hello server/n");
- //send data
- int sendlen = strlen(sendbuf) +1;
- int retlen = 0;
- int leftlen = sendlen;
- char *ptr = sendbuf;
- fd_set fds_write;
- struct timeval tval;
- int selret = 0;
- tval.tv_sec = 3;
- tval.tv_usec = 0;
- FD_ZERO(&fds_write);
- FD_SET(sock_fd, &fds_write);
- retlen = send(sock_fd, ptr, sendlen, 0);
- if(retlen < sendlen)
- {
- if(retlen < 0)
- {
- if(errno != EWOULDBLOCK && errno != ENOBUFS && errno != EAGAIN && errno != EINTR)
- return -1;
- else
- retlen = 0;
- }
- while(1)
- {
- FD_ZERO(&fds_write);
- FD_SET(sock_fd, &fds_write);
- selret = select(sock_fd + 1, NULL, &fds_write, NULL, &tval);
- if(selret < 0)
- {
- if(errno == EINTR)
- {
- continue;
- }
- else
- {
- syslog(LOG_ERR, "%s:%d, select failed", __FILE__, __LINE__);
- return -1;
- }
- }
- else if(selret == 0)
- {
- syslog(LOG_ERR, "%s:%d, select timeout, no descriptors can be read or written", __FILE__, __LINE__);
- continue;
- }
- else
- {
- if(FD_ISSET(sock_fd, &fds_write) )
- {
- leftlen -= retlen;
- sendlen = leftlen;
- ptr += retlen;
- syslog(LOG_ERR, "%s:%d, send data", __FILE__, __LINE__);
- do
- {
- retlen = send(sock_fd, ptr, sendlen, 0);
- if(retlen < 0)
- {
- if(errno == EAGAIN || errno == EWOULDBLOCK)
- break;
- else if(errno == EINTR)
- retlen = 0;
- else
- syslog(LOG_ERR, "%s:%d, recv data error is %d", __FILE__, __LINE__, errno);
- }
- leftlen -= retlen;
- sendlen = leftlen;
- ptr += retlen;
- }while(leftlen);
- }
- else
- {
- return -1;
- }
- }
- }
- }
- return sendlen;
- }
- int main()
- {
- int sock_fd = 0;
- char recvbuf[RECV_BUF_SIZE] = {0};
- char sendbuf[SEND_BUF_SIZE] = {0};
- int recvlen = 0;
- int retlen = 0;
- int sendlen = 0;
- int leftlen = 0;
- char *ptr = NULL;
- struct sockaddr_in ser_addr;
- int fdflags = 0;
- bool bIsconnected = false;
- int addrlen = sizeof(ser_addr);
- memset(&ser_addr, 0, sizeof(ser_addr));
- ser_addr.sin_family = AF_INET;
- inet_aton("127.0.0.1", (struct in_addr *)&ser_addr.sin_addr);
- ser_addr.sin_port = htons(SERVER_PORT);
- sock_fd = socket(AF_INET, SOCK_STREAM, 0);
- if(sock_fd < 0)
- {
- syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__);
- close(sock_fd);
- exit(1);
- }
- 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);
- }
- if(Connect(sock_fd, (struct sockaddr *)&ser_addr, &addrlen) == false)
- {
- syslog(LOG_ERR, "%s:%d, fcntl set nonblock failed", __FILE__, __LINE__);
- close(sock_fd);
- exit(1);
- }
- while(1)
- {
- int recvlen = Recv(sock_fd, recvbuf, RECV_BUF_SIZE) ;
- if(recvlen < 0)
- break;
- else if( recvlen > 0)
- {
- int senlen = Send(sock_fd, sendbuf, RECV_BUF_SIZE);
- if(sendlen < 0)
- break;
- }
- }
- close(sock_fd);
- }
在服務器端,要關注的一個東西是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中
- #ifndef _I386_ERRNO_H
- #define _I386_ERRNO_H
- #define EPERM 1 /* Operation not permitted */
- #define ENOENT 2 /* No such file or directory */
- #define ESRCH 3 /* No such process */
- #define EINTR 4 /* Interrupted system call */
- #define EIO 5 /* I/O error */
- #define ENXIO 6 /* No such device or address */
- #define E2BIG 7 /* Arg list too long */
- #define ENOEXEC 8 /* Exec format error */
- #define EBADF 9 /* Bad file number */
- #define ECHILD 10 /* No child processes */
- #define EAGAIN 11 /* Try again */
- #define ENOMEM 12 /* Out of memory */
- #define EACCES 13 /* Permission denied */
- #define EFAULT 14 /* Bad address */
- #define ENOTBLK 15 /* Block device required */
- #define EBUSY 16 /* Device or resource busy */
- #define EEXIST 17 /* File exists */
- #define EXDEV 18 /* Cross-device link */
- #define ENODEV 19 /* No such device */
- #define ENOTDIR 20 /* Not a directory */
- #define EISDIR 21 /* Is a directory */
- #define EINVAL 22 /* Invalid argument */
- #define ENFILE 23 /* File table overflow */
- #define EMFILE 24 /* Too many open files */
- #define ENOTTY 25 /* Not a typewriter */
- #define ETXTBSY 26 /* Text file busy */
- #define EFBIG 27 /* File too large */
- #define ENOSPC 28 /* No space left on device */
- #define ESPIPE 29 /* Illegal seek */
- #define EROFS 30 /* Read-only file system */
- #define EMLINK 31 /* Too many links */
- #define EPIPE 32 /* Broken pipe */
- #define EDOM 33 /* Math argument out of domain of func */
- #define ERANGE 34 /* Math result not representable */
- #define EDEADLK 35 /* Resource deadlock would occur */
- #define ENAMETOOLONG 36 /* File name too long */
- #define ENOLCK 37 /* No record locks available */
- #define ENOSYS 38 /* Function not implemented */
- #define ENOTEMPTY 39 /* Directory not empty */
- #define ELOOP 40 /* Too many symbolic links encountered */
- #define EWOULDBLOCK EAGAIN /* Operation would block */
- #define ENOMSG 42 /* No message of desired type */
- #define EIDRM 43 /* Identifier removed */
- #define ECHRNG 44 /* Channel number out of range */
- #define EL2NSYNC 45 /* Level 2 not synchronized */
- #define EL3HLT 46 /* Level 3 halted */
- #define EL3RST 47 /* Level 3 reset */
- #define ELNRNG 48 /* Link number out of range */
- #define EUNATCH 49 /* Protocol driver not attached */
- #define ENOCSI 50 /* No CSI structure available */
- #define EL2HLT 51 /* Level 2 halted */
- #define EBADE 52 /* Invalid exchange */
- #define EBADR 53 /* Invalid request descriptor */
- #define EXFULL 54 /* Exchange full */
- #define ENOANO 55 /* No anode */
- #define EBADRQC 56 /* Invalid request code */
- #define EBADSLT 57 /* Invalid slot */
- #define EDEADLOCK EDEADLK
- #define EBFONT 59 /* Bad font file format */
- #define ENOSTR 60 /* Device not a stream */
- #define ENODATA 61 /* No data available */
- #define ETIME 62 /* Timer expired */
- #define ENOSR 63 /* Out of streams resources */
- #define ENONET 64 /* Machine is not on the network */
- #define ENOPKG 65 /* Package not installed */
- #define EREMOTE 66 /* Object is remote */
- #define ENOLINK 67 /* Link has been severed */
- #define EADV 68 /* Advertise error */
- #define ESRMNT 69 /* Srmount error */
- #define ECOMM 70 /* Communication error on send */
- #define EPROTO 71 /* Protocol error */
- #define EMULTIHOP 72 /* Multihop attempted */
- #define EDOTDOT 73 /* RFS specific error */
- #define EBADMSG 74 /* Not a data message */
- #define EOVERFLOW 75 /* Value too large for defined data type */
- #define ENOTUNIQ 76 /* Name not unique on network */
- #define EBADFD 77 /* File descriptor in bad state */
- #define EREMCHG 78 /* Remote address changed */
- #define ELIBACC 79 /* Can not access a needed shared library */
- #define ELIBBAD 80 /* Accessing a corrupted shared library */
- #define ELIBSCN 81 /* .lib section in a.out corrupted */
- #define ELIBMAX 82 /* Attempting to link in too many shared libraries */
- #define ELIBEXEC 83 /* Cannot exec a shared library directly */
- #define EILSEQ 84 /* Illegal byte sequence */
- #define ERESTART 85 /* Interrupted system call should be restarted */
- #define ESTRPIPE 86 /* Streams pipe error */
- #define EUSERS 87 /* Too many users */
- #define ENOTSOCK 88 /* Socket operation on non-socket */
- #define EDESTADDRREQ 89 /* Destination address required */
- #define EMSGSIZE 90 /* Message too long */
- #define EPROTOTYPE 91 /* Protocol wrong type for socket */
- #define ENOPROTOOPT 92 /* Protocol not available */
- #define EPROTONOSUPPORT 93 /* Protocol not supported */
- #define ESOCKTNOSUPPORT 94 /* Socket type not supported */
- #define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
- #define EPFNOSUPPORT 96 /* Protocol family not supported */
- #define EAFNOSUPPORT 97 /* Address family not supported by protocol */
- #define EADDRINUSE 98 /* Address already in use */
- #define EADDRNOTAVAIL 99 /* Cannot assign requested address */
- #define ENETDOWN 100 /* Network is down */
- #define ENETUNREACH 101 /* Network is unreachable */
- #define ENETRESET 102 /* Network dropped connection because of reset */
- #define ECONNABORTED 103 /* Software caused connection abort */
- #define ECONNRESET 104 /* Connection reset by peer */
- #define ENOBUFS 105 /* No buffer space available */
- #define EISCONN 106 /* Transport endpoint is already connected */
- #define ENOTCONN 107 /* Transport endpoint is not connected */
- #define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
- #define ETOOMANYREFS 109 /* Too many references: cannot splice */
- #define ETIMEDOUT 110 /* Connection timed out */
- #define ECONNREFUSED 111 /* Connection refused */
- #define EHOSTDOWN 112 /* Host is down */
- #define EHOSTUNREACH 113 /* No route to host */
- #define EALREADY 114 /* Operation already in progress */
- #define EINPROGRESS 115 /* Operation now in progress */
- #define ESTALE 116 /* Stale NFS file handle */
- #define EUCLEAN 117 /* Structure needs cleaning */
- #define ENOTNAM 118 /* Not a XENIX named type file */
- #define ENAVAIL 119 /* No XENIX semaphores available */
- #define EISNAM 120 /* Is a named type file */
- #define EREMOTEIO 121 /* Remote I/O error */
- #define EDQUOT 122 /* Quota exceeded */
- #define ENOMEDIUM 123 /* No medium found */
- #define EMEDIUMTYPE 124 /* Wrong medium type */
- #define ECANCELED 125 /* Operation Canceled */
- #define ENOKEY 126 /* Required key not available */
- #define EKEYEXPIRED 127 /* Key has expired */
- #define EKEYREVOKED 128 /* Key has been revoked */
- #define EKEYREJECTED 129 /* Key was rejected by service */
- /* for robust mutexes */
- #define EOWNERDEAD 130 /* Owner died */
- #define ENOTRECOVERABLE 131 /* State not recoverable */
- #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來區分正常情況和異常情況。
- int error = 0;
- int len = sizeof(error);
- int rc = getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, (void *) &error, &len);
- if(rc == -1)
- {
- syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__);
- return false;
- }
- else if(error)
- {
- syslog(LOG_ERR, "%s:%d, connection is closed", __FILE__, __LINE__);
- 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#