Linux C编程第十八章 高并发服务器(一)

网友投稿 423 2022-05-30

f高并发服务器

一、多进程并发服务器

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

代码实现:

wrap.c

1 #include 2 #include 3 #include 4 #include 5 #include 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) { 19 if ((errno == ECONNABORTED) || (errno == EINTR)) 20 goto again; 21 else 22 perr_exit("accept error"); 23 } 24 return n; 25 } 26 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 28 { 29 int n; 30 31 if ((n = bind(fd, sa, salen)) < 0) 32 perr_exit("bind error"); 33 34 return n; 35 } 36 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 38 { 39 int n; 40 41 if ((n = connect(fd, sa, salen)) < 0) 42 perr_exit("connect error"); 43 44 return n; 45 } 46 47 int Listen(int fd, int backlog) 48 { 49 int n; 50 51 if ((n = listen(fd, backlog)) < 0) 52 perr_exit("listen error"); 53 54 return n; 55 } 56 57 int Socket(int family, int type, int protocol) 58 { 59 int n; 60 61 if ((n = socket(family, type, protocol)) < 0) 62 perr_exit("socket error"); 63 64 return n; 65 } 66 67 ssize_t Read(int fd, void *ptr, size_t nbytes) 68 { 69 ssize_t n; 70 71 again: 72 if ( (n = read(fd, ptr, nbytes)) == -1) { 73 if (errno == EINTR) 74 goto again; 75 else 76 return -1; 77 } 78 return n; 79 } 80 81 ssize_t Write(int fd, const void *ptr, size_t nbytes) 82 { 83 ssize_t n; 84 85 again: 86 if ( (n = write(fd, ptr, nbytes)) == -1) { 87 if (errno == EINTR) 88 goto again; 89 else 90 return -1; 91 } 92 return n; 93 } 94 95 int Close(int fd) 96 { 97 int n; 98 if ((n = close(fd)) == -1) 99 perr_exit("close error"); 100 101 return n; 102 } 103 104 /*参三: 应该读取的字节数*/ 105 ssize_t Readn(int fd, void *vptr, size_t n) 106 { 107 size_t nleft; //usigned int 剩余未读取的字节数 108 ssize_t nread; //int 实际读到的字节数 109 char *ptr; 110 111 ptr = vptr; 112 nleft = n; 113 114 while (nleft > 0) { 115 if ((nread = read(fd, ptr, nleft)) < 0) { 116 if (errno == EINTR) 117 nread = 0; 118 else 119 return -1; 120 } else if (nread == 0) 121 break; 122 123 nleft -= nread; 124 ptr += nread; 125 } 126 return n - nleft; 127 } 128 129 ssize_t Writen(int fd, const void *vptr, size_t n) 130 { 131 size_t nleft; 132 ssize_t nwritten; 133 const char *ptr; 134 135 ptr = vptr; 136 nleft = n; 137 while (nleft > 0) { 138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 139 if (nwritten < 0 && errno == EINTR) 140 nwritten = 0; 141 else 142 return -1; 143 } 144 145 nleft -= nwritten; 146 ptr += nwritten; 147 } 148 return n; 149 } 150 151 static ssize_t my_read(int fd, char *ptr) 152 { 153 static int read_cnt; 154 static char *read_ptr; 155 static char read_buf[100]; 156 157 if (read_cnt <= 0) { 158 again: 159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 160 if (errno == EINTR) 161 goto again; 162 return -1; 163 } else if (read_cnt == 0) 164 return 0; 165 read_ptr = read_buf; 166 } 167 read_cnt--; 168 *ptr = *read_ptr++; 169 170 return 1; 171 } 172 173 ssize_t Readline(int fd, void *vptr, size_t maxlen) 174 { 175 ssize_t n, rc; 176 char c, *ptr; 177 178 ptr = vptr; 179 for (n = 1; n < maxlen; n++) { 180 if ( (rc = my_read(fd, &c)) == 1) { 181 *ptr++ = c; 182 if (c == '\n') 183 break; 184 } else if (rc == 0) { 185 *ptr = 0; 186 return n - 1; 187 } else 188 return -1; 189 } 190 *ptr = 0; 191 192 return n; 193 }

wrap.h

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

server.c

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 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 }

client.c

1 /* client.c */ 2 #include 3 #include 4 #include 5 #include 6 #include 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 }

makefile

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. 线程共享:

全局数据区

堆区

一块有效内存的地址

代码实现:

wrap.c

