Linux 高并发服务器

Posted xuejiale

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 高并发服务器相关的知识,希望对你有一定的参考价值。

高并发服务器

技术图片

一、多进程并发服务器

    1. 实现示意图

技术图片

    2. 使用多进程并发服务器时要考虑以下几点:

  • 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
  • 系统内创建进程个数(与内存大小相关)
  • 进程创建过多是否降低整体服务性能(进程调度)

    3. 使用多进程的方式, 解决服务器处理多连接的问题:

    (1)共享

  • 读时共享, 写时复制
  • 文件描述符
  • 内存映射区 -- mmap

    (2)父进程 的角色是什么?

     等待接受客户端连接 -- accept

     有链接:

  • 创建一个子进程 fork()
  • 将通信的文件描述符关闭

    (3)子进程的角色是什么?

      1)通信

  • 使用accept返回值 - fd

      2)关掉监听的文件描述符

  • 浪费资源

    (4)创建的进程的个数有限制吗?

  • 受硬件限制
  • 文件描述符默认也是有上限的1024

    (5)子进程资源回收

      1)wait/waitpid

     2)使用信号回收

  • 信号捕捉

           signal

           sigaction - 推荐

  • 捕捉信号: SIGCHLD

    代码实现:

技术图片
  1 #include <stdlib.h>
  2 #include <stdio.h>
  3 #include <unistd.h>
  4 #include <errno.h>
  5 #include <sys/socket.h>
  6 
  7 void perr_exit(const char *s)
  8 {
  9     perror(s);
 10     exit(-1);
 11 }
 12 
 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
 14 {
 15     int n;
 16 
 17 again:
 18     if ((n = accept(fd, sa, salenptr)) < 0) {
 19         if ((errno == ECONNABORTED) || (errno == EINTR))
 20             goto again;
 21         else
 22             perr_exit("accept error");
 23     }
 24     return n;
 25 }
 26 
 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
 28 {
 29     int n;
 30 
 31     if ((n = bind(fd, sa, salen)) < 0)
 32         perr_exit("bind error");
 33 
 34     return n;
 35 }
 36 
 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
 38 {
 39     int n;
 40 
 41     if ((n = connect(fd, sa, salen)) < 0)
 42         perr_exit("connect error");
 43 
 44     return n;
 45 }
 46 
 47 int Listen(int fd, int backlog)
 48 {
 49     int n;
 50 
 51     if ((n = listen(fd, backlog)) < 0)
 52         perr_exit("listen error");
 53 
 54     return n;
 55 }
 56 
 57 int Socket(int family, int type, int protocol)
 58 {
 59     int n;
 60 
 61     if ((n = socket(family, type, protocol)) < 0)
 62         perr_exit("socket error");
 63 
 64     return n;
 65 }
 66 
 67 ssize_t Read(int fd, void *ptr, size_t nbytes)
 68 {
 69     ssize_t n;
 70 
 71 again:
 72     if ( (n = read(fd, ptr, nbytes)) == -1) {
 73         if (errno == EINTR)
 74             goto again;
 75         else
 76             return -1;
 77     }
 78     return n;
 79 }
 80 
 81 ssize_t Write(int fd, const void *ptr, size_t nbytes)
 82 {
 83     ssize_t n;
 84 
 85 again:
 86     if ( (n = write(fd, ptr, nbytes)) == -1) {
 87         if (errno == EINTR)
 88             goto again;
 89         else
 90             return -1;
 91     }
 92     return n;
 93 }
 94 
 95 int Close(int fd)
 96 {
 97     int n;
 98     if ((n = close(fd)) == -1)
 99         perr_exit("close error");
100 
101     return n;
102 }
103 
104 /*参三: 应该读取的字节数*/
105 ssize_t Readn(int fd, void *vptr, size_t n)
106 {
107     size_t  nleft;              //usigned int 剩余未读取的字节数
108     ssize_t nread;              //int 实际读到的字节数
109     char   *ptr;
110 
111     ptr = vptr;
112     nleft = n;
113 
114     while (nleft > 0) {
115         if ((nread = read(fd, ptr, nleft)) < 0) {
116             if (errno == EINTR)
117                 nread = 0;
118             else
119                 return -1;
120         } else if (nread == 0)
121             break;
122 
123         nleft -= nread;
124         ptr += nread;
125     }
126     return n - nleft;
127 }
128 
129 ssize_t Writen(int fd, const void *vptr, size_t n)
130 {
131     size_t nleft;
132     ssize_t nwritten;
133     const char *ptr;
134 
135     ptr = vptr;
136     nleft = n;
137     while (nleft > 0) {
138         if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
139             if (nwritten < 0 && errno == EINTR)
140                 nwritten = 0;
141             else
142                 return -1;
143         }
144 
145         nleft -= nwritten;
146         ptr += nwritten;
147     }
148     return n;
149 }
150 
151 static ssize_t my_read(int fd, char *ptr)
152 {
153     static int read_cnt;
154     static char *read_ptr;
155     static char read_buf[100];
156 
157     if (read_cnt <= 0) {
158 again:
159         if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
160             if (errno == EINTR)
161                 goto again;
162             return -1;
163         } else if (read_cnt == 0)
164             return 0;
165         read_ptr = read_buf;
166     }
167     read_cnt--;
168     *ptr = *read_ptr++;
169 
170     return 1;
171 }
172 
173 ssize_t Readline(int fd, void *vptr, size_t maxlen)
174 {
175     ssize_t n, rc;
176     char    c, *ptr;
177 
178     ptr = vptr;
179     for (n = 1; n < maxlen; n++) {
180         if ( (rc = my_read(fd, &c)) == 1) {
181             *ptr++ = c;
182             if (c  == \\n)
183                 break;
184         } else if (rc == 0) {
185             *ptr = 0;
186             return n - 1;
187         } else
188             return -1;
189     }
190     *ptr  = 0;
191 
192     return n;
193 }
wrap.c
技术图片
 1 #ifndef __WRAP_H_
 2 #define __WRAP_H_
 3 
 4 void perr_exit(const char *s);
 5 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
 6 int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
 7 int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
 8 int Listen(int fd, int backlog);
 9 int Socket(int family, int type, int protocol);
10 ssize_t Read(int fd, void *ptr, size_t nbytes);
11 ssize_t Write(int fd, const void *ptr, size_t nbytes);
12 int Close(int fd);
13 ssize_t Readn(int fd, void *vptr, size_t n);
14 ssize_t Writen(int fd, const void *vptr, size_t n);
15 ssize_t my_read(int fd, char *ptr);
16 ssize_t Readline(int fd, void *vptr, size_t maxlen);
17 
18 #endif
wrap.h
技术图片
  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <netinet/in.h>
  4 #include <arpa/inet.h>
  5 #include <signal.h>
  6 #include <sys/wait.h>
  7 #include <ctype.h>
  8 #include <unistd.h>
  9 
 10 #include "wrap.h"
 11 
 12 #define MAXLINE 8192
 13 #define SERV_PORT 8000
 14 
 15 void do_sigchild(int num)
 16 {
 17     while (waitpid(0, NULL, WNOHANG) > 0);
 18 }
 19 
 20 int main(void)
 21 {
 22     struct sockaddr_in servaddr, cliaddr;
 23     socklen_t cliaddr_len;
 24     int listenfd, connfd;
 25     char buf[MAXLINE];
 26     char str[INET_ADDRSTRLEN];
 27     int i, n;
 28     pid_t pid;
 29 
 30     //临时屏蔽sigchld信号
 31     sigset_t myset;
 32     sigemptyset(&myset);
 33     sigaddset(&myset, SIGCHLD);
 34     // 自定义信号集 -》 内核阻塞信号集
 35     sigprocmask(SIG_BLOCK, &myset, NULL);
 36 
 37 
 38     listenfd = Socket(AF_INET, SOCK_STREAM, 0);
 39 
 40     int opt = 1;
 41     // 设置端口复用
 42     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 43 
 44     bzero(&servaddr, sizeof(servaddr));
 45     servaddr.sin_family = AF_INET;
 46     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 47     servaddr.sin_port = htons(SERV_PORT);
 48 
 49     Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
 50 
 51     Listen(listenfd, 20);
 52 
 53     printf("Accepting connections ...\\n");
 54     while (1) 
 55     {
 56         cliaddr_len = sizeof(cliaddr);
 57         connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
 58 
 59         // 有新的连接则创建一个进程
 60         pid = fork();
 61         if (pid == 0) 
 62         {
 63             Close(listenfd);
 64             while (1) 
 65             {
 66                 n = Read(connfd, buf, MAXLINE);
 67                 if (n == 0) 
 68                 {
 69                     printf("the other side has been closed.\\n");
 70                     break;
 71                 }
 72                 printf("received from %s at PORT %d\\n",
 73                         inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
 74                         ntohs(cliaddr.sin_port));
 75 
 76                 for (i = 0; i < n; i++)
 77                     buf[i] = toupper(buf[i]);
 78 
 79                 Write(STDOUT_FILENO, buf, n);
 80                 Write(connfd, buf, n);
 81             }
 82             Close(connfd);
 83             return 0;
 84         } 
 85         else if (pid > 0) 
 86         {
 87             struct sigaction act;
 88             act.sa_flags = 0;
 89             act.sa_handler = do_sigchild;
 90             sigemptyset(&act.sa_mask);
 91             sigaction(SIGCHLD, &act, NULL);
 92             // 解除对sigchld信号的屏蔽
 93             sigprocmask(SIG_UNBLOCK, &myset, NULL);
 94 
 95             Close(connfd);
 96         }  
 97         else
 98         {
 99             perr_exit("fork");
100         }
101     }
102     return 0;
103 }
server.c
技术图片
 1 /* client.c */
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <netinet/in.h>
 6 #include <arpa/inet.h>
 7 
 8 #include "wrap.h"
 9 
10 #define MAXLINE 8192
11 #define SERV_PORT 8000
12 
13 int main(int argc, char *argv[])
14 {
15     struct sockaddr_in servaddr;
16     char buf[MAXLINE];
17     int sockfd, n;
18 
19     sockfd = Socket(AF_INET, SOCK_STREAM, 0);
20 
21     bzero(&servaddr, sizeof(servaddr));
22     servaddr.sin_family = AF_INET;
23     inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
24     servaddr.sin_port = htons(SERV_PORT);
25 
26     Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
27 
28     while (fgets(buf, MAXLINE, stdin) != NULL) 
29     {
30         Write(sockfd, buf, strlen(buf));
31         n = Read(sockfd, buf, MAXLINE);
32         if (n == 0) 
33         {
34             printf("the other side has been closed.\\n");
35             break;
36         }
37         else
38             Write(STDOUT_FILENO, buf, n);
39     }
40 
41     Close(sockfd);
42 
43     return 0;
44 }
client.c
技术图片
 1 src = $(wildcard *.c)
 2 obj = $(patsubst %.c, %.o, $(src))
 3 
 4 all: server client
 5 
 6 server: server.o wrap.o
 7     gcc server.o wrap.o -o server -Wall
 8 client: client.o wrap.o
 9     gcc client.o wrap.o -o client -Wall
