Linux C編程之十八 高併發服務器

高併發服務器

1、多進程併發服務器

    1. 實現示意圖linux

    2. 使用多進程併發服務器時要考慮如下幾點:編程

  • 父進程最大文件描述個數(父進程中須要close關閉accept返回的新文件描述符)
  • 系統內建立進程個數(與內存大小相關)
  • 進程建立過可能是否下降總體服務性能(進程調度)

    3. 使用多進程的方式, 解決服務器處理多鏈接的問題:centos

    (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

2、多線程併發服務器

    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

3、多路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 調用次數越多, 系統的開銷越大

4、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端:

[root@centos epoll]# ./client 6666
000001111122222

read buf = 000001111122222
, len = 5


read buf = 11111, len = 5


read buf = 22222, len = 5

    server端:

[root@centos 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端:

[root@centos 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端:

[root@centos 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端:

[root@centos 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端:

[root@centos 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限制

      解決辦法:

  • 查看受計算機硬件限制的文件描述符上限
  • 經過配置文件修改上限值

5、線程池併發服務器

    1)預先建立阻塞於accept多線程,使用互斥鎖上鎖保護accept

    2)預先建立多線程,由主線程調用accept

6、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. 在應用層自定義協議, 作數據校驗

7、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

8、廣播

    廣播結構圖:

      注意:廣播只適用於局域網

     代碼實現:

 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

9、多播(組播)

    使用範圍:

  • 局域網
  • 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命令查看網卡編號,如:

[root@centos ~]# 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

10、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

11、其餘經常使用函數

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後能夠獲取客戶端信息。

相關文章
相關標籤/搜索