1 #include 2 #include 3 #include 4 #include 5 #include 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) { 19 if ((errno == ECONNABORTED) || (errno == EINTR)) 20 goto again; 21 else 22 perr_exit("accept error"); 23 } 24 return n; 25 } 26 27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 28 { 29 int n; 30 31 if ((n = bind(fd, sa, salen)) < 0) 32 perr_exit("bind error"); 33 34 return n; 35 } 36 37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 38 { 39 int n; 40 41 if ((n = connect(fd, sa, salen)) < 0) 42 perr_exit("connect error"); 43 44 return n; 45 } 46 47 int Listen(int fd, int backlog) 48 { 49 int n; 50 51 if ((n = listen(fd, backlog)) < 0) 52 perr_exit("listen error"); 53 54 return n; 55 } 56 57 int Socket(int family, int type, int protocol) 58 { 59 int n; 60 61 if ((n = socket(family, type, protocol)) < 0) 62 perr_exit("socket error"); 63 64 return n; 65 } 66 67 ssize_t Read(int fd, void *ptr, size_t nbytes) 68 { 69 ssize_t n; 70 71 again: 72 if ( (n = read(fd, ptr, nbytes)) == -1) { 73 if (errno == EINTR) 74 goto again; 75 else 76 return -1; 77 } 78 return n; 79 } 80 81 ssize_t Write(int fd, const void *ptr, size_t nbytes) 82 { 83 ssize_t n; 84 85 again: 86 if ( (n = write(fd, ptr, nbytes)) == -1) { 87 if (errno == EINTR) 88 goto again; 89 else 90 return -1; 91 } 92 return n; 93 } 94 95 int Close(int fd) 96 { 97 int n; 98 if ((n = close(fd)) == -1) 99 perr_exit("close error"); 100 101 return n; 102 } 103 104 /*参三: 应该读取的字节数*/ 105 ssize_t Readn(int fd, void *vptr, size_t n) 106 { 107 size_t nleft; //usigned int 剩余未读取的字节数 108 ssize_t nread; //int 实际读到的字节数 109 char *ptr; 110 111 ptr = vptr; 112 nleft = n; 113 114 while (nleft > 0) { 115 if ((nread = read(fd, ptr, nleft)) < 0) { 116 if (errno == EINTR) 117 nread = 0; 118 else 119 return -1; 120 } else if (nread == 0) 121 break; 122 123 nleft -= nread; 124 ptr += nread; 125 } 126 return n - nleft; 127 } 128 129 ssize_t Writen(int fd, const void *vptr, size_t n) 130 { 131 size_t nleft; 132 ssize_t nwritten; 133 const char *ptr; 134 135 ptr = vptr; 136 nleft = n; 137 while (nleft > 0) { 138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) { 139 if (nwritten < 0 && errno == EINTR) 140 nwritten = 0; 141 else 142 return -1; 143 } 144 145 nleft -= nwritten; 146 ptr += nwritten; 147 } 148 return n; 149 } 150 151 static ssize_t my_read(int fd, char *ptr) 152 { 153 static int read_cnt; 154 static char *read_ptr; 155 static char read_buf[100]; 156 157 if (read_cnt <= 0) { 158 again: 159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { 160 if (errno == EINTR) 161 goto again; 162 return -1; 163 } else if (read_cnt == 0) 164 return 0; 165 read_ptr = read_buf; 166 } 167 read_cnt--; 168 *ptr = *read_ptr++; 169 170 return 1; 171 } 172 173 ssize_t Readline(int fd, void *vptr, size_t maxlen) 174 { 175 ssize_t n, rc; 176 char c, *ptr; 177 178 ptr = vptr; 179 for (n = 1; n < maxlen; n++) { 180 if ( (rc = my_read(fd, &c)) == 1) { 181 *ptr++ = c; 182 if (c == '\n') 183 break; 184 } else if (rc == 0) { 185 *ptr = 0; 186 return n - 1; 187 } else 188 return -1; 189 } 190 *ptr = 0; 191 192 return n; 193 }

wrap.h

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

server.c

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 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 }

client.c

1 /* client.c */ 2 #include 3 #include 4 #include 5 #include 6 #include 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 }

makefile

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工作流程的理解:

【Linux C编程】第十八章 高并发服务器(一)

【Linux C编程】第十八章 高并发服务器(二)

Linux 任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:如何使用 Go 更好地开发并发程序
下一篇:DevOps=开发吃掉运维?醒醒吧,你的DevOps拓扑结构有问题!
相关文章