10 
11 %.o:%.c
12     gcc -c $< -Wall
13 
14 .PHONY: clean all
15 clean: 
16     -rm -rf server client $(obj)
makefile

二、多线程并发服务器

    1. 实现示意图

技术图片

    2. 使用线程模型开发服务器时需考虑以下问题:

  • 调整进程内最大文件描述符上限
  • 线程如有共享数据,考虑线程同步
  • 服务于客户端线程退出时,退出处理。(退出值,分离态)
  • 系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU

    3. 线程共享:

  • 全局数据区
  • 堆区
  • 一块有效内存的地址

    代码实现:

技术图片
  1 #include <stdlib.h>
  2 #include <stdio.h>
  3 #include <unistd.h>
  4 #include <errno.h>
  5 #include <sys/socket.h>
  6 
  7 void perr_exit(const char *s)
  8 {
  9     perror(s);
 10     exit(-1);
 11 }
 12 
 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
 14 {
 15     int n;
 16 
 17 again:
 18     if ((n = accept(fd, sa, salenptr)) < 0) {
 19         if ((errno == ECONNABORTED) || (errno == EINTR))
 20             goto again;
 21         else
 22             perr_exit("accept error");
 23     }
 24     return n;
 25 }
 26 
 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
 28 {
 29     int n;
 30 
 31     if ((n = bind(fd, sa, salen)) < 0)
 32         perr_exit("bind error");
 33 
 34     return n;
 35 }
 36 
 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
 38 {
 39     int n;
 40 
 41     if ((n = connect(fd, sa, salen)) < 0)
 42         perr_exit("connect error");
 43 
 44     return n;
 45 }
 46 
 47 int Listen(int fd, int backlog)
 48 {
 49     int n;
 50 
 51     if ((n = listen(fd, backlog)) < 0)
 52         perr_exit("listen error");
 53 
 54     return n;
 55 }
 56 
 57 int Socket(int family, int type, int protocol)
 58 {
 59     int n;
 60 
 61     if ((n = socket(family, type, protocol)) < 0)
 62         perr_exit("socket error");
 63 
 64     return n;
 65 }
 66 
 67 ssize_t Read(int fd, void *ptr, size_t nbytes)
 68 {
 69     ssize_t n;
 70 
 71 again:
 72     if ( (n = read(fd, ptr, nbytes)) == -1) {
 73         if (errno == EINTR)
 74             goto again;
 75         else
 76             return -1;
 77     }
 78     return n;
 79 }
 80 
 81 ssize_t Write(int fd, const void *ptr, size_t nbytes)
 82 {
 83     ssize_t n;
 84 
 85 again:
 86     if ( (n = write(fd, ptr, nbytes)) == -1) {
 87         if (errno == EINTR)
 88             goto again;
 89         else
 90             return -1;
 91     }
 92     return n;
 93 }
 94 
 95 int Close(int fd)
 96 {
 97     int n;
 98     if ((n = close(fd)) == -1)
 99         perr_exit("close error");
100 
101     return n;
102 }
103 
104 /*参三: 应该读取的字节数*/
105 ssize_t Readn(int fd, void *vptr, size_t n)
106 {
107     size_t  nleft;              //usigned int 剩余未读取的字节数
108     ssize_t nread;              //int 实际读到的字节数
109     char   *ptr;
110 
111     ptr = vptr;
112     nleft = n;
113 
114     while (nleft > 0) {
115         if ((nread = read(fd, ptr, nleft)) < 0) {
116             if (errno == EINTR)
117                 nread = 0;
118             else
119                 return -1;
120         } else if (nread == 0)
121             break;
122 
123         nleft -= nread;
124         ptr += nread;
125     }
126     return n - nleft;
127 }
128 
129 ssize_t Writen(int fd, const void *vptr, size_t n)
130 {
131     size_t nleft;
132     ssize_t nwritten;
133     const char *ptr;
134 
135     ptr = vptr;
136     nleft = n;
137     while (nleft > 0) {
138         if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
139             if (nwritten < 0 && errno == EINTR)
140                 nwritten = 0;
141             else
142                 return -1;
143         }
144 
145         nleft -= nwritten;
146         ptr += nwritten;
147     }
148     return n;
149 }
150 
151 static ssize_t my_read(int fd, char *ptr)
152 {
153     static int read_cnt;
154     static char *read_ptr;
155     static char read_buf[100];
156 
157     if (read_cnt <= 0) {
158 again:
159         if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
160             if (errno == EINTR)
161                 goto again;
162             return -1;
163         } else if (read_cnt == 0)
164             return 0;
165         read_ptr = read_buf;
166     }
167     read_cnt--;
168     *ptr = *read_ptr++;
169 
170     return 1;
171 }
172 
173 ssize_t Readline(int fd, void *vptr, size_t maxlen)
174 {
175     ssize_t n, rc;
176     char    c, *ptr;
177 
178     ptr = vptr;
179     for (n = 1; n < maxlen; n++) {
180         if ( (rc = my_read(fd, &c)) == 1) {
181             *ptr++ = c;
182             if (c  == \\n)
183                 break;
184         } else if (rc == 0) {
185             *ptr = 0;
186             return n - 1;
187         } else
188             return -1;
189     }
190     *ptr  = 0;
191 
192     return n;
193 }
wrap.c
技术图片
 1 #ifndef __WRAP_H_
 2 #define __WRAP_H_
 3 
 4 void perr_exit(const char *s);
 5 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
 6 int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
 7 int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
 8 int Listen(int fd, int backlog);
 9 int Socket(int family, int type, int protocol);
10 ssize_t Read(int fd, void *ptr, size_t nbytes);
11 ssize_t Write(int fd, const void *ptr, size_t nbytes);
12 int Close(int fd);
13 ssize_t Readn(int fd, void *vptr, size_t n);
14 ssize_t Writen(int fd, const void *vptr, size_t n);
15 ssize_t my_read(int fd, char *ptr);
16 ssize_t Readline(int fd, void *vptr, size_t maxlen);
17 
18 #endif
wrap.h
技术图片
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <arpa/inet.h>
 4 #include <pthread.h>
 5 #include <ctype.h>
 6 #include <unistd.h>
 7 #include <fcntl.h>
 8 
 9 #include "wrap.h"
10 
11 #define MAXLINE 8192
12 #define SERV_PORT 8000
13 
14 struct s_info 
15 {                     //定义一个结构体, 将地址结构跟cfd捆绑
16     struct sockaddr_in cliaddr;
17     int connfd;
18 };
19 
20 void *do_work(void *arg)
21 {
22     int n,i;
23     struct s_info *ts = (struct s_info*)arg;
24     char buf[MAXLINE];
25     char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看
26 
27     while (1) 
28     {
29         n = Read(ts->connfd, buf, MAXLINE);                     //读客户端
30         if (n == 0) 
31         {
32             printf("the client %d closed...\\n", ts->connfd);
33             break;                                              //跳出循环,关闭cfd
34         }
35         printf("received from %s at PORT %d\\n",
36                 inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
37                 ntohs((*ts).cliaddr.sin_port));                 //打印客户端信息(IP/PORT)
38 
39         for (i = 0; i < n; i++) 
40         {
41             buf[i] = toupper(buf[i]);                           //小写-->大写
42         }
43 
44         Write(STDOUT_FILENO, buf, n);                           //写出至屏幕
45         Write(ts->connfd, buf, n);                              //回写给客户端
46     }
47     Close(ts->connfd);
48 
49     return NULL;
50 }
51 
52 int main(void)
53 {
54     struct sockaddr_in servaddr, cliaddr;
55     socklen_t cliaddr_len;
56     int listenfd, connfd;
57     pthread_t tid;
58     struct s_info ts[256];      //根据最大线程数创建结构体数组.
59     int i = 0;
60 
61     listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //创建一个socket, 得到lfd
62 
63     bzero(&servaddr, sizeof(servaddr));                             //地址结构清零
64     servaddr.sin_family = AF_INET;
65     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                   //指定本地任意IP
66     servaddr.sin_port = htons(SERV_PORT);                           //指定端口号 8000
67 
68     Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
69 
70     Listen(listenfd, 128);      //设置同一时刻链接服务器上限数
71 
72     printf("Accepting client connect ...\\n");
73 
74     while (1) 
75     {
76         cliaddr_len = sizeof(cliaddr);
77         connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞监听客户端链接请求
78         ts[i].cliaddr = cliaddr;
79         ts[i].connfd = connfd;
80 
81         pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
82         pthread_detach(tid);                                                    //子线程分离,防止僵线程产生.
83         i++;
84         if(i == 256)
85         {
86             break;
87         }
88     }
89 
90     return 0;
91 }
server.c
技术图片
 1 /* client.c */
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <netinet/in.h>
 6 #include <arpa/inet.h>
 7 #include "wrap.h"
 8 
 9 #define MAXLINE 80
10 #define SERV_PORT 8000
11 
12 int main(int argc, char *argv[])
13 {
14     struct sockaddr_in servaddr;
15     char buf[MAXLINE];
16     int sockfd, n;
17 
18     sockfd = Socket(AF_INET, SOCK_STREAM, 0);
19 
20     bzero(&servaddr, sizeof(servaddr));
21     servaddr.sin_family = AF_INET;
22     inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr);
23     servaddr.sin_port = htons(SERV_PORT);
24 
25     Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
26 
27     while (fgets(buf, MAXLINE, stdin) != NULL) 
28     {
29         Write(sockfd, buf, strlen(buf));
30         n = Read(sockfd, buf, MAXLINE);
31         if (n == 0)
32             printf("the other side has been closed.\\n");
33         else
34             Write(STDOUT_FILENO, buf, n);
35     }
36 
37     Close(sockfd);
38 
39     return 0;
40 }
client.c
技术图片
 1 src = $(wildcard *.c)
 2 obj = $(patsubst %.c, %.o, $(src))
 3 
 4 all: server client
 5 
 6 server: server.o wrap.o
 7     gcc server.o wrap.o -o server -Wall -lpthread
 8 client: client.o wrap.o
 9     gcc client.o wrap.o -o client -Wall -lpthread
10 
11 %.o:%.c
12     gcc -c $< -Wall 
13 
14 .PHONY: clean all
15 clean: 
16     -rm -rf server client $(obj)
makefile

三、多路I/O转接服务器

    1. IO多路转接技术概述

     多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。

     1)先构造一张有关文件描述符的列表, 将要监听的文件描述符添加到该表中
     2)然后调用一个函数,监听该表中的文件描述符,直到这些描述符表中的一个进行I/O操作时,该函数才返回。

  • 该函数为阻塞函数
  • 函数对文件描述符的检测操作是由内核完成的

     3)在返回时,它告诉进程有多少(哪些)描述符要进行I/O操作。

     IO操作方式:

     (1)阻塞等待

  • 优点:不占用cpu宝贵的时间片
  • 缺点:同一时刻只能处理一个操作, 效率低

