Linux 高并发服务器
Posted xuejiale
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 高并发服务器相关的知识,希望对你有一定的参考价值。
高并发服务器
一、多进程并发服务器
1. 实现示意图
2. 使用多进程并发服务器时要考虑以下几点:
- 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
- 系统内创建进程个数(与内存大小相关)
- 进程创建过多是否降低整体服务性能(进程调度)
3. 使用多进程的方式, 解决服务器处理多连接的问题:
(1)共享
- 读时共享, 写时复制
- 文件描述符
- 内存映射区 -- mmap
(2)父进程 的角色是什么?
等待接受客户端连接 -- accept
有链接:
- 创建一个子进程 fork()
- 将通信的文件描述符关闭
(3)子进程的角色是什么?
1)通信
- 使用accept返回值 - fd
2)关掉监听的文件描述符
- 浪费资源
(4)创建的进程的个数有限制吗?
- 受硬件限制
- 文件描述符默认也是有上限的1024
(5)子进程资源回收
1)wait/waitpid
2)使用信号回收
- 信号捕捉
signal
sigaction - 推荐
- 捕捉信号: SIGCHLD
代码实现:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/socket.h> 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) { 19 if ((errno == ECONNABORTED) || (errno == EINTR)) 20 goto again; 21 else 22 perr_exit("accept error"); 23 } 24 return n; 25 } 26 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 28 { 29 int n; 30 31 if ((n = bind(fd, sa, salen)) < 0) 32 perr_exit("bind error"); 33 34 return n; 35 } 36 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 38 { 39 int n; 40 41 if ((n = connect(fd, sa, salen)) < 0) 42 perr_exit("connect error"); 43 44 return n; 45 } 46 47 int Listen(int fd, int backlog) 48 { 49 int n; 50 51 if ((n = listen(fd, backlog)) < 0) 52 perr_exit("listen error"); 53 54 return n; 55 } 56 57 int Socket(int family, int type, int protocol) 58 { 59 int n; 60 61 if ((n = socket(family, type, protocol)) < 0) 62 perr_exit("socket error"); 63 64 return n; 65 } 66 67 ssize_t Read(int fd, void *ptr, size_t nbytes) 68 { 69 ssize_t n; 70 71 again: 72 if ( (n = read(fd, ptr, nbytes)) == -1) { 73 if (errno == EINTR) 74 goto again; 75 else 76 return -1; 77 } 78 return n; 79 } 80 81 ssize_t Write(int fd, const void *ptr, size_t nbytes) 82 { 83 ssize_t n; 84 85 again: 86 if ( (n = write(fd, ptr, nbytes)) == -1) { 87 if (errno == EINTR) 88 goto again; 89 else 90 return -1; 91 } 92 return n; 93 } 94 95 int Close(int fd) 96 { 97 int n; 98 if ((n = close(fd)) == -1) 99 perr_exit("close error"); 100 101 return n; 102 } 103 104 /*参三: 应该读取的字节数*/ 105 ssize_t Readn(int fd, void *vptr, size_t n) 106 { 107 size_t nleft; //usigned int 剩余未读取的字节数 108 ssize_t nread; //int 实际读到的字节数 109 char *ptr; 110 111 ptr = vptr; 112 nleft = n; 113 114 while (nleft > 0) { 115 if ((nread = read(fd, ptr, nleft)) < 0) { 116 if (errno == EINTR) 117 nread = 0; 118 else 119 return -1; 120 } else if (nread == 0) 121 break; 122 123 nleft -= nread; 124 ptr += nread; 125 } 126 return n - nleft; 127 } 128 129 ssize_t Writen(int fd, const void *vptr, size_t n) 130 { 131 size_t nleft; 132 ssize_t nwritten; 133 const char *ptr; 134 135 ptr = vptr; 136 nleft = n; 137 while (nleft > 0) { 138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 139 if (nwritten < 0 && errno == EINTR) 140 nwritten = 0; 141 else 142 return -1; 143 } 144 145 nleft -= nwritten; 146 ptr += nwritten; 147 } 148 return n; 149 } 150 151 static ssize_t my_read(int fd, char *ptr) 152 { 153 static int read_cnt; 154 static char *read_ptr; 155 static char read_buf[100]; 156 157 if (read_cnt <= 0) { 158 again: 159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 160 if (errno == EINTR) 161 goto again; 162 return -1; 163 } else if (read_cnt == 0) 164 return 0; 165 read_ptr = read_buf; 166 } 167 read_cnt--; 168 *ptr = *read_ptr++; 169 170 return 1; 171 } 172 173 ssize_t Readline(int fd, void *vptr, size_t maxlen) 174 { 175 ssize_t n, rc; 176 char c, *ptr; 177 178 ptr = vptr; 179 for (n = 1; n < maxlen; n++) { 180 if ( (rc = my_read(fd, &c)) == 1) { 181 *ptr++ = c; 182 if (c == ‘\\n‘) 183 break; 184 } else if (rc == 0) { 185 *ptr = 0; 186 return n - 1; 187 } else 188 return -1; 189 } 190 *ptr = 0; 191 192 return n; 193 }
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
1 #include <stdio.h> 2 #include <string.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <signal.h> 6 #include <sys/wait.h> 7 #include <ctype.h> 8 #include <unistd.h> 9 10 #include "wrap.h" 11 12 #define MAXLINE 8192 13 #define SERV_PORT 8000 14 15 void do_sigchild(int num) 16 { 17 while (waitpid(0, NULL, WNOHANG) > 0); 18 } 19 20 int main(void) 21 { 22 struct sockaddr_in servaddr, cliaddr; 23 socklen_t cliaddr_len; 24 int listenfd, connfd; 25 char buf[MAXLINE]; 26 char str[INET_ADDRSTRLEN]; 27 int i, n; 28 pid_t pid; 29 30 //临时屏蔽sigchld信号 31 sigset_t myset; 32 sigemptyset(&myset); 33 sigaddset(&myset, SIGCHLD); 34 // 自定义信号集 -》 内核阻塞信号集 35 sigprocmask(SIG_BLOCK, &myset, NULL); 36 37 38 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 39 40 int opt = 1; 41 // 设置端口复用 42 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 43 44 bzero(&servaddr, sizeof(servaddr)); 45 servaddr.sin_family = AF_INET; 46 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 47 servaddr.sin_port = htons(SERV_PORT); 48 49 Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 50 51 Listen(listenfd, 20); 52 53 printf("Accepting connections ...\\n"); 54 while (1) 55 { 56 cliaddr_len = sizeof(cliaddr); 57 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); 58 59 // 有新的连接则创建一个进程 60 pid = fork(); 61 if (pid == 0) 62 { 63 Close(listenfd); 64 while (1) 65 { 66 n = Read(connfd, buf, MAXLINE); 67 if (n == 0) 68 { 69 printf("the other side has been closed.\\n"); 70 break; 71 } 72 printf("received from %s at PORT %d\\n", 73 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 74 ntohs(cliaddr.sin_port)); 75 76 for (i = 0; i < n; i++) 77 buf[i] = toupper(buf[i]); 78 79 Write(STDOUT_FILENO, buf, n); 80 Write(connfd, buf, n); 81 } 82 Close(connfd); 83 return 0; 84 } 85 else if (pid > 0) 86 { 87 struct sigaction act; 88 act.sa_flags = 0; 89 act.sa_handler = do_sigchild; 90 sigemptyset(&act.sa_mask); 91 sigaction(SIGCHLD, &act, NULL); 92 // 解除对sigchld信号的屏蔽 93 sigprocmask(SIG_UNBLOCK, &myset, NULL); 94 95 Close(connfd); 96 } 97 else 98 { 99 perr_exit("fork"); 100 } 101 } 102 return 0; 103 }
1 /* client.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 8 #include "wrap.h" 9 10 #define MAXLINE 8192 11 #define SERV_PORT 8000 12 13 int main(int argc, char *argv[]) 14 { 15 struct sockaddr_in servaddr; 16 char buf[MAXLINE]; 17 int sockfd, n; 18 19 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 20 21 bzero(&servaddr, sizeof(servaddr)); 22 servaddr.sin_family = AF_INET; 23 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 24 servaddr.sin_port = htons(SERV_PORT); 25 26 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 27 28 while (fgets(buf, MAXLINE, stdin) != NULL) 29 { 30 Write(sockfd, buf, strlen(buf)); 31 n = Read(sockfd, buf, MAXLINE); 32 if (n == 0) 33 { 34 printf("the other side has been closed.\\n"); 35 break; 36 } 37 else 38 Write(STDOUT_FILENO, buf, n); 39 } 40 41 Close(sockfd); 42 43 return 0; 44 }
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)
二、多线程并发服务器
1. 实现示意图
2. 使用线程模型开发服务器时需考虑以下问题:
- 调整进程内最大文件描述符上限
- 线程如有共享数据,考虑线程同步
- 服务于客户端线程退出时,退出处理。(退出值,分离态)
- 系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
3. 线程共享:
- 全局数据区
- 堆区
- 一块有效内存的地址
代码实现:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/socket.h> 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) { 19 if ((errno == ECONNABORTED) || (errno == EINTR)) 20 goto again; 21 else 22 perr_exit("accept error"); 23 } 24 return n; 25 } 26 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 28 { 29 int n; 30 31 if ((n = bind(fd, sa, salen)) < 0) 32 perr_exit("bind error"); 33 34 return n; 35 } 36 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 38 { 39 int n; 40 41 if ((n = connect(fd, sa, salen)) < 0) 42 perr_exit("connect error"); 43 44 return n; 45 } 46 47 int Listen(int fd, int backlog) 48 { 49 int n; 50 51 if ((n = listen(fd, backlog)) < 0) 52 perr_exit("listen error"); 53 54 return n; 55 } 56 57 int Socket(int family, int type, int protocol) 58 { 59 int n; 60 61 if ((n = socket(family, type, protocol)) < 0) 62 perr_exit("socket error"); 63 64 return n; 65 } 66 67 ssize_t Read(int fd, void *ptr, size_t nbytes) 68 { 69 ssize_t n; 70 71 again: 72 if ( (n = read(fd, ptr, nbytes)) == -1) { 73 if (errno == EINTR) 74 goto again; 75 else 76 return -1; 77 } 78 return n; 79 } 80 81 ssize_t Write(int fd, const void *ptr, size_t nbytes) 82 { 83 ssize_t n; 84 85 again: 86 if ( (n = write(fd, ptr, nbytes)) == -1) { 87 if (errno == EINTR) 88 goto again; 89 else 90 return -1; 91 } 92 return n; 93 } 94 95 int Close(int fd) 96 { 97 int n; 98 if ((n = close(fd)) == -1) 99 perr_exit("close error"); 100 101 return n; 102 } 103 104 /*参三: 应该读取的字节数*/ 105 ssize_t Readn(int fd, void *vptr, size_t n) 106 { 107 size_t nleft; //usigned int 剩余未读取的字节数 108 ssize_t nread; //int 实际读到的字节数 109 char *ptr; 110 111 ptr = vptr; 112 nleft = n; 113 114 while (nleft > 0) { 115 if ((nread = read(fd, ptr, nleft)) < 0) { 116 if (errno == EINTR) 117 nread = 0; 118 else 119 return -1; 120 } else if (nread == 0) 121 break; 122 123 nleft -= nread; 124 ptr += nread; 125 } 126 return n - nleft; 127 } 128 129 ssize_t Writen(int fd, const void *vptr, size_t n) 130 { 131 size_t nleft; 132 ssize_t nwritten; 133 const char *ptr; 134 135 ptr = vptr; 136 nleft = n; 137 while (nleft > 0) { 138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 139 if (nwritten < 0 && errno == EINTR) 140 nwritten = 0; 141 else 142 return -1; 143 } 144 145 nleft -= nwritten; 146 ptr += nwritten; 147 } 148 return n; 149 } 150 151 static ssize_t my_read(int fd, char *ptr) 152 { 153 static int read_cnt; 154 static char *read_ptr; 155 static char read_buf[100]; 156 157 if (read_cnt <= 0) { 158 again: 159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 160 if (errno == EINTR) 161 goto again; 162 return -1; 163 } else if (read_cnt == 0) 164 return 0; 165 read_ptr = read_buf; 166 } 167 read_cnt--; 168 *ptr = *read_ptr++; 169 170 return 1; 171 } 172 173 ssize_t Readline(int fd, void *vptr, size_t maxlen) 174 { 175 ssize_t n, rc; 176 char c, *ptr; 177 178 ptr = vptr; 179 for (n = 1; n < maxlen; n++) { 180 if ( (rc = my_read(fd, &c)) == 1) { 181 *ptr++ = c; 182 if (c == ‘\\n‘) 183 break; 184 } else if (rc == 0) { 185 *ptr = 0; 186 return n - 1; 187 } else 188 return -1; 189 } 190 *ptr = 0; 191 192 return n; 193 }
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
1 #include <stdio.h> 2 #include <string.h> 3 #include <arpa/inet.h> 4 #include <pthread.h> 5 #include <ctype.h> 6 #include <unistd.h> 7 #include <fcntl.h> 8 9 #include "wrap.h" 10 11 #define MAXLINE 8192 12 #define SERV_PORT 8000 13 14 struct s_info 15 { //定义一个结构体, 将地址结构跟cfd捆绑 16 struct sockaddr_in cliaddr; 17 int connfd; 18 }; 19 20 void *do_work(void *arg) 21 { 22 int n,i; 23 struct s_info *ts = (struct s_info*)arg; 24 char buf[MAXLINE]; 25 char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看 26 27 while (1) 28 { 29 n = Read(ts->connfd, buf, MAXLINE); //读客户端 30 if (n == 0) 31 { 32 printf("the client %d closed...\\n", ts->connfd); 33 break; //跳出循环,关闭cfd 34 } 35 printf("received from %s at PORT %d\\n", 36 inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), 37 ntohs((*ts).cliaddr.sin_port)); //打印客户端信息(IP/PORT) 38 39 for (i = 0; i < n; i++) 40 { 41 buf[i] = toupper(buf[i]); //小写-->大写 42 } 43 44 Write(STDOUT_FILENO, buf, n); //写出至屏幕 45 Write(ts->connfd, buf, n); //回写给客户端 46 } 47 Close(ts->connfd); 48 49 return NULL; 50 } 51 52 int main(void) 53 { 54 struct sockaddr_in servaddr, cliaddr; 55 socklen_t cliaddr_len; 56 int listenfd, connfd; 57 pthread_t tid; 58 struct s_info ts[256]; //根据最大线程数创建结构体数组. 59 int i = 0; 60 61 listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfd 62 63 bzero(&servaddr, sizeof(servaddr)); //地址结构清零 64 servaddr.sin_family = AF_INET; 65 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP 66 servaddr.sin_port = htons(SERV_PORT); //指定端口号 8000 67 68 Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定 69 70 Listen(listenfd, 128); //设置同一时刻链接服务器上限数 71 72 printf("Accepting client connect ...\\n"); 73 74 while (1) 75 { 76 cliaddr_len = sizeof(cliaddr); 77 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求 78 ts[i].cliaddr = cliaddr; 79 ts[i].connfd = connfd; 80 81 pthread_create(&tid, NULL, do_work, (void*)&ts[i]); 82 pthread_detach(tid); //子线程分离,防止僵线程产生. 83 i++; 84 if(i == 256) 85 { 86 break; 87 } 88 } 89 90 return 0; 91 }
1 /* client.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include "wrap.h" 8 9 #define MAXLINE 80 10 #define SERV_PORT 8000 11 12 int main(int argc, char *argv[]) 13 { 14 struct sockaddr_in servaddr; 15 char buf[MAXLINE]; 16 int sockfd, n; 17 18 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 19 20 bzero(&servaddr, sizeof(servaddr)); 21 servaddr.sin_family = AF_INET; 22 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr); 23 servaddr.sin_port = htons(SERV_PORT); 24 25 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 26 27 while (fgets(buf, MAXLINE, stdin) != NULL) 28 { 29 Write(sockfd, buf, strlen(buf)); 30 n = Read(sockfd, buf, MAXLINE); 31 if (n == 0) 32 printf("the other side has been closed.\\n"); 33 else 34 Write(STDOUT_FILENO, buf, n); 35 } 36 37 Close(sockfd); 38 39 return 0; 40 }
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)
三、多路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 }
(2)使用select函的优缺点:
- 优点:
跨平台
- 缺点:
a. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
b. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
c. select支持的文件描述符数量太小了,默认是1024。
为什么是1024?
首先,看下内核中对fd_set的定义: typedef struct { unsigned long fds_bits[__FDSET_LONGS]; } __kernel_fd_set; typedef __kernel_fd_set fd_set; 其中有关的常量定义为: #undef __NFDBITS #define __NFDBITS (8 * sizeof(unsigned long)) #undef __FD_SETSIZE #define __FD_SETSIZE 1024 #undef __FDSET_LONGS #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) 即__NFDBITS为8*4=32,__FD_SETSIZE为1024,那么,__FDSET_LONGS为1024/32=32,因此,fd_set实际上是32个无符号长整形,也就是1024位
(2)select函数及示例
#include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态 readfds: 监控有读数据到达文件描述符集合,传入传出参数 writefds: 监控写数据到达文件描述符集合,传入传出参数 exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数 timeout: 定时阻塞监控时间,3种情况 1.NULL,永远等下去 2.设置timeval,等待固定时间 3.设置timeval里时间均为0,检查描述字后立即返回,轮询 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0 int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1 void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1 void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
select示例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 10 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\\n"); 16 exit(1); 17 } 18 struct sockaddr_in serv_addr; 19 socklen_t serv_len = sizeof(serv_addr); 20 int port = atoi(argv[1]); 21 22 // 创建套接字 23 int lfd = socket(AF_INET, SOCK_STREAM, 0); 24 // 初始化服务器 sockaddr_in 25 memset(&serv_addr, 0, serv_len); 26 serv_addr.sin_family = AF_INET; // 地址族 27 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 28 serv_addr.sin_port = htons(port); // 设置端口 29 // 绑定IP和端口 30 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 31 32 // 设置同时监听的最大个数 33 listen(lfd, 36); 34 printf("Start accept ......\\n"); 35 36 struct sockaddr_in client_addr; 37 socklen_t cli_len = sizeof(client_addr); 38 39 // 最大的文件描述符 40 int maxfd = lfd; 41 // 文件描述符读集合 42 fd_set reads, temp; 43 // init 44 FD_ZERO(&reads); 45 FD_SET(lfd, &reads); 46 47 while(1) 48 { 49 // 委托内核做IO检测 50 temp = reads; 51 int ret = select(maxfd+1, &temp, NULL, NULL, NULL); 52 if(ret == -1) 53 { 54 perror("select error"); 55 exit(1); 56 } 57 // 客户端发起了新的连接 58 if(FD_ISSET(lfd, &temp)) 59 { 60 // 接受连接请求 - accept不阻塞 61 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 62 if(cfd == -1) 63 { 64 perror("accept error"); 65 exit(1); 66 } 67 char ip[64]; 68 printf("new client IP: %s, Port: %d\\n", 69 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 70 ntohs(client_addr.sin_port)); 71 // 将cfd加入到待检测的读集合中 - 下一次就可以检测到了 72 FD_SET(cfd, &reads); 73 // 更新最大的文件描述符 74 maxfd = maxfd < cfd ? cfd : maxfd; 75 } 76 // 已经连接的客户端有数据到达 77 for(int i=lfd+1; i<=maxfd; ++i) 78 { 79 if(FD_ISSET(i, &temp)) 80 { 81 char buf[1024] = {0}; 82 int len = recv(i, buf, sizeof(buf), 0); 83 if(len == -1) 84 { 85 perror("recv error"); 86 exit(1); 87 } 88 else if(len == 0) 89 { 90 printf("客户端已经断开了连接\\n"); 91 close(i); 92 // 从读集合中删除 93 FD_CLR(i, &reads); 94 } 95 else 96 { 97 printf("recv buf: %s\\n", buf); 98 send(i, buf, strlen(buf)+1, 0); 99 } 100 } 101 } 102 } 103 104 close(lfd); 105 return 0; 106 }
select示例2:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/select.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 创建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服务器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 设置端口 26 serv_len = sizeof(serv_addr); 27 // 绑定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 设置同时监听的最大个数 31 listen(lfd, 36); 32 printf("Start accept ......\\n"); 33 34 int ret; 35 int maxfd = lfd; 36 // reads 实时更新,temps 内核检测 37 fd_set reads, temps; 38 39 FD_ZERO(&reads); 40 FD_SET(lfd, &reads); 41 42 while(1) 43 { 44 temps = reads; 45 ret = select(maxfd+1, &temps, NULL, NULL, NULL); 46 if(ret == -1) 47 { 48 perror("select error"); 49 exit(1); 50 } 51 52 53 // 判断是否有新连接 54 if(FD_ISSET(lfd, &temps)) 55 { 56 // 接受连接请求 57 clien_len = sizeof(clien_len); 58 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 59 60 // 文件描述符放入检测集合 61 FD_SET(cfd, &reads); 62 // 更新最大文件描述符 63 maxfd = maxfd < cfd ? cfd : maxfd; 64 } 65 66 // 遍历检测的文件描述符是否有读操作 67 for(int i=lfd+1; i<=maxfd; ++i) 68 { 69 if(FD_ISSET(i, &temps)) 70 { 71 // 读数据 72 char buf[1024] = {0}; 73 int len = read(i, buf, sizeof(buf)); 74 if(len == -1) 75 { 76 perror("read error"); 77 exit(1); 78 } 79 else if(len == 0) 80 { 81 // 对方关闭了连接 82 FD_CLR(i, &reads); 83 close(i); 84 if(maxfd == i) 85 { 86 maxfd--; 87 } 88 } 89 else 90 { 91 printf("read buf = %s\\n", buf); 92 for(int j=0; j<len; ++j) 93 { 94 buf[j] = toupper(buf[j]); 95 } 96 printf("--buf toupper: %s\\n", buf); 97 write(i, buf, strlen(buf)+1); 98 } 99 } 100 } 101 } 102 103 close(lfd); 104 return 0; 105 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/select.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 创建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服务器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 设置端口 26 serv_len = sizeof(serv_addr); 27 // 绑定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 设置同时监听的最大个数 31 listen(lfd, 36); 32 printf("Start accept ......\\n"); 33 34 int ret; 35 int maxfd = lfd; 36 // reads 实时更新,temps 内核检测 37 fd_set reads, temps; 38 39 /*===============================================================*/ 40 // 记录要检测的文件描述符的数组 41 int allfd[FD_SETSIZE]; // 1024 42 // 记录数组中最后一个元素的下标 43 int last_index = 0; 44 // 初始化数组 45 for(int i=0; i<FD_SETSIZE; ++i) 46 { 47 allfd[i] = -1; // 无效文件描述符值 48 } 49 allfd[0] = lfd; // 监听的文件描述符添加到数组中 50 /*===============================================================*/ 51 52 // 初始化监听的读集合 53 FD_ZERO(&reads); 54 FD_SET(lfd, &reads); 55 56 while(1) 57 { 58 // 每次都需要更新,否则select不会重新检测 59 temps = reads; 60 ret = select(maxfd+1, &temps, NULL, NULL, NULL); 61 if(ret == -1) 62 { 63 perror("select error"); 64 exit(1); 65 } 66 67 int i = 0; 68 char bufip[64]; 69 // 判断是否有新连接 70 if(FD_ISSET(lfd, &temps)) 71 { 72 // 接受连接请求 73 clien_len = sizeof(clien_len); 74 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 75 printf("client ip: %s, port: %d\\n", 76 inet_ntop(AF_INET, &clien_addr.sin_addr.s_addr, bufip, sizeof(bufip)), 77 ntohs(clien_addr.sin_port)); 78 79 // 文件描述符放入检测集合 80 FD_SET(cfd, &reads); 81 // 更新最大文件描述符 82 maxfd = maxfd < cfd ? cfd : maxfd; 83 // cfd添加到检测数组中 84 for(i=0; i<FD_SETSIZE; ++i) 85 { 86 if(allfd[i] == -1) 87 { 88 allfd[i] = cfd; 89 break; 90 } 91 } 92 // 更新数组最后一个有效值下标 93 last_index = last_index < i ? i : last_index; 94 } 95 96 // 遍历检测的文件描述符是否有读操作 97 for(i=lfd+1; i<=maxfd; ++i) 98 { 99 if(FD_ISSET(i, &temps)) 100 { 101 // 读数据 102 char buf[1024] = {0}; 103 int len = read(i, buf, sizeof(buf)); 104 if(len == -1) 105 { 106 perror("read error"); 107 exit(1); 108 } 109 else if(len == 0) 110 { 111 // 对方关闭了连接 112 FD_CLR(i, &reads); 113 close(i); 114 if(maxfd == i) 115 { 116 maxfd--; 117 } 118 allfd[i] = -1; 119 printf("对方已经关闭了连接。。。。。。\\n"); 120 } 121 else 122 { 123 printf("read buf = %s\\n", buf); 124 for(int j=0; j<len; ++j) 125 { 126 buf[j] = toupper(buf[j]); 127 } 128 printf("--buf toupper: %s\\n", buf); 129 write(i, buf, strlen(buf)+1); 130 } 131 } 132 } 133 } 134 135 close(lfd); 136 return 0; 137 }
补充 pselect:
pselect原型如下。此模型应用较少,可参考select模型自行编写C/S:
#include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 用sigmask替代当前进程的阻塞信号集,调用返回后还原原有阻塞信号集
3. poll
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd { int fd; /* 文件描述符 */ short events; /* 监控的事件 */ short revents; /* 监控事件中满足条件返回的事件 */ }; POLLIN 普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND POLLRDNORM 数据可读 POLLRDBAND 优先级带数据可读 POLLPRI 高优先级可读数据 POLLOUT 普通或带外数据可写 POLLWRNORM 数据可写 POLLWRBAND 优先级带数据可写 POLLERR 发生错误 POLLHUP 发生挂起 POLLNVAL 描述字不是一个打开的文件
fds 数组地址 nfds 监控数组中有多少文件描述符需要被监控,数组的最大长度, 数组中最后一个使用的元素下标+1,内核会轮询检测fd数组的每个文件描述符 timeout 毫秒级等待 -1:阻塞等,#define INFTIM -1 Linux中没有定义此宏 0:立即返回,不阻塞进程 >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
返回值: IO发送变化的文件描述符的个数
如果不再监控某个文件描述符时,可以把pollfd中,fd设置为-1,poll不再监控此pollfd,下次返回时,把revents设置为0。
示例(使用poll实现的server):
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <poll.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 创建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服务器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 设置端口 26 serv_len = sizeof(serv_addr); 27 // 绑定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 设置同时监听的最大个数 31 listen(lfd, 36); 32 printf("Start accept ......\\n"); 33 34 // poll结构体 35 struct pollfd allfd[1024]; 36 int max_index = 0; 37 // init 38 for(int i=0; i<1024; ++i) 39 { 40 allfd[i].fd = -1; 41 } 42 allfd[0].fd = lfd; 43 allfd[0].events = POLLIN; 44 45 while(1) 46 { 47 int i = 0; 48 int ret = poll(allfd, max_index+1, -1); 49 if(ret == -1) 50 { 51 perror("poll error"); 52 exit(1); 53 } 54 55 // 判断是否有连接请求 56 if(allfd[0].revents & POLLIN) 57 { 58 clien_len = sizeof(clien_addr); 59 // 接受连接请求 60 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 61 printf("============\\n"); 62 63 // cfd添加到poll数组 64 for(i=0; i<1024; ++i) 65 { 66 if(allfd[i].fd == -1) 67 { 68 allfd[i].fd = cfd; 69 break; 70 } 71 } 72 // 更新最后一个元素的下标 73 max_index = max_index < i ? i : max_index; 74 } 75 76 // 遍历数组 77 for(i=1; i<=max_index; ++i) 78 { 79 int fd = allfd[i].fd; 80 if(fd == -1) 81 { 82 continue; 83 } 84 if(allfd[i].revents & POLLIN) 85 { 86 // 接受数据 87 char buf[1024] = {0}; 88 int len = recv(fd, buf, sizeof(buf), 0); 89 if(len == -1) 90 { 91 perror("recv error"); 92 exit(1); 93 } 94 else if(len == 0) 95 { 96 allfd[i].fd = -1; 97 close(fd); 98 printf("客户端已经主动断开连接。。。\\n"); 99 } 100 else 101 { 102 printf("recv buf = %s\\n", buf); 103 for(int k=0; k<len; ++k) 104 { 105 buf[k] = toupper(buf[k]); 106 } 107 printf("buf toupper: %s\\n", buf); 108 send(fd, buf, strlen(buf)+1, 0); 109 } 110 111 } 112 113 } 114 } 115 116 close(lfd); 117 return 0; 118 }
poll与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 }
示例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 创建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服务器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 29 serv_addr.sin_port = htons(port); // 设置端口 30 // 绑定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 设置同时监听的最大个数 34 listen(lfd, 36); 35 printf("Start accept ......\\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 创建epoll树根节点 41 int epfd = epoll_create(2000); 42 // 初始化epoll树 43 struct epoll_event ev; 44 ev.events = EPOLLIN; 45 ev.data.fd = lfd; 46 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 47 48 struct epoll_event all[2000]; 49 while(1) 50 { 51 // 使用epoll通知内核fd 文件IO检测 sizeof(all)/sizeof(all[0]) --> sizeof(struct epoll_event) 52 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 53 54 // 遍历all数组中的前ret个元素 55 for(int i=0; i<ret; ++i) 56 { 57 int fd = all[i].data.fd; 58 // 判断是否有新连接 59 if(fd == lfd) 60 { 61 // 接受连接请求 62 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 63 if(cfd == -1) 64 { 65 perror("accept error"); 66 exit(1); 67 } 68 // 将新得到的cfd挂到树上 69 struct epoll_event temp; 70 temp.events = EPOLLIN; 71 temp.data.fd = cfd; 72 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 73 74 // 打印客户端信息 75 char ip[64] = {0}; 76 printf("New Client IP: %s, Port: %d\\n", 77 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 78 ntohs(client_addr.sin_port)); 79 80 } 81 else 82 { 83 // 处理已经连接的客户端发送过来的数据 84 if(!all[i].events & EPOLLIN) 85 { 86 continue; 87 } 88 89 // 读数据 90 char buf[1024] = {0}; 91 int len = recv(fd, buf, sizeof(buf), 0); 92 if(len == -1) 93 { 94 perror("recv error"); 95 exit(1); 96 } 97 else if(len == 0) 98 { 99 printf("client disconnected ....\\n"); 100 // fd从epoll树上删除 101 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 102 if(ret == -1) 103 { 104 perror("epoll_ctl - del error"); 105 exit(1); 106 } 107 close(fd); 108 } 109 else 110 { 111 printf(" recv buf: %s\\n", buf); 112 write(fd, buf, len); 113 } 114 } 115 } 116 } 117 118 close(lfd); 119 return 0; 120 }
1 /* client.c */ 2 #include <stdio.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <netinet/in.h> 6 #include "wrap.h" 7 8 #define MAXLINE 80 9 #define SERV_PORT 6666 10 11 int main(int argc, char *argv[]) 12 { 13 struct sockaddr_in servaddr; 14 char buf[MAXLINE]; 15 int sockfd, n; 16 17 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 18 19 bzero(&servaddr, sizeof(servaddr)); 20 servaddr.sin_family = AF_INET; 21 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 22 servaddr.sin_port = htons(SERV_PORT); 23 24 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 25 26 while (fgets(buf, MAXLINE, stdin) != NULL) { 27 Write(sockfd, buf, strlen(buf)); 28 n = Read(sockfd, buf, MAXLINE); 29 if (n == 0) 30 printf("the other side has been closed.\\n"); 31 else 32 Write(STDOUT_FILENO, buf, n); 33 } 34 35 Close(sockfd); 36 return 0; 37 }
注意:epoll_wait 调用次数越多, 系统的开销越大
四、epoll进阶
1. 事件模型
EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发。
思考如下步骤:
1)假定我们已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符。
2)管道的另一端写入了2KB的数据
3)调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作
4)读取1KB的数据
5)调用epoll_wait……
在这个过程中,有两种工作模式:
(1)ET模式
ET模式即Edge Triggered工作模式。
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在
等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩
余数据。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面
会介绍避免可能的缺陷。
- 基于非阻塞文件句柄
- 只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的
读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
(2)LT模式
LT模式即Level Triggered工作模式。
与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。
LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任
何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再
为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
2. 实例一
基于管道epoll ET触发模式
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/epoll.h> 4 #include <errno.h> 5 #include <unistd.h> 6 7 #define MAXLINE 10 8 9 int main(int argc, char *argv[]) 10 { 11 int efd, i; 12 int pfd[2]; 13 pid_t pid; 14 char buf[MAXLINE], ch = ‘a‘; 15 16 pipe(pfd); 17 pid = fork(); 18 if (pid == 0) { 19 close(pfd[0]); 20 while (1) { 21 for (i = 0; i < MAXLINE/2; i++) 22 buf[i] = ch; 23 buf[i-1] = ‘\\n‘; 24 ch++; 25 26 for (; i < MAXLINE; i++) 27 buf[i] = ch; 28 buf[i-1] = ‘\\n‘; 29 ch++; 30 31 write(pfd[1], buf, sizeof(buf)); 32 sleep(2); 33 } 34 close(pfd[1]); 35 } else if (pid > 0) { 36 struct epoll_event event; 37 struct epoll_event resevent[10]; 38 int res, len; 39 close(pfd[1]); 40 41 efd = epoll_create(10); 42 /* event.events = EPOLLIN; */ 43 event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 ,默认是水平触发 */ 44 event.data.fd = pfd[0]; 45 epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); 46 47 while (1) { 48 res = epoll_wait(efd, resevent, 10, -1); 49 printf("res %d\\n", res); 50 if (resevent[0].data.fd == pfd[0]) { 51 len = read(pfd[0], buf, MAXLINE/2); 52 write(STDOUT_FILENO, buf, len); 53 } 54 } 55 close(pfd[0]); 56 close(efd); 57 } else { 58 perror("fork"); 59 exit(-1); 60 } 61 return 0; 62 }
3. 实例二
基于网络C/S模型的epoll ET触发模式
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 创建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服务器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 29 serv_addr.sin_port = htons(port); // 设置端口 30 // 绑定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 设置同时监听的最大个数 34 listen(lfd, 36); 35 printf("Start accept ......\\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 创建epoll树根节点 41 int epfd = epoll_create(2000); 42 // 初始化epoll树 43 struct epoll_event ev; 44 45 // 设置边沿触发 46 ev.events = EPOLLIN | EPOLLET; 47 ev.data.fd = lfd; 48 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 49 50 struct epoll_event all[2000]; 51 while(1) 52 { 53 // 使用epoll通知内核fd 文件IO检测 54 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 55 printf("================== epoll_wait =============\\n"); 56 57 // 遍历all数组中的前ret个元素 58 for(int i=0; i<ret; ++i) 59 { 60 int fd = all[i].data.fd; 61 // 判断是否有新连接 62 if(fd == lfd) 63 { 64 // 接受连接请求 65 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 66 if(cfd == -1) 67 { 68 perror("accept error"); 69 exit(1); 70 } 71 // 将新得到的cfd挂到树上 72 struct epoll_event temp; 73 // 设置边沿触发 74 temp.events = EPOLLIN | EPOLLET; 75 temp.data.fd = cfd; 76 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 77 78 // 打印客户端信息 79 char ip[64] = {0}; 80 printf("New Client IP: %s, Port: %d\\n", 81 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 82 ntohs(client_addr.sin_port)); 83 84 } 85 else 86 { 87 // 处理已经连接的客户端发送过来的数据 88 if(!all[i].events & EPOLLIN) 89 { 90 continue; 91 } 92 93 // 读数据 94 char buf[5] = {0}; 95 int len = recv(fd, buf, sizeof(buf), 0); 96 if(len == -1) 97 { 98 perror("recv error"); 99 exit(1); 100 } 101 else if(len == 0) 102 { 103 printf("client disconnected ....\\n"); 104 // fd从epoll树上删除 105 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 106 if(ret == -1) 107 { 108 perror("epoll_ctl - del error"); 109 exit(1); 110 } 111 close(fd); 112 } 113 else 114 { 115 // printf(" recv buf: %s\\n", buf); 116 write(STDOUT_FILENO, buf, len); 117 write(fd, buf, len); 118 } 119 } 120 } 121 } 122 123 close(lfd); 124 return 0; 125 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <fcntl.h> 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\\n"); 16 exit(1); 17 } 18 // 创建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 连接服务器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 写数据 43 // 接收键盘输入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 发送给服务器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服务器端的数据 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\\n", buf, len); 52 } 53 return 0; 54 }
执行结果:
client端:
[[email protected] epoll]# ./client 6666 000001111122222 read buf = 000001111122222 , len = 5 read buf = 11111, len = 5 read buf = 22222, len = 5
server端:
[[email protected] epoll]# ./et_epoll 6666 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 54080 ================== epoll_wait ============= 00000================== epoll_wait ============= 11111================== epoll_wait ============= 22222
执行结果分析:可以看出,当客户端发送数据(000001111122222)到server端(接收数据缓冲区内),但是由于server端一次只接受5个字节(00000),因此在接受完5个字节之后,将接收的5个字节数据发回给客户端,程序又会在epoll_wait处阻塞等待。当有新数据再次发送过来,则会将上一次缓冲区中剩余的数据(11111)读取并发送给客户端,如此最后将(22222)发送给客户端。
4. 实例三
基于网络C/S非阻塞模型的epoll ET触发模式
实现过程中注意两点:
- 当服务端接收到客户端新的连接(cfd),需要设置客户端连接问价描述符(cfd)为非阻塞模式,因为下面需要循环读取服务端缓冲区中的数据,而如果不设置 cfd 为非阻塞模式,则当读完缓冲区的数据 recv 再次读取会阻塞住,则整个程序会被阻塞;
- 通过errno == EAGAIN来判断缓冲区中的数据读取完成。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 10 #include <fcntl.h> 11 #include <errno.h> 12 13 int main(int argc, const char* argv[]) 14 { 15 if(argc < 2) 16 { 17 printf("eg: ./a.out port\\n"); 18 exit(1); 19 } 20 struct sockaddr_in serv_addr; 21 socklen_t serv_len = sizeof(serv_addr); 22 int port = atoi(argv[1]); 23 24 // 创建套接字 25 int lfd = socket(AF_INET, SOCK_STREAM, 0); 26 // 初始化服务器 sockaddr_in 27 memset(&serv_addr, 0, serv_len); 28 serv_addr.sin_family = AF_INET; // 地址族 29 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 30 serv_addr.sin_port = htons(port); // 设置端口 31 // 绑定IP和端口 32 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 33 34 // 设置同时监听的最大个数 35 listen(lfd, 36); 36 printf("Start accept ......\\n"); 37 38 struct sockaddr_in client_addr; 39 socklen_t cli_len = sizeof(client_addr); 40 41 // 创建epoll树根节点 42 int epfd = epoll_create(2000); 43 // 初始化epoll树 44 struct epoll_event ev; 45 46 // 设置边沿触发 47 ev.events = EPOLLIN; 48 ev.data.fd = lfd; 49 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 50 51 struct epoll_event all[2000]; 52 while(1) 53 { 54 // 使用epoll通知内核fd 文件IO检测 55 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 56 printf("================== epoll_wait =============\\n"); 57 58 // 遍历all数组中的前ret个元素 59 for(int i=0; i<ret; ++i) 60 { 61 int fd = all[i].data.fd; 62 // 判断是否有新连接 63 if(fd == lfd) 64 { 65 // 接受连接请求 66 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 67 if(cfd == -1) 68 { 69 perror("accept error"); 70 exit(1); 71 } 72 // 设置文件cfd为非阻塞模式 73 int flag = fcntl(cfd, F_GETFL); 74 flag |= O_NONBLOCK; 75 fcntl(cfd, F_SETFL, flag); 76 77 // 将新得到的cfd挂到树上 78 struct epoll_event temp; 79 // 设置边沿触发 80 temp.events = EPOLLIN | EPOLLET; 81 temp.data.fd = cfd; 82 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 83 84 // 打印客户端信息 85 char ip[64] = {0}; 86 printf("New Client IP: %s, Port: %d\\n", 87 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 88 ntohs(client_addr.sin_port)); 89 90 } 91 else 92 { 93 // 处理已经连接的客户端发送过来的数据 94 if(!all[i].events & EPOLLIN) 95 { 96 continue; 97 } 98 99 // 读数据 100 char buf[5] = {0}; 101 int len; 102 // 循环读数据 103 while( (len = recv(fd, buf, sizeof(buf), 0)) > 0 ) 104 { 105 // 数据打印到终端 106 write(STDOUT_FILENO, buf, len); 107 // 发送给客户端 108 send(fd, buf, len, 0); 109 } 110 if(len == 0) 111 { 112 printf("客户端断开了连接\\n"); 113 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 114 if(ret == -1) 115 { 116 perror("epoll_ctl - del error"); 117 exit(1); 118 } 119 close(fd); 120 } 121 else if(len == -1) 122 { 123 if(errno == EAGAIN) 124 { 125 printf("缓冲区数据已经读完\\n"); 126 } 127 else 128 { 129 printf("recv error----\\n"); 130 exit(1); 131 } 132 } 133 } 134 } 135 } 136 137 close(lfd); 138 return 0; 139 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <fcntl.h> 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\\n"); 16 exit(1); 17 } 18 // 创建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 连接服务器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 写数据 43 // 接收键盘输入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 发送给服务器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服务器端的数据 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\\n", buf, len); 52 } 53 return 0; 54 }
执行结果:
server端:
[[email protected] epoll]# ./nonblock_et_epoll 8888 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 47634 ================== epoll_wait ============= 000001111122222 缓冲区数据已经读完 ================== epoll_wait ============= hello world 缓冲区数据已经读完
client端:
[[email protected] epoll]# ./client 8888 000001111122222 read buf = 000001111122222 , len = 17 hello world read buf = hello world , len = 13
执行结果分析:可以看出设置为epoll et非阻塞模式,当客户端发送数据不管有多少个字节,server端会全部从缓冲区读取并发送给客户端(包括客户端发送的回车(‘\\n‘))。
5. 示例四
基于网络C/S模型的epoll LT触发模式
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/epoll.h> 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 创建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服务器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 29 serv_addr.sin_port = htons(port); // 设置端口 30 // 绑定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 设置同时监听的最大个数 34 listen(lfd, 36); 35 printf("Start accept ......\\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 创建epoll树根节点 41 int epfd = epoll_create(2000); 42 // 初始化epoll树 43 struct epoll_event ev; 44 ev.events = EPOLLIN; 45 ev.data.fd = lfd; 46 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 47 48 struct epoll_event all[2000]; 49 while(1) 50 { 51 // 使用epoll通知内核fd 文件IO检测 52 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 53 printf("================== epoll_wait =============\\n"); 54 55 // 遍历all数组中的前ret个元素 56 for(int i=0; i<ret; ++i) 57 { 58 int fd = all[i].data.fd; 59 // 判断是否有新连接 60 if(fd == lfd) 61 { 62 // 接受连接请求 63 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 64 if(cfd == -1) 65 { 66 perror("accept error"); 67 exit(1); 68 } 69 // 将新得到的cfd挂到树上 70 struct epoll_event temp; 71 temp.events = EPOLLIN; 72 temp.data.fd = cfd; 73 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); 74 75 // 打印客户端信息 76 char ip[64] = {0}; 77 printf("New Client IP: %s, Port: %d\\n", 78 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 79 ntohs(client_addr.sin_port)); 80 81 } 82 else 83 { 84 // 处理已经连接的客户端发送过来的数据 85 if(!all[i].events & EPOLLIN) 86 { 87 continue; 88 } 89 90 // 读数据 91 char buf[5] = {0}; 92 int len = recv(fd, buf, sizeof(buf), 0); 93 if(len == -1) 94 { 95 perror("recv error"); 96 exit(1); 97 } 98 else if(len == 0) 99 { 100 printf("client disconnected ....\\n"); 101 // fd从epoll树上删除 102 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); 103 if(ret == -1) 104 { 105 perror("epoll_ctl - del error"); 106 exit(1); 107 } 108 close(fd); 109 110 } 111 else 112 { 113 // printf(" recv buf: %s\\n", buf); 114 write(STDOUT_FILENO, buf, len); 115 write(fd, buf, len); 116 } 117 } 118 } 119 } 120 121 close(lfd); 122 return 0; 123 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <fcntl.h> 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\\n"); 16 exit(1); 17 } 18 // 创建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 连接服务器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 写数据 43 // 接收键盘输入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 发送给服务器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服务器端的数据 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\\n", buf, len); 52 } 53 return 0; 54 }
执行结果:
server端:
[[email protected] epoll]# ./lt_epoll 8888 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 47636 ================== epoll_wait ============= 00000================== epoll_wait ============= 11111================== epoll_wait ============= 22222================== epoll_wait =============
client端:
[[email protected] epoll]# ./client 8888 000001111122222 read buf = 000001111122222 , len = 17
执行结果分析:可以看出,当客户端发送数据(000001111122222)到server端(接收数据缓冲区内),但是由于server端一次只接受5个字节(00000),因此在接受完5个字节之后,将接收的5个字节数据保存到发送缓冲区。然后程序回到epoll_wait处,此时检测到接收缓冲区还有未接收完的数据程序没有在epoll_wait处阻塞等待。而是继续从上一次缓冲区中读取剩余的数据(11111)及(22222),读取完成之后将所有数据发送给客户端。
文件描述符突破1024限制:
- select - 突破不了, 需要编译内核
- poll和epoll可以突破1024限制
解决办法:
- 查看受计算机硬件限制的文件描述符上限
- 通过配置文件修改上限值
五、线程池并发服务器
1)预先创建阻塞于accept多线程,使用互斥锁上锁保护accept
2)预先创建多线程,由主线程调用accept
六、UDP服务器
传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可
或缺的重要通信手段。
相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和
正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。
那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议
等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅
助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。
与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:
1)服务器应用层设计流量控制,控制发送数据速度。
2)借助setsockopt函数改变接收缓冲区大小。如:
#include <sys/socket.h> int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); int n = 220x1024 setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
注意:udp的数据是不安全的, 容易丢包。出现丢包, 不会出现丢部分数据,要丢只会丢全部数据。
TCP和UDP的使用场景:
tcp使用场景:
1)对数据安全性要求高的时候
a. 登录数据的传输
c. 文件传输
2)http协议
传输层协议 - tcp
udp使用场景:
1)效率高 - 实时性要求比较高
a. 视频聊天
b. 通话
2)有实力的大公司
a. 使用upd
b. 在应用层自定义协议, 做数据校验
七、C/S模型-UDP
UDP处理模型
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。
编译运行server,在两个终端里各开一个client与server交互,看看server是否具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,看此时client还能否和server联系上。和前面TCP程序
的运行结果相比较,体会无连接的含义。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 9 int main(int argc, const char* argv[]) 10 { 11 // 创建套接字 12 int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // fd绑定本地的IP和端口 20 struct sockaddr_in serv; 21 memset(&serv, 0, sizeof(serv)); 22 serv.sin_family = AF_INET; 23 serv.sin_port = htons(8765); 24 serv.sin_addr.s_addr = htonl(INADDR_ANY); 25 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv)); 26 if(ret == -1) 27 { 28 perror("bind error"); 29 exit(1); 30 } 31 32 struct sockaddr_in client; 33 socklen_t cli_len = sizeof(client); 34 // 通信 35 char buf[1024] = {0}; 36 while(1) 37 { 38 int recvlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &cli_len); 39 if(recvlen == -1) 40 { 41 perror("recvform error"); 42 exit(1); 43 } 44 45 printf("recv buf: %s\\n", buf); 46 char ip[64] = {0}; 47 printf("New Client IP: %s, Port: %d\\n", 48 inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)), 49 ntohs(client.sin_port)); 50 51 // 给客户端发送数据 52 sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client)); 53 } 54 55 close(fd); 56 57 return 0; 58 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 9 int main(int argc, const char* argv[]) 10 { 11 // create socket 12 int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // 初始化服务器的IP和端口 20 struct sockaddr_in serv; 21 memset(&serv, 0, sizeof(serv)); 22 serv.sin_family = AF_INET; 23 serv.sin_port = htons(8765); 24 inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr); 25 26 // 通信 27 while(1) 28 { 29 char buf[1024] = {0}; 30 fgets(buf, sizeof(buf), stdin); 31 // 数据的发送 - server - IP port 32 sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv)); 33 34 // 等待服务器发送数据过来 35 recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); 36 printf("recv buf: %s\\n", buf); 37 } 38 39 close(fd); 40 41 return 0; 42 }
八、广播
广播结构图:
注意:广播只适用于局域网
代码实现:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 9 int main(int argc, const char* argv[]) 10 { 11 // 创建套接字 12 int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // 绑定server的iP和端口 20 struct sockaddr_in serv; 21 memset(&serv, 0, sizeof(serv)); 22 serv.sin_family = AF_INET; 23 serv.sin_port = htons(8787); // server端口 24 serv.sin_addr.s_addr = htonl(INADDR_ANY); 25 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv)); 26 if(ret == -1) 27 { 28 perror("bind error"); 29 exit(1); 30 } 31 32 // 初始化客户端地址信息 33 struct sockaddr_in client; 34 memset(&client, 0, sizeof(client)); 35 client.sin_family = AF_INET; 36 client.sin_port = htons(6767); // 客户端要绑定的端口 37 // 使用广播地址给客户端发数据 38 inet_pton(AF_INET, "192.168.30.255", &client.sin_addr.s_addr); 39 40 // 给服务器开放广播权限 41 int flag = 1; 42 setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)); 43 44 // 通信 45 while(1) 46 { 47 // 一直给客户端发数据 48 static int num = 0; 49 char buf[1024] = {0}; 50 sprintf(buf, "hello, udp == %d\\n", num++); 51 int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client)); 52 if(ret == -1) 53 { 54 perror("sendto error"); 55 break; 56 } 57 58 printf("server == send buf: %s\\n", buf); 59 60 sleep(1); 61 } 62 63 close(fd); 64 65 return 0; 66 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 9 int main(int argc, const char* argv[]) 10 { 11 int fd = socket(AF_INET, SOCK_DGRAM, 0); 12 if(fd == -1) 13 { 14 perror("socket error"); 15 exit(1); 16 } 17 18 // 绑定iP和端口 19 struct sockaddr_in client; 20 memset(&client, 0, sizeof(client)); 21 client.sin_family = AF_INET; 22 client.sin_port = htons(6767); 23 inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr); 24 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client)); 25 if(ret == -1) 26 { 27 perror("bind error"); 28 exit(1); 29 } 30 31 // 接收数据 32 while(1) 33 { 34 char buf[1024] = {0}; 35 int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); 36 if(len == -1) 37 { 38 perror("recvfrom error"); 39 break; 40 } 41 42 printf("client == recv buf: %s\\n", buf); 43 } 44 45 close(fd); 46 47 return 0; 48 }
九、多播(组播)
使用范围:
- 局域网
- 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.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用; 224.0.1.0~224.0.1.255 是公用组播地址,可以用于Internet;欲使用需申请。 224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效; 239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。
可使用ip ad命令查看网卡编号,如:
[[email protected] ~]# ip ad 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:60:ad:b3 brd ff:ff:ff:ff:ff:ff inet 192.168.30.137/24 brd 192.168.30.255 scope global noprefixroute dynamic ens33 valid_lft 1642sec preferred_lft 1642sec inet6 fe80::7341:e2f1:498c:8501/64 scope link noprefixroute valid_lft forever preferred_lft forever
if_nametoindex 命令可以根据网卡名,获取网卡序号。
代码实现:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <net/if.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 // 创建套接字 13 int fd = socket(AF_INET, SOCK_DGRAM, 0); 14 if(fd == -1) 15 { 16 perror("socket error"); 17 exit(1); 18 } 19 20 // 绑定server的iP和端口 21 struct sockaddr_in serv; 22 memset(&serv, 0, sizeof(serv)); 23 serv.sin_family = AF_INET; 24 serv.sin_port = htons(8787); // server端口 25 serv.sin_addr.s_addr = htonl(INADDR_ANY); 26 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv)); 27 if(ret == -1) 28 { 29 perror("bind error"); 30 exit(1); 31 } 32 33 // 初始化客户端地址信息 34 struct sockaddr_in client; 35 memset(&client, 0, sizeof(client)); 36 client.sin_family = AF_INET; 37 client.sin_port = htons(6666); // 客户端要绑定的端口 38 // 使用组播地址给客户端发数据 39 inet_pton(AF_INET, "239.0.0.10", &client.sin_addr.s_addr); 40 41 // 给服务器开放组播权限 42 struct ip_mreqn flag; 43 // init flag 44 inet_pton(AF_INET, "239.0.0.10", &flag.imr_multiaddr.s_addr); // 组播地址 45 inet_pton(AF_INET, "0.0.0.0", &flag.imr_address.s_addr); // 本地IP 46 flag.imr_ifindex = if_nametoindex("ens33"); 47 setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &flag, sizeof(flag)); 48 49 // 通信 50 while(1) 51 { 52 // 一直给客户端发数据 53 static int num = 0; 54 char buf[1024] = {0}; 55 sprintf(buf, "hello, udp == %d\\n", num++); 56 int ret = sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client)); 57 if(ret == -1) 58 { 59 perror("sendto error"); 60 break; 61 } 62 63 printf("server == send buf: %s\\n", buf); 64 65 sleep(1); 66 } 67 68 close(fd); 69 70 return 0; 71 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <net/if.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 int fd = socket(AF_INET, SOCK_DGRAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // 绑定iP和端口 20 struct sockaddr_in client; 21 memset(&client, 0, sizeof(client)); 22 client.sin_family = AF_INET; 23 client.sin_port = htons(6666); // ........ 24 inet_pton(AF_INET, "0.0.0.0", &client.sin_addr.s_addr); 25 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client)); 26 if(ret == -1) 27 { 28 perror("bind error"); 29 exit(1); 30 } 31 32 // 加入到组播地址 33 struct ip_mreqn fl; 34 inet_pton(AF_INET, "239.0.0.10", &fl.imr_multiaddr.s_addr); 35 inet_pton(AF_INET, "0.0.0.0", &fl.imr_address.s_addr); 36 fl.imr_ifindex = if_nametoindex("ens33"); 37 setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &fl, sizeof(fl)); 38 39 // 接收数据 40 while(1) 41 { 42 char buf[1024] = {0}; 43 int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); 44 if(len == -1) 45 { 46 perror("recvfrom error"); 47 break; 48 } 49 50 printf("client == recv buf: %s\\n", buf); 51 } 52 53 close(fd); 54 55 return 0; 56 }
十、socket IPC(本地套接字domain)
通过管道与通过本地套接字实现进程间通信:
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址
127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因
为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket
也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIXDomain Socket
通讯的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,
protocol参数仍然指定为0即可。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型
的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址结构类型 __be16 sin_port; /* Port number */ 端口号 struct in_addr sin_addr; /* Internet address */ IP地址 }; struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型 char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径) };
以下程序将UNIX Domain socket绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); #define offsetof(type, member) ((int)&((type *)0)->MEMBER)
代码实现:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <sys/un.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 int lfd = socket(AF_LOCAL, SOCK_STREAM, 0); 13 if(lfd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 // 如果套接字文件存在, 删除套接字文件 20 unlink("server.sock"); 21 22 // 绑定 23 struct sockaddr_un serv; 24 serv.sun_family = AF_LOCAL; 25 strcpy(serv.sun_path, "server.sock"); 26 int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv)); 27 if(ret == -1) 28 { 29 perror("bind error"); 30 exit(1); 31 } 32 33 // 监听 34 ret = listen(lfd, 36); 35 if(ret == -1) 36 { 37 perror("listen error"); 38 exit(1); 39 } 40 41 // 等待接收连接请求 42 struct sockaddr_un client; 43 socklen_t len = sizeof(client); 44 int cfd = accept(lfd, (struct sockaddr*)&client, &len); 45 if(cfd == -1) 46 { 47 perror("accept error"); 48 exit(1); 49 } 50 printf("======client bind file: %s\\n", client.sun_path); 51 52 // 通信 53 while(1) 54 { 55 char buf[1024] = {0}; 56 int recvlen = recv(cfd, buf, sizeof(buf), 0); 57 if(recvlen == -1) 58 { 59 perror("recv error"); 60 exit(1); 61 } 62 else if(recvlen == 0) 63 { 64 printf("clietn disconnect ....\\n"); 65 close(cfd); 66 break; 67 } 68 else 69 { 70 printf("recv buf: %s\\n", buf); 71 send(cfd, buf, recvlen, 0); 72 } 73 } 74 close(cfd); 75 close(lfd); 76 77 return 0; 78 }
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <string.h> 7 #include <arpa/inet.h> 8 #include <sys/un.h> 9 10 int main(int argc, const char* argv[]) 11 { 12 int fd = socket(AF_LOCAL, SOCK_STREAM, 0); 13 if(fd == -1) 14 { 15 perror("socket error"); 16 exit(1); 17 } 18 19 unlink("client.sock"); 20 21 // ================================ 22 // 给客户端绑定一个套接字文件 23 struct sockaddr_un client; 24 client.sun_family = AF_LOCAL; 25 strcpy(client.sun_path, "client.sock"); 26 int ret = bind(fd, (struct sockaddr*)&client, sizeof(client)); 27 if(ret == -1) 28 { 29 perror("bind error"); 30 exit(1); 31 } 32 33 // 初始化server信息 34 struct sockaddr_un serv; 35 serv.sun_family = AF_LOCAL; 36 strcpy(serv.sun_path, "server.sock"); 37 38 // 连接服务器 39 connect(fd, (struct sockaddr*)&serv, sizeof(serv)); 40 41 // 通信 42 while(1) 43 { 44 char buf[1024] = {0}; 45 fgets(buf, sizeof(buf), stdin); 46 send(fd, buf, strlen(buf)+1, 0); 47 48 // 接收数据 49 recv(fd, buf, sizeof(buf), 0); 50 printf("recv buf: %s\\n", buf); 51 } 52 53 close(fd); 54 55 return 0; 56 } 57
十一、其他常用函数
1. 名字与地址转换
gethostbyname根据给定的主机名,获取主机信息。
过时,仅用于IPv4,且线程不安全。
1 #include <stdio.h> 2 #include <netdb.h> 3 #include <arpa/inet.h> 4 5 extern int h_errno; 6 7 int main(int argc, char *argv[]) 8 { 9 struct hostent *host; 10 char str[128]; 11 host = gethostbyname(argv[1]); 12 printf("%s\\n", host->h_name); 13 14 while (*(host->h_aliases) != NULL) 15 printf("%s\\n", *host->h_aliases++); 16 17 switch (host->h_addrtype) { 18 case AF_INET: 19 while (*(host->h_addr_list) != NULL) 20 printf("%s\\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); 21 break; 22 default: 23 printf("unknown address type\\n"); 24 break; 25 } 26 return 0; 27 }
gethostbyaddr函数。
此函数只能获取域名解析服务器的url和/etc/hosts里登记的IP对应的域名。
1 #include <stdio.h> 2 #include <netdb.h> 3 #include <arpa/inet.h> 4 5 extern int h_errno; 6 7 int main(int argc, char *argv[]) 8 { 9 struct hostent *host; 10 char str[128]; 11 struct in_addr addr; 12 13 inet_pton(AF_INET, argv[1], &addr); 14 host = gethostbyaddr((char *)&addr, 4, AF_INET); 15 printf("%s\\n", host->h_name); 16 17 while (*(host->h_aliases) != NULL) 18 printf("%s\\n", *host->h_aliases++); 19 switch (host->h_addrtype) { 20 case AF_INET: 21 while (*(host->h_addr_list) != NULL) 22 printf("%s\\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str))); 23 break; 24 default: 25 printf("unknown address type\\n"); 26 break; 27 } 28 return 0; 29 }
getservbyname
getservbyport
根据服务程序名字或端口号获取信息。使用频率不高。
getaddrinfo
getnameinfo
freeaddrinfo
可同时处理IPv4和IPv6,线程安全的。
2. 套接口和地址关联
getsockname
根据accpet返回的sockfd,得到临时端口号
getpeername
根据accpet返回的sockfd,得到远端链接的端口号,在exec后可以获取客户端信息。
以上是关于Linux 高并发服务器的主要内容,如果未能解决你的问题,请参考以下文章