https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorialhtml
根據這一篇寫一個最簡單的demo。而後開始寫client。git
client最初的代碼以下:github
1 #include <sys/socket.h> 2 #include <sys/un.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <unistd.h> 6 #include <sys/socket.h> 7 #include <fcntl.h> 8 #include <errno.h> 9 10 int main(int argc, char *argv[]) { 11 struct sockaddr_un addr; 12 int fd,rc; 13 14 if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 15 perror("socket error"); 16 exit(-1); 17 } 18 19 const char *socket_path = "/tmp/mysocket"; 20 memset(&addr, 0, sizeof(addr)); 21 addr.sun_family = AF_UNIX; 22 strcpy(addr.sun_path, socket_path); 23 24 if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { 25 perror("connect error"); 26 exit(-1); 27 } 28 29 char sendbuf[8145] = {0}; 30 rc = 8145; 31 32 { 33 if (write(fd, sendbuf, rc) != rc) { 34 if (rc > 0) fprintf(stderr,"partial write"); 35 else { 36 perror("write error"); 37 exit(-1); 38 } 39 } 40 } 41 42 43 char buf[1024] = {0}; 44 45 while ((rc = read(fd, buf, 1024)) > 0) { 46 buf[rc] = '\0'; 47 printf("%s\n", buf); 48 } 49 50 close(fd); 51 52 return 0; 53 }
代碼很簡單,會發現有個問題,read這裏會阻塞住不退出。網絡
由於這是阻塞IO,讀不到數據時會阻塞。有沒辦法能夠知道服務端已經寫完了呢?若是用非阻塞的是否是有不同的返回碼呢。又試了下非阻塞版。socket
1 int val = fcntl(fd, F_GETFL, 0); 2 fcntl(fd, F_SETFL, val|O_NONBLOCK);// 設置爲非阻塞 3 4 //... 5 6 char buf[1024] = {0}; 7 while (true) { 8 rc = read(fd, buf, 1024); 9 if (rc > 0) { 10 buf[rc] = '\0'; 11 printf("recv:%s\n", buf); 12 } else if (rc == 0) { 13 break; 14 } else if (rc < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) { 15 //printf("errno %d\n", errno); 16 continue; 17 } else { 18 break; 19 } 20 }
這時就會出現一直跑到第15行這裏,errno一直是EWOULDBLOCK/EAGAIN。函數
非阻塞模式下返回值 <0時而且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的狀況下認爲鏈接是正常的, 繼續發送。spa
https://blog.csdn.net/qq_14821541/article/details/52028924.net
好吧,問題一樣沒有解決。實際上網絡通訊server端可能會出現不少狀況,寫得慢、網絡慢或者server掛了等,爲了魯棒性,一個比較通用的策略就是超時。若是超了時間就直接退出。code
1 struct timeval tv; 2 tv.tv_sec = 3; 3 tv.tv_usec = 0; 4 setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
用阻塞+超時,這樣就能夠正常退出了。不過仍是沒解決正常狀況下的退出。server
一個簡單的思路就是服務端寫完了數據,在數據的最終加上一個mark,標識已經寫完了,client讀到這個mark,就直接退出。
1 #include <sys/socket.h> 2 #include <sys/un.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <unistd.h> 6 #include <sys/socket.h> 7 #include <fcntl.h> 8 #include <errno.h> 9 10 int main(int argc, char *argv[]) { 11 struct sockaddr_un addr; 12 int fd,rc; 13 14 if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 15 perror("socket error"); 16 exit(-1); 17 } 18 19 struct timeval tv; 20 tv.tv_sec = 3; 21 tv.tv_usec = 0; 22 setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); 23 24 const char *socket_path = "/tmp/mysocket"; 25 memset(&addr, 0, sizeof(addr)); 26 addr.sun_family = AF_UNIX; 27 strcpy(addr.sun_path, socket_path); 28 29 if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { 30 perror("connect error"); 31 exit(-1); 32 } 33 34 const char *end_mark = "$@%^&~"; 35 int end_mark_len = strlen(end_mark); 36 37 char sendbuf[8145] = {0}; 38 rc = 8145; 39 memcpy(sendbuf + rc - end_mark_len, end_mark, end_mark_len); 40 41 printf("%s %d\n", sendbuf, rc); 42 43 { 44 if (write(fd, sendbuf, rc) != rc) { 45 if (rc > 0) fprintf(stderr,"partial write"); 46 else { 47 perror("write error"); 48 exit(-1); 49 } 50 } 51 } 52 53 54 char buf[1024] = {0}; 55 while ((rc = read(fd, buf, 1024)) > 0) { 56 buf[rc] = '\0'; 57 if (rc < end_mark_len) break; 58 if (strncmp(buf + rc - end_mark_len, end_mark, end_mark_len) == 0) { 59 printf("%s\n", buf); 60 break; 61 } 62 } 63 64 close(fd); 65 66 return 0; 67 }
https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial
前面咱們用bufferevent_setcb來設置回調函數,libevent的回調觸發時機是這樣的:
參考:http://senlinzhan.github.io/2017/08/20/libevent-buffer/
http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html
這裏提到的demo,會有幾個問題:
第一個問題,是由於鏈接創建,若某一端關閉鏈接,而另外一端仍然向它寫數據,第一次寫數據後會收到RST響應,此後再寫數據,內核將向進程發出SIGPIPE信號,通知進程此鏈接已經斷開。而SIGPIPE信號的默認處理是終止程序。解決方案就是直接忽略SIGPIPE信號。
1 signal(SIGPIPE, SIG_IGN);
第二個問題,一樣用一個mark來標記讀取結束。這裏用到evbuffer_peek來獲取整個buffer內存而不是copy出來再查。
http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html
1 bool CheckReadFinished(struct evbuffer *input) { 2 const int limit_vec = 10; 3 4 struct evbuffer_iovec v[limit_vec]; 5 int n = evbuffer_peek(input, -1, NULL, v, limit_vec); 6 if (n <= 0) { 7 return false; 8 } 9 10 int end_mark_len = strlen(end_mark); 11 for (unsigned i = n - 1; i >= 0; --i) { 12 size_t len = v[i].iov_len; 13 if (len >= end_mark_len) { 14 return strncmp((char*)(v[i].iov_base) + (len - end_mark_len), end_mark, end_mark_len) == 0; 15 } else { 16 if (strncmp((char*)(v[i].iov_base), end_mark + (end_mark_len - len), len) != 0) { 17 return false; 18 } 19 end_mark_len -= len; 20 } 21 } 22 return false; 23 }
這裏直接用了limit_vec來限制大小,若是超出buff大小就認爲是錯誤的。
1 static void echo_read_cb(struct bufferevent *bev, void *ctx) { 2 struct evbuffer *input = bufferevent_get_input(bev); 3 struct evbuffer *output = bufferevent_get_output(bev); 4 5 if (CheckReadFinished(input)) { 6 size_t len = evbuffer_get_length(input); 7 printf("we got some data: %d\n", len); 8 evbuffer_add_printf(output, end_mark); 9 } 10 }
第三個問題,client異常退出是避免不了的,因此要有容錯機制,一樣是採用超時來容錯。
1 static void 2 accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) { 3 evutil_make_socket_nonblocking(fd); 4 5 struct event_base *base = evconnlistener_get_base(listener); 6 struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); 7 bufferevent_setcb(bev, echo_read_cb, echo_write_cb, echo_event_cb, NULL); 8 9 // 設置超時,而後斷開連接 10 struct timeval read_tv = {2, 0}, write_tv = {3, 0}; 11 bufferevent_set_timeouts(bev, &read_tv, &write_tv); 12 13 bufferevent_enable(bev, EV_READ | EV_WRITE); 14 }
而後在BEV_EVENT_TIMEOUT事件觸發時free掉evbuff。由於咱們指定了BEV_OPT_CLOSE_ON_FREE,因此這時候就會斷掉鏈接。
1 static void echo_event_cb(struct bufferevent *bev, short events, void *ctx) { 2 if (events & BEV_EVENT_ERROR) 3 perror("Error from bufferevent"); 4 if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { 5 printf("free event\n"); 6 bufferevent_free(bev); 7 } 8 }
這裏咱們也能夠看到,正常狀況下,當client讀取結束以後會close(fd),這時就會觸發BEV_EVENT_EOF事件,一樣是會關掉服務端的鏈接。