技术图片

技术图片

     (2)非阻塞, 忙轮询

  • 优点: 提高了程序的执行效率
  • 缺点: 需要占用更多的cpu和系统资源

     一个任务:

技术图片

     多个任务:

技术图片

     解决方案:使用IO多路转接技术 select/poll/epoll

     第一种: select/poll

技术图片

    注意:select 代收员比较懒, 她只会告诉你有几个快递到了,但是哪个快递,你需要挨个遍历一遍。

    第二种: epoll

技术图片

    注意:epoll代收快递员很勤快, 她不仅会告诉你有几个快递到了, 还会告诉你是哪个快递公司的快递。

    主要使用的方法有三种:select/poll/epoll

     2. select

    (1)首先分析select的工作原理?

技术图片

  • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数。
  • 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力。

     结合下面select函数的介绍及下面的伪代码用select实现一个server端有助于上面select工作流程的理解:

技术图片
 1 int main()
 2 {
 3     int lfd = socket();
 4     bind();
 5     listen();
 6     
 7     // 创建一文件描述符表
 8     fd_st reads, temp;
 9     // 初始化
10     fd_zero(&reads);
11     // 监听的lfd加入到读集合
12     fd_set(lfd, &reads);
13     int maxfd = lfd;
14     
15     while(1)
16     {
17         // 委托检测
18         temp = reads;
19         int ret = select(maxfd+1, &temp, NULL, NULL, NULL);
20         
21         // 是不是监听的
22         if(fd_isset(lfd, &temp))
23         {
24             // 接受新连接
25             int cfd = accept();
26             // cfd加入读集合
27             fd_set(cfd, &reads);
28             // 更新maxfd
29             maxfd=maxfd<cfd ? cfd:maxfd;
30         }
31         // 客户端发送数据
32         for(int i=lfd+1; i<=maxfd; ++i)
33         {
34             if(fd_isset(i, &temp)
35             {
36                 int len = read();
37                 if(len == 0)
38                 {
39                     // cfd 从读集合中del
40                     fd_clr(i, &reads);
41                 }
42                 write();
43             }
44         }
45     }
46 }
select实现server伪代码

    (2)使用select函的优缺点:

  • 优点:

                   跨平台

  • 缺点:

                   a. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;

                   b. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;

                   c. select支持的文件描述符数量太小了,默认是1024。

      为什么是1024?

首先,看下内核中对fd_set的定义:
typedef struct {
    unsigned long fds_bits[__FDSET_LONGS];
} __kernel_fd_set;

typedef __kernel_fd_set fd_set;

其中有关的常量定义为:
#undef __NFDBITS
#define __NFDBITS (8 * sizeof(unsigned long))

#undef __FD_SETSIZE
#define __FD_SETSIZE 1024

#undef __FDSET_LONGS
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)

即__NFDBITS为8*4=32,__FD_SETSIZE为1024,那么,__FDSET_LONGS为1024/32=32,因此,fd_set实际上是32个无符号长整形,也就是1024位

    (2)select函数及示例

#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);

    nfds:         监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
    readfds:    监控有读数据到达文件描述符集合,传入传出参数
    writefds:    监控写数据到达文件描述符集合,传入传出参数
    exceptfds:    监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
    timeout:    定时阻塞监控时间,3种情况
                1.NULL,永远等下去
                2.设置timeval,等待固定时间
                3.设置timeval里时间均为0,检查描述字后立即返回,轮询
    struct timeval {
        long tv_sec; /* seconds */
        long tv_usec; /* microseconds */
    };
    void FD_CLR(int fd, fd_set *set);     //把文件描述符集合里fd清0
    int FD_ISSET(int fd, fd_set *set);     //测试文件描述符集合里fd是否置1
    void FD_SET(int fd, fd_set *set);     //把文件描述符集合里fd位置1
    void FD_ZERO(fd_set *set);             //把文件描述符集合里所有位清0

 select示例:

技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 
 10 
 11 int main(int argc, const char* argv[])
 12 {
 13     if(argc < 2)
 14     {
 15         printf("eg: ./a.out port\\n");
 16         exit(1);
 17     }
 18     struct sockaddr_in serv_addr;
 19     socklen_t serv_len = sizeof(serv_addr);
 20     int port = atoi(argv[1]);
 21 
 22     // 创建套接字
 23     int lfd = socket(AF_INET, SOCK_STREAM, 0);
 24     // 初始化服务器 sockaddr_in 
 25     memset(&serv_addr, 0, serv_len);
 26     serv_addr.sin_family = AF_INET;                   // 地址族 
 27     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 28     serv_addr.sin_port = htons(port);            // 设置端口 
 29     // 绑定IP和端口
 30     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 31 
 32     // 设置同时监听的最大个数
 33     listen(lfd, 36);
 34     printf("Start accept ......\\n");
 35 
 36     struct sockaddr_in client_addr;
 37     socklen_t cli_len = sizeof(client_addr);
 38 
 39     // 最大的文件描述符
 40     int maxfd = lfd;
 41     // 文件描述符读集合
 42     fd_set reads, temp;
 43     // init
 44     FD_ZERO(&reads);
 45     FD_SET(lfd, &reads);
 46 
 47     while(1)
 48     {
 49         // 委托内核做IO检测
 50         temp = reads;
 51         int ret = select(maxfd+1, &temp, NULL, NULL, NULL);
 52         if(ret == -1)
 53         {
 54             perror("select error");
 55             exit(1);
 56         }
 57         // 客户端发起了新的连接
 58         if(FD_ISSET(lfd, &temp))
 59         {
 60             // 接受连接请求 - accept不阻塞
 61             int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
 62             if(cfd == -1)
 63             {
 64                 perror("accept error");
 65                 exit(1);
 66             }
 67             char ip[64];
 68             printf("new client IP: %s, Port: %d\\n", 
 69                    inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
 70                    ntohs(client_addr.sin_port));
 71             // 将cfd加入到待检测的读集合中 - 下一次就可以检测到了
 72             FD_SET(cfd, &reads);
 73             // 更新最大的文件描述符
 74             maxfd = maxfd < cfd ? cfd : maxfd;
 75         }
 76         // 已经连接的客户端有数据到达
 77         for(int i=lfd+1; i<=maxfd; ++i)
 78         {
 79             if(FD_ISSET(i, &temp))
 80             {
 81                 char buf[1024] = {0};
 82                 int len = recv(i, buf, sizeof(buf), 0);
 83                 if(len == -1)
 84                 {
 85                     perror("recv error");
 86                     exit(1);
 87                 }
 88                 else if(len == 0)
 89                 {
 90                     printf("客户端已经断开了连接\\n");
 91                     close(i);
 92                     // 从读集合中删除
 93                     FD_CLR(i, &reads);
 94                 }
 95                 else
 96                 {
 97                     printf("recv buf: %s\\n", buf);
 98                     send(i, buf, strlen(buf)+1, 0);
 99                 }
100             }
101         }
102     }
103 
104     close(lfd);
105     return 0;
106 }
select.c

 select示例2:

技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <sys/select.h>
 10 
 11 #define SERV_PORT 8989
 12 
 13 int main(int argc, const char* argv[])
 14 {
 15     int lfd, cfd;
 16     struct sockaddr_in serv_addr, clien_addr;
 17     int serv_len, clien_len;
 18 
 19     // 创建套接字
 20     lfd = socket(AF_INET, SOCK_STREAM, 0);
 21     // 初始化服务器 sockaddr_in 
 22     memset(&serv_addr, 0, sizeof(serv_addr));
 23     serv_addr.sin_family = AF_INET;                   // 地址族 
 24     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 25     serv_addr.sin_port = htons(SERV_PORT);            // 设置端口 
 26     serv_len = sizeof(serv_addr);
 27     // 绑定IP和端口
 28     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 29 
 30     // 设置同时监听的最大个数
 31     listen(lfd, 36);
 32     printf("Start accept ......\\n");
 33 
 34     int ret;
 35     int maxfd = lfd;
 36     // reads 实时更新,temps 内核检测
 37     fd_set reads, temps;
 38 
 39     FD_ZERO(&reads);
 40     FD_SET(lfd, &reads);
 41 
 42     while(1)
 43     {
 44         temps = reads;
 45         ret = select(maxfd+1, &temps, NULL, NULL, NULL);
 46         if(ret == -1)
 47         {
 48             perror("select error");
 49             exit(1);
 50         }
 51 
 52 
 53         // 判断是否有新连接
 54         if(FD_ISSET(lfd, &temps))
 55         {
 56             // 接受连接请求
 57             clien_len = sizeof(clien_len);
 58             int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
 59 
 60             // 文件描述符放入检测集合
 61             FD_SET(cfd, &reads);
 62             // 更新最大文件描述符
 63             maxfd = maxfd < cfd ? cfd : maxfd;
 64         }
 65 
 66         // 遍历检测的文件描述符是否有读操作
 67         for(int i=lfd+1; i<=maxfd; ++i)
 68         {
 69             if(FD_ISSET(i, &temps))
 70             {
 71                 // 读数据
 72                 char buf[1024] = {0};
 73                 int len = read(i, buf, sizeof(buf));
 74                 if(len  == -1)
 75                 {
 76                     perror("read error");
 77                     exit(1);
 78                 }
 79                 else if(len == 0)
 80                 {
 81                     // 对方关闭了连接
 82                     FD_CLR(i, &reads);
 83                     close(i);
 84                     if(maxfd == i)
 85                     {
 86                         maxfd--;
 87                     }
 88                 }
 89                 else
 90                 {
 91                     printf("read buf = %s\\n", buf);
 92                     for(int j=0; j<len; ++j)
 93                     {
 94                         buf[j] = toupper(buf[j]);
 95                     }
 96                     printf("--buf toupper: %s\\n", buf);
 97                     write(i, buf, strlen(buf)+1);
 98                 }
 99             }
100         }
101     }
102 
103     close(lfd);
104     return 0;
105 }
select.c
技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <sys/select.h>
 10 
 11 #define SERV_PORT 8989
 12 
 13 int main(int argc, const char* argv[])
 14 {
 15     int lfd, cfd;
 16     struct sockaddr_in serv_addr, clien_addr;
 17     int serv_len, clien_len;
 18 
 19     // 创建套接字
 20     lfd = socket(AF_INET, SOCK_STREAM, 0);
 21     // 初始化服务器 sockaddr_in 
 22     memset(&serv_addr, 0, sizeof(serv_addr));
 23     serv_addr.sin_family = AF_INET;                   // 地址族 
 24     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 25     serv_addr.sin_port = htons(SERV_PORT);            // 设置端口 
 26     serv_len = sizeof(serv_addr);
 27     // 绑定IP和端口
 28     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 29 
 30     // 设置同时监听的最大个数
 31     listen(lfd, 36);
 32     printf("Start accept ......\\n");
 33 
 34     int ret;
 35     int maxfd = lfd;
 36     // reads 实时更新,temps 内核检测
 37     fd_set reads, temps;
 38     
 39     /*===============================================================*/
 40     // 记录要检测的文件描述符的数组
 41     int allfd[FD_SETSIZE];   // 1024
 42     // 记录数组中最后一个元素的下标
 43     int last_index = 0;
 44     // 初始化数组
 45     for(int i=0; i<FD_SETSIZE; ++i)
 46     {
 47         allfd[i] = -1;  // 无效文件描述符值
 48     }
 49     allfd[0] = lfd;     // 监听的文件描述符添加到数组中
 50     /*===============================================================*/
 51 
 52     // 初始化监听的读集合
 53     FD_ZERO(&reads);
 54     FD_SET(lfd, &reads);
 55 
 56     while(1)
 57     {
 58         // 每次都需要更新,否则select不会重新检测
 59         temps = reads;
 60         ret = select(maxfd+1, &temps, NULL, NULL, NULL);
 61         if(ret == -1)
 62         {
 63             perror("select error");
 64             exit(1);
 65         }
 66 
 67         int i = 0;
 68         char bufip[64];
 69         // 判断是否有新连接
 70         if(FD_ISSET(lfd, &temps))
 71         {
 72             // 接受连接请求
 73             clien_len = sizeof(clien_len);
 74             int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
 75             printf("client ip: %s, port: %d\\n",
 76                    inet_ntop(AF_INET, &clien_addr.sin_addr.s_addr, bufip, sizeof(bufip)),
 77                    ntohs(clien_addr.sin_port));
 78 
 79             // 文件描述符放入检测集合
 80             FD_SET(cfd, &reads);
 81             // 更新最大文件描述符
 82             maxfd = maxfd < cfd ? cfd : maxfd;
 83             // cfd添加到检测数组中
 84             for(i=0; i<FD_SETSIZE; ++i)
 85             {
 86                 if(allfd[i] == -1)
 87                 {
 88                     allfd[i] = cfd;
 89                     break;
 90                 }
 91             }
 92             // 更新数组最后一个有效值下标
 93             last_index = last_index < i ? i : last_index; 
 94         }
 95 
 96         // 遍历检测的文件描述符是否有读操作
 97         for(i=lfd+1; i<=maxfd; ++i)
 98         {
 99             if(FD_ISSET(i, &temps))
100             {
101                 // 读数据
102                 char buf[1024] = {0};
103                 int len = read(i, buf, sizeof(buf));
104                 if(len  == -1)
105                 {
106                     perror("read error");
107                     exit(1);
108                 }
109                 else if(len == 0)
110                 {
111                     // 对方关闭了连接
112                     FD_CLR(i, &reads);
113                     close(i);
114                     if(maxfd == i)
115                     {
116                         maxfd--;
117                     }
118                     allfd[i] = -1;
119                     printf("对方已经关闭了连接。。。。。。\\n");
120                 }
121                 else
122                 {
123                     printf("read buf = %s\\n", buf);
124                     for(int j=0; j<len; ++j)
125                     {
126                         buf[j] = toupper(buf[j]);
127                     }
128                     printf("--buf toupper: %s\\n", buf);
129                     write(i, buf, strlen(buf)+1);
130                 }
131             }
132         }
133     }
134 
135     close(lfd);
136     return 0;
137 }
select_plus.c

    补充 pselect:

    pselect原型如下。此模型应用较少,可参考select模型自行编写C/S:

