Article From:https://www.cnblogs.com/linyx/p/9966237.html

https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial

Write a simple demo based on this article. Then start writing client.

clienttuning

clientThe original code is as follows:

 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 }            

The code is very simple, and you’ll find a problem. Read will block you from quitting.

Because this is blocking IO, it will block if it can’t read the data. Is there any way to know that the server has finished writing? If you use non-blocking, are there different return codes? We tried the non-blocking version again.

 1 int val = fcntl(fd, F_GETFL, 0);
 2 fcntl(fd, F_SETFL, val|O_NONBLOCK);// Set to non blocking 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     }

Then there will be a run up to line 15, where errno has always been EWOULDBLOCK/EAGAIN.

Return value in non-blocking mode <0And (errno= EINTR | | errno= EWOULDBLOCK | | errno= EAGAIN) the case that the connection is normal, continue to send.

https://blog.csdn.net/qq_14821541/article/details/52028924

Well, the problem is also unsolved. In fact, there may be many situations on the server side of network communication, such as slow writing, slow network or server hanging. For robustness, a more general strategy is timeout. If it’s too late, just quit.

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));

With blocking + timeout, you can exit normally. However, it still failed to solve the normal withdrawal.

A simple idea is that the server writes the data, and at the end of the data, add a mark, the logo has been written, the client reads the mark, and then exits directly.

 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 }

servertuning

https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial

We used bufferevent_setcb to set the callback function. The timing of libevent’s callback trigger is as follows:

  1. When the input buffer data is greater than or equal to the input low water level, the read callback is invoked. By default, the input low water level value is 0, that is to say, as long as the socket becomes readable, the read callback is invoked.
  2. Write callback is called when the output buffer data is less than or equal to the output low water level. By default, the value of the output low water level is 0, that is to say, write callbacks are invoked only when the data in the output buffer has been sent out. Therefore, write callbacks by default can also be understood asWrite complete callback.
  3. Event callbacks are invoked when a connection is established, closed, timeout or error occurs.

Reference: http://senlinzhan.github.io/2017/08/20/libevent-buffer/

http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html

The demo mentioned here has several problems:

  1. clientWhen the server continues to write data in advance, it receives SIGPIPE signal and then exits directly.
  2. echo_read_cbReadback callback, if read data is large, may trigger many times, but we need to process the data at the end of the same time, here also need to determine whether the data has been read to end;
  3. If client exits early, the link will not close even if SIGPIPE signal is ignored.

 The first problem is because the connection is established. If one end closes the connection and the other end still writes data to it, the RST response will be received after the first write, and then the data will be written again. The kernel will send a SIGPIPE signal to the process to inform the process that the connection has been disconnected. And the silence of SIGPIPE signalRecognition processing is termination procedure. The solution is to ignore SIGPIPE signals directly.

1 signal(SIGPIPE, SIG_IGN);

The second problem is to mark the end of the reading with the same mark. Evbuffer_peek is used here to get the entire buffer memory instead of copying it out for review.

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 is used directly to limit size, which is considered wrong if it exceeds buff size.

 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 }

The third problem is that client abnormal exit is unavoidable, so there must be a fault-tolerant mechanism, the same is the use of timeouts to fault-tolerant.

 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     // Set timeout, then disconnect
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 }

Then evbuff is freely dropped when the BEV_EVENT_TIMEOUT event triggers. Because we specify BEV_OPT_CLOSE_ON_FREE, the connection will be disconnected at this time.

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 }

Here we can also see that normally, when the client reads out, it will close (fd), then trigger the BEV_EVENT_EOF event, which will also turn off the connection of the server.

 

Leave a Reply

Your email address will not be published. Required fields are marked *