• Linux C编程之十八 高并发服务器


    高并发服务器

    一、多进程并发服务器

        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  == '
    ')
    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 ...
    ");
     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.
    ");
     70                     break;
     71                 }
     72                 printf("received from %s at PORT %d
    ",
     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.
    ");
    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  == '
    ')
    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...
    ", ts->connfd);
    33             break;                                              //跳出循环,关闭cfd
    34         }
    35         printf("received from %s at PORT %d
    ",
    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 ...
    ");
    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.
    ");
    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
    ");
     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 ......
    ");
     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
    ", 
     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("客户端已经断开了连接
    ");
     91                     close(i);
     92                     // 从读集合中删除
     93                     FD_CLR(i, &reads);
     94                 }
     95                 else
     96                 {
     97                     printf("recv buf: %s
    ", 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 ......
    ");
     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
    ", buf);
     92                     for(int j=0; j<len; ++j)
     93                     {
     94                         buf[j] = toupper(buf[j]);
     95                     }
     96                     printf("--buf toupper: %s
    ", 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 ......
    ");
     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
    ",
     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("对方已经关闭了连接。。。。。。
    ");
    120                 }
    121                 else
    122                 {
    123                     printf("read buf = %s
    ", buf);
    124                     for(int j=0; j<len; ++j)
    125                     {
    126                         buf[j] = toupper(buf[j]);
    127                     }
    128                     printf("--buf toupper: %s
    ", 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 ......
    ");
     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("============
    ");
     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("客户端已经主动断开连接。。。
    ");
     99                 }
    100                 else
    101                 {
    102                     printf("recv buf = %s
    ", buf);
    103                     for(int k=0; k<len; ++k)
    104                     {
    105                         buf[k] = toupper(buf[k]);
    106                     }
    107                     printf("buf toupper: %s
    ", 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
    ");
     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 ......
    ");
     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
    ",
     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 ....
    ");
    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
    ", 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.
    ");
    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] = '
    ';
    24             ch++;
    25 
    26             for (; i < MAXLINE; i++)
    27                 buf[i] = ch;
    28             buf[i-1] = '
    ';
    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
    ", 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
    ");
     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 ......
    ");
     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 =============
    ");
     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
    ",
     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 ....
    ");
    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
    ", 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
    ");
    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
    ", 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
    ");
     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 ......
    ");
     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 =============
    ");
     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
    ",
     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("客户端断开了连接
    ");
    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("缓冲区数据已经读完
    ");
    126                     }
    127                     else
    128                     {
    129                         printf("recv error----
    ");
    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
    ");
    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
    ", 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端会全部从缓冲区读取并发送给客户端(包括客户端发送的回车(' '))。

    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
    ");
     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 ......
    ");
     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 =============
    ");
     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
    ",
     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 ....
    ");
    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
    ", 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
    ");
    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
    ", 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限制

          解决办法:

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

    五、线程池并发服务器

        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
    ", buf);
    46         char ip[64] = {0};
    47         printf("New Client IP: %s, Port: %d
    ",
    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
    ", 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
    ", 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
    ", 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
    ", 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命令查看网卡编号,如:

    [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
    ", 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
    ", 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
    ", 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
    ", 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 ....
    ");
    65             close(cfd);
    66             break;
    67         }
    68         else
    69         {
    70             printf("recv buf: %s
    ", 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
    ", 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
    ", host->h_name);
    13 
    14     while (*(host->h_aliases) != NULL)
    15         printf("%s
    ", *host->h_aliases++);
    16 
    17     switch (host->h_addrtype) {
    18         case AF_INET:
    19             while (*(host->h_addr_list) != NULL)
    20             printf("%s
    ", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
    21         break;
    22         default:
    23             printf("unknown address type
    ");
    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
    ", host->h_name);
    16 
    17     while (*(host->h_aliases) != NULL)
    18         printf("%s
    ", *host->h_aliases++);
    19     switch (host->h_addrtype) {
    20         case AF_INET:
    21             while (*(host->h_addr_list) != NULL)
    22             printf("%s
    ", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
    23             break;
    24         default:
    25             printf("unknown address type
    ");
    26             break;
    27     }
    28     return 0;
    29 }
    示例

        getservbyname

        getservbyport

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

        getaddrinfo

        getnameinfo

        freeaddrinfo

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

    2. 套接口和地址关联

        getsockname

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

        getpeername

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

  • 相关阅读:
    Nginx+uWsgi+Django+Python+MongoDB+mySQL服务器搭建
    Scott Guthrie's Blog on ASP.NET
    NPOI 读写excel
    用C#开发了一个Android 浏览器APP
    Windows 8 应用开发技术资源
    微软发布Sample Browser for Windows 8版:5000示例代码,"触手可及"
    依赖注入
    DIY 一套正版、免费、强大的 Visual Studio 2012 IDE
    基于JQuery EasyUI、Web Api的 ASP.NET MVC 代码生成插件
    深度剖析Byteart Retail案例:AOP 异常处理与缓存
  • 原文地址:https://www.cnblogs.com/xuejiale/p/10849095.html
Copyright © 2020-2023  润新知