#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);
    struct timespec {
        long tv_sec; /* seconds */
        long tv_nsec; /* nanoseconds */
    };
    用sigmask替代当前进程的阻塞信号集,调用返回后还原原有阻塞信号集

     3. poll

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd {
        int fd; /* 文件描述符 */
        short events; /* 监控的事件 */
        short revents; /* 监控事件中满足条件返回的事件 */
    };
    POLLIN            普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
    POLLRDNORM        数据可读
    POLLRDBAND        优先级带数据可读
    POLLPRI         高优先级可读数据
    POLLOUT        普通或带外数据可写
    POLLWRNORM        数据可写
    POLLWRBAND        优先级带数据可写
    POLLERR         发生错误
    POLLHUP         发生挂起
    POLLNVAL         描述字不是一个打开的文件

fds 数组地址 nfds 监控数组中有多少文件描述符需要被监控,数组的最大长度, 数组中最后一个使用的元素下标+1,内核会轮询检测fd数组的每个文件描述符 timeout 毫秒级等待
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏 0:立即返回,不阻塞进程 >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
返回值: IO发送变化的文件描述符的个数

技术图片

    如果不再监控某个文件描述符时,可以把pollfd中,fd设置为-1,poll不再监控此pollfd,下次返回时,把revents设置为0。

    示例(使用poll实现的server):

技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <poll.h>
 10 
 11 #define SERV_PORT 8989
 12 
 13 int main(int argc, const char* argv[])
 14 {
 15     int lfd, cfd;
 16     struct sockaddr_in serv_addr, clien_addr;
 17     int serv_len, clien_len;
 18 
 19     // 创建套接字
 20     lfd = socket(AF_INET, SOCK_STREAM, 0);
 21     // 初始化服务器 sockaddr_in 
 22     memset(&serv_addr, 0, sizeof(serv_addr));
 23     serv_addr.sin_family = AF_INET;                   // 地址族 
 24     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 25     serv_addr.sin_port = htons(SERV_PORT);            // 设置端口 
 26     serv_len = sizeof(serv_addr);
 27     // 绑定IP和端口
 28     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 29 
 30     // 设置同时监听的最大个数
 31     listen(lfd, 36);
 32     printf("Start accept ......\\n");
 33 
 34     // poll结构体
 35     struct pollfd allfd[1024];
 36     int max_index = 0;
 37     // init
 38     for(int i=0; i<1024; ++i)
 39     {
 40         allfd[i].fd = -1;
 41     }
 42     allfd[0].fd = lfd;
 43     allfd[0].events = POLLIN;
 44 
 45     while(1)
 46     {
 47         int i = 0;
 48         int ret = poll(allfd, max_index+1, -1); 
 49         if(ret == -1)
 50         {
 51             perror("poll error");
 52             exit(1);
 53         }
 54 
 55         // 判断是否有连接请求
 56         if(allfd[0].revents & POLLIN)
 57         {
 58             clien_len = sizeof(clien_addr);
 59             // 接受连接请求
 60             int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
 61             printf("============\\n");
 62 
 63             // cfd添加到poll数组
 64             for(i=0; i<1024; ++i)
 65             {
 66                 if(allfd[i].fd == -1)
 67                 {
 68                     allfd[i].fd = cfd;
 69                     break;
 70                 }
 71             }
 72             // 更新最后一个元素的下标
 73             max_index = max_index < i ? i : max_index;
 74         }
 75 
 76         // 遍历数组
 77         for(i=1; i<=max_index; ++i)
 78         {
 79             int fd = allfd[i].fd;
 80             if(fd == -1)
 81             {
 82                 continue;
 83             }
 84             if(allfd[i].revents & POLLIN)
 85             {
 86                 // 接受数据
 87                 char buf[1024] = {0};
 88                 int len = recv(fd, buf, sizeof(buf), 0);
 89                 if(len == -1)
 90                 {
 91                     perror("recv error");
 92                     exit(1);
 93                 }
 94                 else if(len == 0)
 95                 {
 96                     allfd[i].fd = -1;
 97                     close(fd);
 98                     printf("客户端已经主动断开连接。。。\\n");
 99                 }
100                 else
101                 {
102                     printf("recv buf = %s\\n", buf);
103                     for(int k=0; k<len; ++k)
104                     {
105                         buf[k] = toupper(buf[k]);
106                     }
107                     printf("buf toupper: %s\\n", buf);
108                     send(fd, buf, strlen(buf)+1, 0);
109                 }
110 
111             }
112 
113         }
114     }
115 
116     close(lfd);
117     return 0;
118 }
poll.c

    poll与select的比较:

  • 两者其实没有大的变化,主要是poll没有select对于1024的限制,由于内部实现是通过链表来实现的,因此理论上没有限制。但是两者最大的缺点还是内核会轮询检测fd数组的每个文件描述符。

    补充 ppoll:

    GNU定义了ppoll(非POSIX标准),可以支持设置信号屏蔽字,可参考poll模型自行实现C/S。

#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);

     4. epoll

    epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者

每次等待事件之前都必须重新准备要被侦听的文件描述符集合(用户态和内核态共享同一片文件描述符表内存),另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那

些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了

    目前epell是linux大规模并发网络程序中的热门首选模型。

    epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提

高应用程序效率。

    可以使用cat命令查看一个进程可以打开的socket描述符上限。

cat /proc/sys/fs/file-max

    如有需要,可以通过修改配置文件的方式修改该上限值。

sudo vi /etc/security/limits.conf
    在文件尾部写入以下配置,soft软限制,hard硬限制。
    * soft nofile 65536
    * hard nofile 100000

    基础API

      1)创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。

#include <sys/epoll.h>
int epoll_create(int size)        size:监听数目, epoll上能关注的最大描述符数

     2)控制某个epoll监控的文件描述符上的事件:注册、修改、删除。

#include <sys/epoll.h>
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
        epfd:    为epoll_creat的句柄
        op:        表示动作,用3个宏来表示:
            EPOLL_CTL_ADD (注册新的fd到epfd),
            EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
            EPOLL_CTL_DEL (从epfd删除一个fd);
        event:    告诉内核需要监听的事件

        struct epoll_event {
            __uint32_t events; /* Epoll events */
            epoll_data_t data; /* User data variable */
        };
        typedef union epoll_data {
            void *ptr;
            int fd;
            uint32_t u32;
            uint64_t u64;
        } epoll_data_t;

        EPOLLIN :    表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
        EPOLLOUT:    表示对应的文件描述符可以写
        EPOLLPRI:    表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
        EPOLLERR:    表示对应的文件描述符发生错误
        EPOLLHUP:    表示对应的文件描述符被挂断;
        EPOLLET:     将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

     3)等待所监控文件描述符上有事件的产生,类似于select()调用。

#include <sys/epoll.h>
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
        events:        用来存内核得到事件的集合,用于回传待处理事件的数组
        maxevents:    告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size
        timeout:    是超时时间
            -1:    阻塞
            0:    立即返回,非阻塞
            >0:    指定毫秒
        返回值:    成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

 epoll工作原理:

技术图片

通过下面的伪代码有助于上面的理解:

技术图片
 1 int main()
 2 {
 3     // 创建监听的套接字
 4     int lfd = socket();
 5     // 绑定
 6     bind();
 7     // 监听
 8     listen();
 9     
10     // epoll树根节点
11     int epfd = epoll_create(3000);
12     // 存储发送变化的fd对应信息
13     struct epoll_event all[3000];
14     // init
15     // 监听的lfd挂到epoll树上
16     struct epoll_event ev;
17     // 在ev中init lfd信息
18     ev.events = EPOLLIN ;
19     ev.data.fd = lfd;
20     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
21     while(1)
22     {
23         // 委托内核检测事件
24         int ret = epoll_wait(epfd, all, 3000, -1);
25         // 根据ret遍历all数组
26         for(int i=0; i<ret; ++i)
27         {
28             int fd = all[i].data.fd;
29             // 有新的连接
30             if( fd == lfd)
31             {
32                 // 接收连接请求 - accept不阻塞
33                 int cfd = accept();
34                 // cfd上树
35                 ev.events = EPOLLIN;
36                 ev.data.fd = cfd;
37                 epoll_ctl(epfd, epoll_ctl_add, cfd, &ev);
38             }
39             // 已经连接的客户端有数据发送过来
40             else
41             {
42                 // 只处理客户端发来的数据
43                 if(!all[i].events & EPOLLIN)
44                 {
45                     continue;
46                 }
47                 // 读数据
48                 int len = recv();
49                 if(len == 0)
50                 {
51                     close(fd);
52                     // 检测的fd从树上删除
53                     epoll_ctl(epfd, epoll_ctl_del, fd, NULL);
54                 }
55                 // 写数据
56                 send();
57             }
58         }
59     }
60 }
epoll伪代码

示例:

技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <sys/epoll.h>
 10 
 11 
 12 int main(int argc, const char* argv[])
 13 {
 14     if(argc < 2)
 15     {
 16         printf("eg: ./a.out port\\n");
 17         exit(1);
 18     }
 19     struct sockaddr_in serv_addr;
 20     socklen_t serv_len = sizeof(serv_addr);
 21     int port = atoi(argv[1]);
 22 
 23     // 创建套接字
 24     int lfd = socket(AF_INET, SOCK_STREAM, 0);
 25     // 初始化服务器 sockaddr_in 
 26     memset(&serv_addr, 0, serv_len);
 27     serv_addr.sin_family = AF_INET;                   // 地址族 
 28     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 29     serv_addr.sin_port = htons(port);            // 设置端口 
 30     // 绑定IP和端口
 31     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 32 
 33     // 设置同时监听的最大个数
 34     listen(lfd, 36);
 35     printf("Start accept ......\\n");
 36 
 37     struct sockaddr_in client_addr;
 38     socklen_t cli_len = sizeof(client_addr);
 39 
 40     // 创建epoll树根节点
 41     int epfd = epoll_create(2000);
 42     // 初始化epoll树
 43     struct epoll_event ev;
 44     ev.events = EPOLLIN;
 45     ev.data.fd = lfd;
 46     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
 47 
 48     struct epoll_event all[2000];
 49     while(1)
 50     {
 51         // 使用epoll通知内核fd 文件IO检测 sizeof(all)/sizeof(all[0]) --> sizeof(struct epoll_event)
 52         int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
 53 
 54         // 遍历all数组中的前ret个元素
 55         for(int i=0; i<ret; ++i)
 56         {
 57             int fd = all[i].data.fd;
 58             // 判断是否有新连接
 59             if(fd == lfd)
 60             {
 61                 // 接受连接请求
 62                 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
 63                 if(cfd == -1)
 64                 {
 65                     perror("accept error");
 66                     exit(1);
 67                 }
 68                 // 将新得到的cfd挂到树上
 69                 struct epoll_event temp;
 70                 temp.events = EPOLLIN;
 71                 temp.data.fd = cfd;
 72                 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
 73                 
 74                 // 打印客户端信息
 75                 char ip[64] = {0};
 76                 printf("New Client IP: %s, Port: %d\\n",
 77                     inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
 78                     ntohs(client_addr.sin_port));
 79                 
 80             }
 81             else
 82             {
 83                 // 处理已经连接的客户端发送过来的数据
 84                 if(!all[i].events & EPOLLIN) 
 85                 {
 86                     continue;
 87                 }
 88 
 89                 // 读数据
 90                 char buf[1024] = {0};
 91                 int len = recv(fd, buf, sizeof(buf), 0);
 92                 if(len == -1)
 93                 {
 94                     perror("recv error");
 95                     exit(1);
 96                 }
 97                 else if(len == 0)
 98                 {
 99                     printf("client disconnected ....\\n");
100                     // fd从epoll树上删除
101                     ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
102                     if(ret == -1)
103                     {
104                         perror("epoll_ctl - del error");
105                         exit(1);
106                     }
107                     close(fd);
108                 }
109                 else
110                 {
111                     printf(" recv buf: %s\\n", buf);
112                     write(fd, buf, len);
113                 }
114             }
115         }
116     }
117 
118     close(lfd);
119     return 0;
120 }
epoll.c
技术图片
 1 /* client.c */
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <netinet/in.h>
 6 #include "wrap.h"
 7 
 8 #define MAXLINE 80
 9 #define SERV_PORT 6666
10 
11 int main(int argc, char *argv[])
12 {
13     struct sockaddr_in servaddr;
14     char buf[MAXLINE];
15     int sockfd, n;
16 
17     sockfd = Socket(AF_INET, SOCK_STREAM, 0);
18 
19     bzero(&servaddr, sizeof(servaddr));
20     servaddr.sin_family = AF_INET;
21     inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
22     servaddr.sin_port = htons(SERV_PORT);
23 
24     Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
25 
26     while (fgets(buf, MAXLINE, stdin) != NULL) {
27         Write(sockfd, buf, strlen(buf));
28         n = Read(sockfd, buf, MAXLINE);
29         if (n == 0)
30             printf("the other side has been closed.\\n");
31         else
32             Write(STDOUT_FILENO, buf, n);
33     }
34 
35     Close(sockfd);
36     return 0;
37 }
client.c

    注意:epoll_wait 调用次数越多, 系统的开销越大

四、epoll进阶

 1. 事件模型

      EPOLL事件有两种模型:

      Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。

      Level Triggered (LT) 水平触发只要有数据都会触发。

     思考如下步骤:

     1)假定我们已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符。

     2)管道的另一端写入了2KB的数据

     3)调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作

     4)读取1KB的数据

     5)调用epoll_wait……

     在这个过程中,有两种工作模式:

     (1)ET模式

      ET模式即Edge Triggered工作模式。

      如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在

等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩

余数据。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面

会介绍避免可能的缺陷。

  • 基于非阻塞文件句柄
  • 只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的

读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。

     (2)LT模式

      LT模式即Level Triggered工作模式。

     与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。

     LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任

何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

     ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再

为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

2.  实例一

    基于管道epoll ET触发模式

技术图片
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/epoll.h>
 4 #include <errno.h>
 5 #include <unistd.h>
 6 
 7 #define MAXLINE 10
 8 
 9 int main(int argc, char *argv[])
10 {
11     int efd, i;
12     int pfd[2];
13     pid_t pid;
14     char buf[MAXLINE], ch = a;
15 
16     pipe(pfd);
17     pid = fork();
18     if (pid == 0) {
19         close(pfd[0]);
20         while (1) {
21             for (i = 0; i < MAXLINE/2; i++)
22                 buf[i] = ch;
23             buf[i-1] = \\n;
24             ch++;
25 
26             for (; i < MAXLINE; i++)
27                 buf[i] = ch;
28             buf[i-1] = \\n;
29             ch++;
30 
31             write(pfd[1], buf, sizeof(buf));
32             sleep(2);
33         }
34         close(pfd[1]);
35     } else if (pid > 0) {
36         struct epoll_event event;
37         struct epoll_event resevent[10];
38         int res, len;
39         close(pfd[1]);
40 
41         efd = epoll_create(10);
42         /* event.events = EPOLLIN; */
43         event.events = EPOLLIN | EPOLLET;        /* ET 边沿触发 ,默认是水平触发 */
44         event.data.fd = pfd[0];
45     epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
46 
47         while (1) {
48             res = epoll_wait(efd, resevent, 10, -1);
49             printf("res %d\\n", res);
50             if (resevent[0].data.fd == pfd[0]) {
51                 len = read(pfd[0], buf, MAXLINE/2);
52                 write(STDOUT_FILENO, buf, len);
53             }
54         }
55         close(pfd[0]);
56         close(efd);
57     } else {
58         perror("fork");
59         exit(-1);
60     }
61     return 0;
62 }
et_epoll.c

3. 实例二

    基于网络C/S模型的epoll ET触发模式

技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <sys/epoll.h>
 10 
 11 
 12 int main(int argc, const char* argv[])
 13 {
 14     if(argc < 2)
 15     {
 16         printf("eg: ./a.out port\\n");
 17         exit(1);
 18     }
 19     struct sockaddr_in serv_addr;
 20     socklen_t serv_len = sizeof(serv_addr);
 21     int port = atoi(argv[1]);
 22 
 23     // 创建套接字
 24     int lfd = socket(AF_INET, SOCK_STREAM, 0);
 25     // 初始化服务器 sockaddr_in 
 26     memset(&serv_addr, 0, serv_len);
 27     serv_addr.sin_family = AF_INET;                   // 地址族 
 28     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 29     serv_addr.sin_port = htons(port);            // 设置端口 
 30     // 绑定IP和端口
 31     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 32 
 33     // 设置同时监听的最大个数
 34     listen(lfd, 36);
 35     printf("Start accept ......\\n");
 36 
 37     struct sockaddr_in client_addr;
 38     socklen_t cli_len = sizeof(client_addr);
 39 
 40     // 创建epoll树根节点
 41     int epfd = epoll_create(2000);
 42     // 初始化epoll树
 43     struct epoll_event ev;
 44 
 45     // 设置边沿触发
 46     ev.events = EPOLLIN | EPOLLET;
 47     ev.data.fd = lfd;
 48     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
 49 
 50     struct epoll_event all[2000];
 51     while(1)
 52     {
 53         // 使用epoll通知内核fd 文件IO检测
 54         int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
 55         printf("================== epoll_wait =============\\n");
 56 
 57         // 遍历all数组中的前ret个元素
 58         for(int i=0; i<ret; ++i)
 59         {
 60             int fd = all[i].data.fd;
 61             // 判断是否有新连接
 62             if(fd == lfd)
 63             {
 64                 // 接受连接请求
 65                 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
 66                 if(cfd == -1)
 67                 {
 68                     perror("accept error");
 69                     exit(1);
 70                 }
 71                 // 将新得到的cfd挂到树上
 72                 struct epoll_event temp;
 73                 // 设置边沿触发
 74                 temp.events = EPOLLIN | EPOLLET;
 75                 temp.data.fd = cfd;
 76                 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
 77                 
 78                 // 打印客户端信息
 79                 char ip[64] = {0};
 80                 printf("New Client IP: %s, Port: %d\\n",
 81                     inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
 82                     ntohs(client_addr.sin_port));
 83                 
 84             }
 85             else
 86             {
 87                 // 处理已经连接的客户端发送过来的数据
 88                 if(!all[i].events & EPOLLIN) 
 89                 {
 90                     continue;
 91                 }
 92 
 93                 // 读数据
 94                 char buf[5] = {0};
 95                 int len = recv(fd, buf, sizeof(buf), 0);
 96                 if(len == -1)
 97                 {
 98                     perror("recv error");
 99                     exit(1);
100                 }
101                 else if(len == 0)
102                 {
103                     printf("client disconnected ....\\n");
104                     // fd从epoll树上删除
105                     ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
106                     if(ret == -1)
107                     {
108                         perror("epoll_ctl - del error");
109                         exit(1);
110                     }
111                     close(fd);                   
112                 }
113                 else
114                 {
115                     // printf(" recv buf: %s\\n", buf);
116                     write(STDOUT_FILENO, buf, len);
117                     write(fd, buf, len);
118                 }
119             }
120         }
121     }
122 
123     close(lfd);
124     return 0;
125 }
et_epoll.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <fcntl.h>
 9 
