我們知道,linux下socket編程有常見的幾個系統調用:
對於服務器來說, 有socket(), bind(),listen(), accept(),read(),write()
對於客戶端來說,有socket(),connect()
這里主要要講的是客戶端這邊的connect函數。
對於客戶端來說,需要打開一個套接字,然后與對端服務器連接,例如:
1 int main(int argc, char **argv) 2 { 3 struct sockaddr_in s_addr; 4 memset(&s_addr, 0, sizeof(s_addr)); 5 s_addr.sin_family = AF_INET; 6 s_addr.sin_addr.s_addr = inet_addr("remote host"); 7 s_addr.sin_port = htons(remote port); 8 socklen_t addr_len = sizeof(struct sockaddr); 9 int c_fd = socket(AF_INET, SOCK_STREAM, 0); 10 int ret = connect(c_fd, (struct sockaddr*)&s_addr, addr_len); 11 ...... 12 }
當connect上對端服務器之后,就可以使用該套接字發送數據了。
我們知道,如果socket為TCP套接字, 則connect函數會激發TCP的三次握手過程,而三次握手是需要一些時間的,內核中對connect的超時限制是75秒,就是說如果超過75秒則connect會由於超時而返回失敗。但是如果對端服務器由於某些問題無法連接,那么每一個客戶端發起的connect都會要等待75才會返回,因為socket默認是阻塞的。對於一些線上服務來說,假設某些對端服務器出問題了,在這種情況下就有可能引發嚴重的后果。或者在有些時候,我們不希望在調用connect的時候阻塞住,有一些額外的任務需要處理;
這種場景下,我們就可以將socket設置為非阻塞,如下代碼:
int flags = fcntl(c_fd, F_GETFL, 0); if(flags < 0) { return 0; } fcntl(c_fd, F_SETFL, flags | O_NONBLOCK);
當我們將socket設置為NONBLOCK后,在調用connect的時候,如果操作不能馬上完成,那connect便會立即返回,此時connect有可能返回-1, 此時需要根據相應的錯誤碼errno,來判斷連接是否在繼續進行。
當errno=EINPROGRESS時,這種情況是正常的,此時連接在繼續進行,但是仍未完成;同時TCP的三路握手操作繼續進行;后續只要用select/epoll去注冊對應的事件並設置超時時間來判斷連接否是連接成功就可以了。
int ret = connect(c_fd, (struct sockaddr*)&s_addr, addr_len); while(ret < 0) { if( errno == EINPROGRESS ) { break; } else { perror("connect fail'\n"); return 0; } }
這個地方,我們很可能會判斷如果ret小於0,就直接判斷連接失敗而返回了,沒有根據errno去判斷EINPROGRESS這個錯誤碼。這里也是昨天在寫份程序的時候遇到的一個坑。
使用非阻塞 connect 需要注意的問題是:
1. 很可能 調用 connect 時會立即建立連接(比如,客戶端和服務端在同一台機子上),必須處理這種情況。
2. Posix 定義了兩條與 select 和 非阻塞 connect 相關的規定:
1)連接成功建立時,socket 描述字變為可寫。(連接建立時,寫緩沖區空閑,所以可寫)
2)連接建立失敗時,socket 描述字既可讀又可寫。 (由於有未決的錯誤,從而可讀又可寫)
不過我同時用epoll也做了實驗(connect一個無效端口,errno=110, errmsg=connect refused),當連接失敗的時候,會觸發epoll的EPOLLERR與EPOLLIN,不會觸發EPOLLOUT。
當用select檢測連接時,socket既可讀又可寫,只能在可讀的集合通過getsockopt獲取錯誤碼。
當用epoll檢測連接時,socket既可讀又可寫,只能在EPOLLERR中通過getsockopt獲取錯誤碼。
完整代碼如下:
/* * File: main.cpp * Created on March 7, 2013, 5:54 PM */ #include <cstdlib> #include <string> #include <iostream> #include <sys/epoll.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/select.h> #include <error.h> #include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <arpa/inet.h> using namespace std; struct so { int fd; string val; }; int select_version(int *fd) { int c_fd = *fd; fd_set rset, wset; struct timeval tval; FD_ZERO(&rset); FD_SET(c_fd, &rset); wset = rset; tval.tv_sec = 0; tval.tv_usec = 300 * 1000; //300毫秒 int ready_n; if ((ready_n = select(c_fd + 1, &rset, &wset, NULL, &tval)) == 0) { close(c_fd); /* timeout */ errno = ETIMEDOUT; perror("select timeout.\n"); return (-1); } if (FD_ISSET(c_fd, &rset)) { int error; socklen_t len = sizeof (error); if (getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { cout << "getsockopt error." << endl; return -1; } cout << "in fire." << error << endl; } if (FD_ISSET(c_fd, &wset)) { int error; socklen_t len = sizeof (error); if (getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { cout << "getsockopt error." << endl; return -1; } cout << "out fire." << error << endl; } return 0; } int epoll_version(int *fd) { int c_fd = *fd; int ep = epoll_create(1024); struct epoll_event event; event.events = (uint32_t) (EPOLLIN | EPOLLOUT | EPOLLET); struct so _data; _data.fd = c_fd; _data.val = "test"; event.data.ptr = (void*) &_data; epoll_ctl(ep, EPOLL_CTL_ADD, c_fd, &event); struct epoll_event eventArr[1000]; int status, err; socklen_t len; err = 0; len = sizeof (err); int n = epoll_wait(ep, eventArr, 20, 300); for (int i = 0; i < n; i++) { epoll_event ev = eventArr[i]; int events = ev.events; if (events & EPOLLERR) { struct so* so_data = (struct so*) ev.data.ptr; cout << so_data->val << ",err event fire." << endl; status = getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &err, &len); cout << status << "," << err << endl; } if (events & EPOLLIN) { struct so* so_data = (struct so*) ev.data.ptr; cout << so_data->val << ",in event fire." << endl; status = getsockopt(c_fd, SOL_SOCKET, SO_ERROR, &err, &len); cout << status << "," << err << endl; } if (events & EPOLLOUT) { struct so* so_data1 = (struct so*) ev.data.ptr; cout << so_data1->val << ",out event fire." << endl; } } } int main(int argc, char** argv) { string ip = "127.0.0.1"; int port = 25698; int c_fd, flags, ret; struct sockaddr_in s_addr; memset(&s_addr, 0, sizeof (s_addr)); s_addr.sin_family = AF_INET; s_addr.sin_port = htons(port); s_addr.sin_addr.s_addr = inet_addr(ip.c_str()); if ((c_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("create socket fail.\n"); exit(0); } flags = fcntl(c_fd, F_GETFL, 0); if (flags < 0) { perror("get socket flags fail.\n"); return -1; } if (fcntl(c_fd, F_SETFL, flags | O_NONBLOCK) < 0) { perror("set socket O_NONBLOCK fail.\n"); return -1; } ret = connect(c_fd, (struct sockaddr*) &s_addr, sizeof (struct sockaddr)); while (ret < 0) { if (errno == EINPROGRESS) { break; } else { perror("connect remote server fail.\n"); printf("%d\n", errno); exit(0); } } //select_version(&c_fd); epoll_version(&c_fd); return 0; }
ps:文中如有不妥的地方,望各位博友指正。