10 // tcp client
11 int main(int argc, const char* argv[])
12 {
13     if(argc < 2)
14     {
15         printf("eg: ./a.out port\\n");
16         exit(1);
17     }
18     // 创建套接字
19     int fd = socket(AF_INET, SOCK_STREAM, 0);
20     if(fd == -1)
21     {
22         perror("socket error");
23         exit(1);
24     }
25     int port = atoi(argv[1]);
26     // 连接服务器
27     struct sockaddr_in serv_addr;
28     memset(&serv_addr, 0, sizeof(serv_addr));
29     serv_addr.sin_family = AF_INET;
30     serv_addr.sin_port = htons(port);
31     inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
32     int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
33     if(ret == -1)
34     {
35         perror("connect error");
36         exit(1);
37     }
38 
39     // 通信
40     while(1)
41     {
42         // 写数据
43         // 接收键盘输入
44         char buf[512];
45         fgets(buf, sizeof(buf), stdin);
46         // 发送给服务器
47         write(fd, buf, strlen(buf)+1);
48 
49         // 接收服务器端的数据
50         int len = read(fd, buf, sizeof(buf));
51         printf("read buf = %s, len = %d\\n", buf, len);
52     }
53     return 0;
54 }
tcp_client.c

    执行结果:

    client端:

[[email protected] epoll]# ./client 6666
000001111122222

read buf = 000001111122222
, len = 5


read buf = 11111, len = 5


read buf = 22222, len = 5

    server端:

[[email protected] epoll]# ./et_epoll 6666
Start accept ......
================== epoll_wait =============
New Client IP: 127.0.0.1, Port: 54080
================== epoll_wait =============
00000================== epoll_wait =============
11111================== epoll_wait =============
22222

    执行结果分析:可以看出,当客户端发送数据(000001111122222)到server端(接收数据缓冲区内),但是由于server端一次只接受5个字节(00000),因此在接受完5个字节之后,将接收的5个字节数据发回给客户端,程序又会在epoll_wait处阻塞等待。当有新数据再次发送过来,则会将上一次缓冲区中剩余的数据(11111)读取并发送给客户端,如此最后将(22222)发送给客户端。

4. 实例三

    基于网络C/S非阻塞模型的epoll ET触发模式

    实现过程中注意两点:

  • 当服务端接收到客户端新的连接(cfd),需要设置客户端连接问价描述符(cfd)为非阻塞模式,因为下面需要循环读取服务端缓冲区中的数据,而如果不设置 cfd 为非阻塞模式,则当读完缓冲区的数据 recv 再次读取会阻塞住,则整个程序会被阻塞;
  • 通过errno == EAGAIN来判断缓冲区中的数据读取完成。
技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <sys/epoll.h>
 10 #include <fcntl.h>
 11 #include <errno.h>
 12 
 13 int main(int argc, const char* argv[])
 14 {
 15     if(argc < 2)
 16     {
 17         printf("eg: ./a.out port\\n");
 18         exit(1);
 19     }
 20     struct sockaddr_in serv_addr;
 21     socklen_t serv_len = sizeof(serv_addr);
 22     int port = atoi(argv[1]);
 23 
 24     // 创建套接字
 25     int lfd = socket(AF_INET, SOCK_STREAM, 0);
 26     // 初始化服务器 sockaddr_in 
 27     memset(&serv_addr, 0, serv_len);
 28     serv_addr.sin_family = AF_INET;                   // 地址族 
 29     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 30     serv_addr.sin_port = htons(port);            // 设置端口 
 31     // 绑定IP和端口
 32     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 33 
 34     // 设置同时监听的最大个数
 35     listen(lfd, 36);
 36     printf("Start accept ......\\n");
 37 
 38     struct sockaddr_in client_addr;
 39     socklen_t cli_len = sizeof(client_addr);
 40 
 41     // 创建epoll树根节点
 42     int epfd = epoll_create(2000);
 43     // 初始化epoll树
 44     struct epoll_event ev;
 45 
 46     // 设置边沿触发
 47     ev.events = EPOLLIN;
 48     ev.data.fd = lfd;
 49     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
 50 
 51     struct epoll_event all[2000];
 52     while(1)
 53     {
 54         // 使用epoll通知内核fd 文件IO检测
 55         int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
 56         printf("================== epoll_wait =============\\n");
 57 
 58         // 遍历all数组中的前ret个元素
 59         for(int i=0; i<ret; ++i)
 60         {
 61             int fd = all[i].data.fd;
 62             // 判断是否有新连接
 63             if(fd == lfd)
 64             {
 65                 // 接受连接请求
 66                 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
 67                 if(cfd == -1)
 68                 {
 69                     perror("accept error");
 70                     exit(1);
 71                 }
 72                 // 设置文件cfd为非阻塞模式
 73                 int flag = fcntl(cfd, F_GETFL);
 74                 flag |= O_NONBLOCK;
 75                 fcntl(cfd, F_SETFL, flag);
 76 
 77                 // 将新得到的cfd挂到树上
 78                 struct epoll_event temp;
 79                 // 设置边沿触发
 80                 temp.events = EPOLLIN | EPOLLET;
 81                 temp.data.fd = cfd;
 82                 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
 83                 
 84                 // 打印客户端信息
 85                 char ip[64] = {0};
 86                 printf("New Client IP: %s, Port: %d\\n",
 87                     inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
 88                     ntohs(client_addr.sin_port));
 89                 
 90             }
 91             else
 92             {
 93                 // 处理已经连接的客户端发送过来的数据
 94                 if(!all[i].events & EPOLLIN) 
 95                 {
 96                     continue;
 97                 }
 98 
 99                 // 读数据
100                 char buf[5] = {0};
101                 int len;
102                 // 循环读数据
103                 while( (len = recv(fd, buf, sizeof(buf), 0)) > 0 )
104                 {
105                     // 数据打印到终端
106                     write(STDOUT_FILENO, buf, len);
107                     // 发送给客户端
108                     send(fd, buf, len, 0);
109                 }
110                 if(len == 0)
111                 {
112                     printf("客户端断开了连接\\n");
113                     ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
114                     if(ret == -1)
115                     {
116                         perror("epoll_ctl - del error");
117                         exit(1);
118                     }
119                     close(fd);
120                 }
121                 else if(len == -1)
122                 {
123                     if(errno == EAGAIN)
124                     {
125                         printf("缓冲区数据已经读完\\n");
126                     }
127                     else
128                     {
129                         printf("recv error----\\n");
130                         exit(1);
131                     }
132                 }
133             }
134         }
135     }
136 
137     close(lfd);
138     return 0;
139 }
nonblock_et_epoll.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <fcntl.h>
 9 
10 // tcp client
11 int main(int argc, const char* argv[])
12 {
13     if(argc < 2)
14     {
15         printf("eg: ./a.out port\\n");
16         exit(1);
17     }
18     // 创建套接字
19     int fd = socket(AF_INET, SOCK_STREAM, 0);
20     if(fd == -1)
21     {
22         perror("socket error");
23         exit(1);
24     }
25     int port = atoi(argv[1]);
26     // 连接服务器
27     struct sockaddr_in serv_addr;
28     memset(&serv_addr, 0, sizeof(serv_addr));
29     serv_addr.sin_family = AF_INET;
30     serv_addr.sin_port = htons(port);
31     inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
32     int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
33     if(ret == -1)
34     {
35         perror("connect error");
36         exit(1);
37     }
38 
39     // 通信
40     while(1)
41     {
42         // 写数据
43         // 接收键盘输入
44         char buf[512];
45         fgets(buf, sizeof(buf), stdin);
46         // 发送给服务器
47         write(fd, buf, strlen(buf)+1);
48 
49         // 接收服务器端的数据
50         int len = read(fd, buf, sizeof(buf));
51         printf("read buf = %s, len = %d\\n", buf, len);
52     }
53     return 0;
54 }
tcp_client.c

    执行结果:

    server端:

[[email protected] epoll]# ./nonblock_et_epoll 8888
Start accept ......
================== epoll_wait =============
New Client IP: 127.0.0.1, Port: 47634
================== epoll_wait =============
000001111122222
缓冲区数据已经读完
================== epoll_wait =============
hello world
缓冲区数据已经读完

    client端:

[[email protected] epoll]# ./client 8888
000001111122222
read buf = 000001111122222
, len = 17
hello world
read buf = hello world
, len = 13

    执行结果分析:可以看出设置为epoll et非阻塞模式,当客户端发送数据不管有多少个字节,server端会全部从缓冲区读取并发送给客户端(包括客户端发送的回车(‘\\n‘))。

5. 示例四

    基于网络C/S模型的epoll LT触发模式

技术图片
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 #include <sys/epoll.h>
 10 
 11 
 12 int main(int argc, const char* argv[])
 13 {
 14     if(argc < 2)
 15     {
 16         printf("eg: ./a.out port\\n");
 17         exit(1);
 18     }
 19     struct sockaddr_in serv_addr;
 20     socklen_t serv_len = sizeof(serv_addr);
 21     int port = atoi(argv[1]);
 22 
 23     // 创建套接字
 24     int lfd = socket(AF_INET, SOCK_STREAM, 0);
 25     // 初始化服务器 sockaddr_in 
 26     memset(&serv_addr, 0, serv_len);
 27     serv_addr.sin_family = AF_INET;                   // 地址族 
 28     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
 29     serv_addr.sin_port = htons(port);            // 设置端口 
 30     // 绑定IP和端口
 31     bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
 32 
 33     // 设置同时监听的最大个数
 34     listen(lfd, 36);
 35     printf("Start accept ......\\n");
 36 
 37     struct sockaddr_in client_addr;
 38     socklen_t cli_len = sizeof(client_addr);
 39 
 40     // 创建epoll树根节点
 41     int epfd = epoll_create(2000);
 42     // 初始化epoll树
 43     struct epoll_event ev;
 44     ev.events = EPOLLIN;
 45     ev.data.fd = lfd;
 46     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
 47 
 48     struct epoll_event all[2000];
 49     while(1)
 50     {
 51         // 使用epoll通知内核fd 文件IO检测
 52         int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
 53         printf("================== epoll_wait =============\\n");
 54 
 55         // 遍历all数组中的前ret个元素
 56         for(int i=0; i<ret; ++i)
 57         {
 58             int fd = all[i].data.fd;
 59             // 判断是否有新连接
 60             if(fd == lfd)
 61             {
 62                 // 接受连接请求
 63                 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
 64                 if(cfd == -1)
 65                 {
 66                     perror("accept error");
 67                     exit(1);
 68                 }
 69                 // 将新得到的cfd挂到树上
 70                 struct epoll_event temp;
 71                 temp.events = EPOLLIN;
 72                 temp.data.fd = cfd;
 73                 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
 74                 
 75                 // 打印客户端信息
 76                 char ip[64] = {0};
 77                 printf("New Client IP: %s, Port: %d\\n",
 78                     inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
 79                     ntohs(client_addr.sin_port));
 80                 
 81             }
 82             else
 83             {
 84                 // 处理已经连接的客户端发送过来的数据
 85                 if(!all[i].events & EPOLLIN) 
 86                 {
 87                     continue;
 88                 }
 89 
 90                 // 读数据
 91                 char buf[5] = {0};
 92                 int len = recv(fd, buf, sizeof(buf), 0);
 93                 if(len == -1)
 94                 {
 95                     perror("recv error");
 96                     exit(1);
 97                 }
 98                 else if(len == 0)
 99                 {
100                     printf("client disconnected ....\\n");
101                     // fd从epoll树上删除
102                     ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
103                     if(ret == -1)
104                     {
105                         perror("epoll_ctl - del error");
106                         exit(1);
107                     }
108                     close(fd);
109                     
110                 }
111                 else
112                 {
113                     // printf(" recv buf: %s\\n", buf);
114                     write(STDOUT_FILENO, buf, len);
115                     write(fd, buf, len);
116                 }
117             }
118         }
119     }
120 
121     close(lfd);
122     return 0;
123 }
lt_epoll.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <fcntl.h>
 9 
10 // tcp client
11 int main(int argc, const char* argv[])
12 {
13     if(argc < 2)
14     {
15         printf("eg: ./a.out port\\n");
16         exit(1);
17     }
18     // 创建套接字
19     int fd = socket(AF_INET, SOCK_STREAM, 0);
20     if(fd == -1)
21     {
22         perror("socket error");
23         exit(1);
24     }
25     int port = atoi(argv[1]);
26     // 连接服务器
27     struct sockaddr_in serv_addr;
28     memset(&serv_addr, 0, sizeof(serv_addr));
29     serv_addr.sin_family = AF_INET;
30     serv_addr.sin_port = htons(port);
31     inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
32     int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
33     if(ret == -1)
34     {
35         perror("connect error");
36         exit(1);
37     }
38 
39     // 通信
40     while(1)
41     {
42         // 写数据
43         // 接收键盘输入
44         char buf[512];
45         fgets(buf, sizeof(buf), stdin);
46         // 发送给服务器
47         write(fd, buf, strlen(buf)+1);
48 
49         // 接收服务器端的数据
50         int len = read(fd, buf, sizeof(buf));
51         printf("read buf = %s, len = %d\\n", buf, len);
52     }
53     return 0;
54 }
tcp_client.c

    执行结果:

    server端:

[[email protected] epoll]# ./lt_epoll 8888
Start accept ......
================== epoll_wait =============
New Client IP: 127.0.0.1, Port: 47636
================== epoll_wait =============
00000================== epoll_wait =============
11111================== epoll_wait =============
22222================== epoll_wait =============

    client端:

[[email protected] epoll]# ./client 8888
000001111122222
read buf = 000001111122222
, len = 17

    执行结果分析:可以看出,当客户端发送数据(000001111122222)到server端(接收数据缓冲区内),但是由于server端一次只接受5个字节(00000),因此在接受完5个字节之后,将接收的5个字节数据保存到发送缓冲区。然后程序回到epoll_wait处,此时检测到接收缓冲区还有未接收完的数据程序没有在epoll_wait处阻塞等待。而是继续从上一次缓冲区中读取剩余的数据(11111)及(22222),读取完成之后将所有数据发送给客户端。

文件描述符突破1024限制:

  • select - 突破不了, 需要编译内核
  • poll和epoll可以突破1024限制

      解决办法:

  • 查看受计算机硬件限制的文件描述符上限
  • 通过配置文件修改上限值

五、线程池并发服务器

    1)预先创建阻塞于accept多线程,使用互斥锁上锁保护accept

    2)预先创建多线程,由主线程调用accept

六、UDP服务器

    传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可

或缺的重要通信手段。

    相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和

正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。

    那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议

等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅

助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。

    与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:

    1)服务器应用层设计流量控制,控制发送数据速度。

    2)借助setsockopt函数改变接收缓冲区大小。如:

#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    int n = 220x1024
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

    注意:udp的数据是不安全的, 容易丢包。出现丢包, 不会出现丢部分数据,要丢只会丢全部数据。

    TCP和UDP的使用场景:

     tcp使用场景:

     1)对数据安全性要求高的时候
          a. 登录数据的传输
          c. 文件传输
     2)http协议
          传输层协议 - tcp
     udp使用场景:
     1)效率高 - 实时性要求比较高
          a. 视频聊天
          b. 通话
      2)有实力的大公司
          a. 使用upd
          b. 在应用层自定义协议, 做数据校验

七、C/S模型-UDP

技术图片

UDP处理模型

    由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。

    编译运行server,在两个终端里各开一个client与server交互,看看server是否具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,看此时client还能否和server联系上。和前面TCP程序

的运行结果相比较,体会无连接的含义。

技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 
 9 int main(int argc, const char* argv[])
10 {
11     // 创建套接字
12     int fd = socket(AF_INET, SOCK_DGRAM, 0);
13     if(fd == -1)
14     {
15         perror("socket error");
16         exit(1);
17     }
18     
19     // fd绑定本地的IP和端口
20     struct sockaddr_in serv;
21     memset(&serv, 0, sizeof(serv));
22     serv.sin_family = AF_INET;
23     serv.sin_port = htons(8765);
24     serv.sin_addr.s_addr = htonl(INADDR_ANY);
25     int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
26     if(ret == -1)
27     {
28         perror("bind error");
29         exit(1);
30     }
31 
32     struct sockaddr_in client;
33     socklen_t cli_len = sizeof(client);
34     // 通信
35     char buf[1024] = {0};
36     while(1)
37     {
38         int recvlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &cli_len);
39         if(recvlen == -1)
40         {
41             perror("recvform error");
42             exit(1);
43         }
44         
45         printf("recv buf: %s\\n", buf);
46         char ip[64] = {0};
47         printf("New Client IP: %s, Port: %d\\n",
48             inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
49             ntohs(client.sin_port));
50 
51         // 给客户端发送数据
52         sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
53     }
54     
55     close(fd);
56 
57     return 0;
58 }
udp_server.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 
 9 int main(int argc, const char* argv[])
10 {
11     // create socket
12     int fd = socket(AF_INET, SOCK_DGRAM, 0);
13     if(fd == -1)
14     {
15         perror("socket error");
16         exit(1);
17     }
18 
19     // 初始化服务器的IP和端口
20     struct sockaddr_in serv;
21     memset(&serv, 0, sizeof(serv));
22     serv.sin_family = AF_INET;
23     serv.sin_port = htons(8765);
24     inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
25 
26     // 通信
27     while(1)
28     {
29         char buf[1024] = {0};
30         fgets(buf, sizeof(buf), stdin);
31         // 数据的发送 - server - IP port
32         sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv));
33 
34         // 等待服务器发送数据过来
35         recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
36         printf("recv buf: %s\\n", buf);
37     }
38     
39     close(fd);
40 
41     return 0;
42 }
udp_client.c

八、广播

    广播结构图:

技术图片

      注意:广播只适用于局域网

     代码实现:

技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 
 9 int main(int argc, const char* argv[])
10 {
11     // 创建套接字
12     int fd = socket(AF_INET, SOCK_DGRAM, 0);
13     if(fd == -1)
14     {
15         perror("socket error");
16         exit(1);
17     }
18 
19     // 绑定server的iP和端口
20     struct sockaddr_in serv;
21     memset(&serv, 0, sizeof(serv));
22     serv.sin_family  = AF_INET;
23     serv.sin_port = htons(8787);    // server端口
24     serv.sin_addr.s_addr = htonl(INADDR_ANY);
25     int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
26     if(ret == -1)
27     {
28         perror("bind error");
29         exit(1);
30     }
31 
32     // 初始化客户端地址信息
33     struct sockaddr_in client;
34     memset(&client, 0, sizeof(client));
35     client.sin_family = AF_INET;
36     client.sin_port = htons(6767);  // 客户端要绑定的端口
37     // 使用广播地址给客户端发数据
38     inet_pton(AF_INET, "192.168.30.255", &client.sin_addr.s_addr);
39 
40     // 给服务器开放广播权限
41     int flag = 1;
42     setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));
43 
44     // 通信
45     while(1)
46     {
47         // 一直给客户端发数据
48         static int num = 0;
49         char buf[1024] = {0};
50         sprintf(buf, "hello, udp == %d\\n", num++);
51         int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
52         if(ret == -1)
53         {
54             perror("sendto error");
55             break;
56         }
57         
58         printf("server == send buf: %s\\n", buf);
59 
60         sleep(1);
61     }
62     
63     close(fd);
64 
65     return 0;
66 }
server.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 
 9 int main(int argc, const char* argv[])
10 {
11     int fd = socket(AF_INET, SOCK_DGRAM, 0);
12     if(fd == -1)
13     {
14         perror("socket error");
15         exit(1);
16     }
17 
18     // 绑定iP和端口
19     struct sockaddr_in client;
20     memset(&client, 0, sizeof(client));
21     client.sin_family = AF_INET;
22     client.sin_port = htons(6767);  
23     inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr);
24     int ret  = bind(fd, (struct sockaddr*)&client, sizeof(client));
25     if(ret == -1)
26     {
27         perror("bind error");
28         exit(1);
29     }
30 
31     // 接收数据
32     while(1)
33     {
34         char buf[1024] = {0};
35         int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
36         if(len == -1)
37         {
38             perror("recvfrom error");
39             break;
40         }
41         
42         printf("client == recv buf: %s\\n", buf);
43     }
44 
45     close(fd);
46     
47     return 0;
48 }
client.c

九、多播(组播)

    使用范围:

  • 局域网
  • Internet

    组播结构图:

技术图片

    结构体:

struct ip_mreqn
{
    // 组播组的IP地址.
     struct in_addr imr_multiaddr; 
      // 本地某一网络设备接口的IP地址。
     struct in_addr imr_interface;   
    int   imr_ifindex;   // 网卡编号
};

struct in_addr 
{
    in_addr_t s_addr;
};

     组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量

都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。

224.0.0.0224.0.0.255        为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0224.0.1.255        是公用组播地址,可以用于Internet;欲使用需申请。
224.0.2.0238.255.255.255    为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0239.255.255.255    为本地管理组播地址,仅在特定的本地范围内有效。

     可使用ip ad命令查看网卡编号,如:

[[email protected] ~]# ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:60:ad:b3 brd ff:ff:ff:ff:ff:ff
    inet 192.168.30.137/24 brd 192.168.30.255 scope global noprefixroute dynamic ens33
       valid_lft 1642sec preferred_lft 1642sec
    inet6 fe80::7341:e2f1:498c:8501/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

    if_nametoindex 命令可以根据网卡名,获取网卡序号。

    代码实现:

技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <net/if.h>
 9 
10 int main(int argc, const char* argv[])
11 {
12     // 创建套接字
13     int fd = socket(AF_INET, SOCK_DGRAM, 0);
14     if(fd == -1)
15     {
16         perror("socket error");
17         exit(1);
18     }
19 
20     // 绑定server的iP和端口
21     struct sockaddr_in serv;
22     memset(&serv, 0, sizeof(serv));
23     serv.sin_family  = AF_INET;
24     serv.sin_port = htons(8787);    // server端口
25     serv.sin_addr.s_addr = htonl(INADDR_ANY);
26     int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
27     if(ret == -1)
28     {
29         perror("bind error");
30         exit(1);
31     }
32 
33     // 初始化客户端地址信息
34     struct sockaddr_in client;
35     memset(&client, 0, sizeof(client));
36     client.sin_family = AF_INET;
37     client.sin_port = htons(6666);  // 客户端要绑定的端口
38     // 使用组播地址给客户端发数据
39     inet_pton(AF_INET, "239.0.0.10", &client.sin_addr.s_addr);
40 
41     // 给服务器开放组播权限
42     struct ip_mreqn flag;
43     // init flag
44     inet_pton(AF_INET, "239.0.0.10", &flag.imr_multiaddr.s_addr);   // 组播地址
45     inet_pton(AF_INET, "0.0.0.0", &flag.imr_address.s_addr);    // 本地IP
46     flag.imr_ifindex = if_nametoindex("ens33");
47     setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &flag, sizeof(flag));
48 
49     // 通信
50     while(1)
51     {
52         // 一直给客户端发数据
53         static int num = 0;
54         char buf[1024] = {0};
55         sprintf(buf, "hello, udp == %d\\n", num++);
56         int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
57         if(ret == -1)
58         {
59             perror("sendto error");
60             break;
61         }
62         
63         printf("server == send buf: %s\\n", buf);
64 
65         sleep(1);
66     }
67     
68     close(fd);
69 
70     return 0;
71 }
server.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <net/if.h>
 9 
10 int main(int argc, const char* argv[])
11 {
12     int fd = socket(AF_INET, SOCK_DGRAM, 0);
13     if(fd == -1)
14     {
15         perror("socket error");
16         exit(1);
17     }
18 
19     // 绑定iP和端口
20     struct sockaddr_in client;
21     memset(&client, 0, sizeof(client));
22     client.sin_family = AF_INET;
23     client.sin_port = htons(6666); // ........ 
24     inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr);
25     int ret  = bind(fd, (struct sockaddr*)&client, sizeof(client));
26     if(ret == -1)
27     {
28         perror("bind error");
29         exit(1);
30     }
31 
32     // 加入到组播地址
33     struct ip_mreqn fl;
34     inet_pton(AF_INET, "239.0.0.10", &fl.imr_multiaddr.s_addr);
35     inet_pton(AF_INET, "0.0.0.0", &fl.imr_address.s_addr);
36     fl.imr_ifindex = if_nametoindex("ens33");
37     setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &fl, sizeof(fl));
38 
39     // 接收数据
40     while(1)
41     {
42         char buf[1024] = {0};
43         int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
44         if(len == -1)
45         {
46             perror("recvfrom error");
47             break;
48         }
49         
50         printf("client == recv buf: %s\\n", buf);
51     }
52 
53     close(fd);
54     
55     return 0;
56 }
client.c

十、socket IPC(本地套接字domain)

    通过管道与通过本地套接字实现进程间通信:

技术图片

    socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址

127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因

为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket

也是可靠的,消息既不会丢失也不会顺序错乱。

    UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIXDomain Socket

通讯的。

    使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,

protocol参数仍然指定为0即可。

    UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型

的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

    对比网络套接字地址结构和本地套接字地址结构:

struct sockaddr_in {
    __kernel_sa_family_t sin_family;             /* 
    Address family */      地址结构类型
    __be16 sin_port;                         /* Port 
    number */        端口号
    struct in_addr sin_addr;                    /* 
    Internet address */    IP地址
};

struct sockaddr_un {
    __kernel_sa_family_t sun_family;         /* AF_UNIX */            地址结构类型
    char sun_path[UNIX_PATH_MAX];         /* pathname */        socket文件名(含路径)
};

    以下程序将UNIX Domain socket绑定到一个地址。

size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->MEMBER)

    代码实现:

技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <sys/un.h>
 9 
10 int main(int argc, const char* argv[])
11 {
12     int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
13     if(lfd == -1)
14     {
15         perror("socket error");
16         exit(1);
17     }
18 
19     // 如果套接字文件存在, 删除套接字文件
20     unlink("server.sock");
21 
22     // 绑定
23     struct sockaddr_un serv;
24     serv.sun_family = AF_LOCAL;
25     strcpy(serv.sun_path, "server.sock");
26     int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
27     if(ret == -1)
28     {
29         perror("bind error");
30         exit(1);
31     }
32      
33     // 监听
34     ret = listen(lfd, 36);
35     if(ret == -1)
36     {
37         perror("listen error");
38         exit(1);
39     }
40 
41     // 等待接收连接请求
42     struct sockaddr_un client;
43     socklen_t len = sizeof(client);
44     int cfd = accept(lfd, (struct sockaddr*)&client, &len);
45     if(cfd == -1)
46     {
47         perror("accept error");
48         exit(1);
49     }
50     printf("======client bind file: %s\\n", client.sun_path);
51      
52     // 通信
53     while(1)
54     {
55         char buf[1024] = {0};
56         int recvlen = recv(cfd, buf, sizeof(buf), 0);
57         if(recvlen == -1)
58         {
59             perror("recv error");
60             exit(1);
61         }
62         else if(recvlen == 0)
63         {
64             printf("clietn disconnect ....\\n");
65             close(cfd);
66             break;
67         }
68         else
69         {
70             printf("recv buf: %s\\n", buf);
71             send(cfd, buf, recvlen, 0);
72         }
73     }
74     close(cfd);
75     close(lfd);
76     
77     return 0;
78 }
server.c
技术图片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <sys/un.h>
 9 
10 int main(int argc, const char* argv[])
11 {
12     int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
13     if(fd == -1)
14     {
15         perror("socket error");
16         exit(1);
17     }
18 
19     unlink("client.sock");
20 
21     // ================================
22     // 给客户端绑定一个套接字文件
23     struct sockaddr_un client;
24     client.sun_family = AF_LOCAL;
25     strcpy(client.sun_path, "client.sock");
26     int ret = bind(fd, (struct sockaddr*)&client, sizeof(client));
27     if(ret == -1)
28     {
29         perror("bind error");
30         exit(1);
31     }
32 
33     // 初始化server信息
34     struct sockaddr_un serv;
35     serv.sun_family = AF_LOCAL;
36     strcpy(serv.sun_path, "server.sock");
37 
38     // 连接服务器
39     connect(fd, (struct sockaddr*)&serv, sizeof(serv));
40 
41     // 通信
42     while(1)
43     {
44         char buf[1024] = {0};
45         fgets(buf, sizeof(buf), stdin);
46         send(fd, buf, strlen(buf)+1, 0);
47 
48         // 接收数据
49         recv(fd, buf, sizeof(buf), 0);
50         printf("recv buf: %s\\n", buf);
51     }
52 
53     close(fd);
54 
55     return 0;
56 }
57     
client.c

十一、其他常用函数

1. 名字与地址转换

    gethostbyname根据给定的主机名,获取主机信息。

    过时,仅用于IPv4,且线程不安全。

技术图片
 1 #include <stdio.h>
 2 #include <netdb.h>
 3 #include <arpa/inet.h>
 4 
 5 extern int h_errno;
 6 
 7 int main(int argc, char *argv[])
 8 {
 9     struct hostent *host;
10     char str[128];
11     host = gethostbyname(argv[1]);
12     printf("%s\\n", host->h_name);
13 
14     while (*(host->h_aliases) != NULL)
15         printf("%s\\n", *host->h_aliases++);
16 
17     switch (host->h_addrtype) {
18         case AF_INET:
19             while (*(host->h_addr_list) != NULL)
20             printf("%s\\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
21         break;
22         default:
23             printf("unknown address type\\n");
24             break;
25     }
26     return 0;
27 }
示例

    gethostbyaddr函数。

    此函数只能获取域名解析服务器的url和/etc/hosts里登记的IP对应的域名。

技术图片
 1 #include <stdio.h>
 2 #include <netdb.h>
 3 #include <arpa/inet.h>
 4 
 5 extern int h_errno;
 6 
 7 int main(int argc, char *argv[])
 8 {
 9     struct hostent *host;
10     char str[128];
11     struct in_addr addr;
12 
13     inet_pton(AF_INET, argv[1], &addr);
14     host = gethostbyaddr((char *)&addr, 4, AF_INET);
15     printf("%s\\n", host->h_name);
16 
17     while (*(host->h_aliases) != NULL)
18         printf("%s\\n", *host->h_aliases++);
19     switch (host->h_addrtype) {
20         case AF_INET:
21             while (*(host->h_addr_list) != NULL)
22             printf("%s\\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
23             break;
24         default:
25             printf("unknown address type\\n");
26             break;
27     }
28     return 0;
29 }
示例

    getservbyname

    getservbyport

    根据服务程序名字或端口号获取信息。使用频率不高。

    getaddrinfo

    getnameinfo

    freeaddrinfo

    可同时处理IPv4和IPv6,线程安全的。

2. 套接口和地址关联

    getsockname

    根据accpet返回的sockfd,得到临时端口号

    getpeername

    根据accpet返回的sockfd,得到远端链接的端口号,在exec后可以获取客户端信息。

以上是关于Linux 高并发服务器的主要内容,如果未能解决你的问题,请参考以下文章

[Linux 高并发服务器]制作静态库与动态库

[Linux 高并发服务器] exec函数族

Linux中epoll+线程池实现高并发

[Linux 高并发服务器]Makefile

[Linux 高并发服务器]文件IO

[Linux 高并发服务器]线程