【Linux C编程】第十八章 高并发服务器(一)
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 <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/socket.h>
6
7 void perr_exit(const char *s)
8 {
9 perror(s);
10 exit(-1);
11 }
12
13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
14 {
15 int n;
16
17 again:
18 if ((n = accept(fd, sa, salenptr)) < 0) {
19 if ((errno == ECONNABORTED) || (errno == EINTR))
20 goto again;
21 else
22 perr_exit("accept error");
23 }
24 return n;
25 }
26
27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
28 {
29 int n;
30
31 if ((n = bind(fd, sa, salen)) < 0)
32 perr_exit("bind error");
33
34 return n;
35 }
36
37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
38 {
39 int n;
40
41 if ((n = connect(fd, sa, salen)) < 0)
42 perr_exit("connect error");
43
44 return n;
45 }
46
47 int Listen(int fd, int backlog)
48 {
49 int n;
50
51 if ((n = listen(fd, backlog)) < 0)
52 perr_exit("listen error");
53
54 return n;
55 }
56
57 int Socket(int family, int type, int protocol)
58 {
59 int n;
60
61 if ((n = socket(family, type, protocol)) < 0)
62 perr_exit("socket error");
63
64 return n;
65 }
66
67 ssize_t Read(int fd, void *ptr, size_t nbytes)
68 {
69 ssize_t n;
70
71 again:
72 if ( (n = read(fd, ptr, nbytes)) == -1) {
73 if (errno == EINTR)
74 goto again;
75 else
76 return -1;
77 }
78 return n;
79 }
80
81 ssize_t Write(int fd, const void *ptr, size_t nbytes)
82 {
83 ssize_t n;
84
85 again:
86 if ( (n = write(fd, ptr, nbytes)) == -1) {
87 if (errno == EINTR)
88 goto again;
89 else
90 return -1;
91 }
92 return n;
93 }
94
95 int Close(int fd)
96 {
97 int n;
98 if ((n = close(fd)) == -1)
99 perr_exit("close error");
100
101 return n;
102 }
103
104 /*参三: 应该读取的字节数*/
105 ssize_t Readn(int fd, void *vptr, size_t n)
106 {
107 size_t nleft; //usigned int 剩余未读取的字节数
108 ssize_t nread; //int 实际读到的字节数
109 char *ptr;
110
111 ptr = vptr;
112 nleft = n;
113
114 while (nleft > 0) {
115 if ((nread = read(fd, ptr, nleft)) < 0) {
116 if (errno == EINTR)
117 nread = 0;
118 else
119 return -1;
120 } else if (nread == 0)
121 break;
122
123 nleft -= nread;
124 ptr += nread;
125 }
126 return n - nleft;
127 }
128
129 ssize_t Writen(int fd, const void *vptr, size_t n)
130 {
131 size_t nleft;
132 ssize_t nwritten;
133 const char *ptr;
134
135 ptr = vptr;
136 nleft = n;
137 while (nleft > 0) {
138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
139 if (nwritten < 0 && errno == EINTR)
140 nwritten = 0;
141 else
142 return -1;
143 }
144
145 nleft -= nwritten;
146 ptr += nwritten;
147 }
148 return n;
149 }
150
151 static ssize_t my_read(int fd, char *ptr)
152 {
153 static int read_cnt;
154 static char *read_ptr;
155 static char read_buf[100];
156
157 if (read_cnt <= 0) {
158 again:
159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
160 if (errno == EINTR)
161 goto again;
162 return -1;
163 } else if (read_cnt == 0)
164 return 0;
165 read_ptr = read_buf;
166 }
167 read_cnt--;
168 *ptr = *read_ptr++;
169
170 return 1;
171 }
172
173 ssize_t Readline(int fd, void *vptr, size_t maxlen)
174 {
175 ssize_t n, rc;
176 char c, *ptr;
177
178 ptr = vptr;
179 for (n = 1; n < maxlen; n++) {
180 if ( (rc = my_read(fd, &c)) == 1) {
181 *ptr++ = c;
182 if (c == '\n')
183 break;
184 } else if (rc == 0) {
185 *ptr = 0;
186 return n - 1;
187 } else
188 return -1;
189 }
190 *ptr = 0;
191
192 return n;
193 }
wrap.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 <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 }
client.c
1 /* client.c */
2 #include <stdio.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7
8 #include "wrap.h"
9
10 #define MAXLINE 8192
11 #define SERV_PORT 8000
12
13 int main(int argc, char *argv[])
14 {
15 struct sockaddr_in servaddr;
16 char buf[MAXLINE];
17 int sockfd, n;
18
19 sockfd = Socket(AF_INET, SOCK_STREAM, 0);
20
21 bzero(&servaddr, sizeof(servaddr));
22 servaddr.sin_family = AF_INET;
23 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
24 servaddr.sin_port = htons(SERV_PORT);
25
26 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
27
28 while (fgets(buf, MAXLINE, stdin) != NULL)
29 {
30 Write(sockfd, buf, strlen(buf));
31 n = Read(sockfd, buf, MAXLINE);
32 if (n == 0)
33 {
34 printf("the other side has been closed.\n");
35 break;
36 }
37 else
38 Write(STDOUT_FILENO, buf, n);
39 }
40
41 Close(sockfd);
42
43 return 0;
44 }
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 <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/socket.h>
6
7 void perr_exit(const char *s)
8 {
9 perror(s);
10 exit(-1);
11 }
12
13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
14 {
15 int n;
16
17 again:
18 if ((n = accept(fd, sa, salenptr)) < 0) {
19 if ((errno == ECONNABORTED) || (errno == EINTR))
20 goto again;
21 else
22 perr_exit("accept error");
23 }
24 return n;
25 }
26
27 int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
28 {
29 int n;
30
31 if ((n = bind(fd, sa, salen)) < 0)
32 perr_exit("bind error");
33
34 return n;
35 }
36
37 int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
38 {
39 int n;
40
41 if ((n = connect(fd, sa, salen)) < 0)
42 perr_exit("connect error");
43
44 return n;
45 }
46
47 int Listen(int fd, int backlog)
48 {
49 int n;
50
51 if ((n = listen(fd, backlog)) < 0)
52 perr_exit("listen error");
53
54 return n;
55 }
56
57 int Socket(int family, int type, int protocol)
58 {
59 int n;
60
61 if ((n = socket(family, type, protocol)) < 0)
62 perr_exit("socket error");
63
64 return n;
65 }
66
67 ssize_t Read(int fd, void *ptr, size_t nbytes)
68 {
69 ssize_t n;
70
71 again:
72 if ( (n = read(fd, ptr, nbytes)) == -1) {
73 if (errno == EINTR)
74 goto again;
75 else
76 return -1;
77 }
78 return n;
79 }
80
81 ssize_t Write(int fd, const void *ptr, size_t nbytes)
82 {
83 ssize_t n;
84
85 again:
86 if ( (n = write(fd, ptr, nbytes)) == -1) {
87 if (errno == EINTR)
88 goto again;
89 else
90 return -1;
91 }
92 return n;
93 }
94
95 int Close(int fd)
96 {
97 int n;
98 if ((n = close(fd)) == -1)
99 perr_exit("close error");
100
101 return n;
102 }
103
104 /*参三: 应该读取的字节数*/
105 ssize_t Readn(int fd, void *vptr, size_t n)
106 {
107 size_t nleft; //usigned int 剩余未读取的字节数
108 ssize_t nread; //int 实际读到的字节数
109 char *ptr;
110
111 ptr = vptr;
112 nleft = n;
113
114 while (nleft > 0) {
115 if ((nread = read(fd, ptr, nleft)) < 0) {
116 if (errno == EINTR)
117 nread = 0;
118 else
119 return -1;
120 } else if (nread == 0)
121 break;
122
123 nleft -= nread;
124 ptr += nread;
125 }
126 return n - nleft;
127 }
128
129 ssize_t Writen(int fd, const void *vptr, size_t n)
130 {
131 size_t nleft;
132 ssize_t nwritten;
133 const char *ptr;
134
135 ptr = vptr;
136 nleft = n;
137 while (nleft > 0) {
138 if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
139 if (nwritten < 0 && errno == EINTR)
140 nwritten = 0;
141 else
142 return -1;
143 }
144
145 nleft -= nwritten;
146 ptr += nwritten;
147 }
148 return n;
149 }
150
151 static ssize_t my_read(int fd, char *ptr)
152 {
153 static int read_cnt;
154 static char *read_ptr;
155 static char read_buf[100];
156
157 if (read_cnt <= 0) {
158 again:
159 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
160 if (errno == EINTR)
161 goto again;
162 return -1;
163 } else if (read_cnt == 0)
164 return 0;
165 read_ptr = read_buf;
166 }
167 read_cnt--;
168 *ptr = *read_ptr++;
169
170 return 1;
171 }
172
173 ssize_t Readline(int fd, void *vptr, size_t maxlen)
174 {
175 ssize_t n, rc;
176 char c, *ptr;
177
178 ptr = vptr;
179 for (n = 1; n < maxlen; n++) {
180 if ( (rc = my_read(fd, &c)) == 1) {
181 *ptr++ = c;
182 if (c == '\n')
183 break;
184 } else if (rc == 0) {
185 *ptr = 0;
186 return n - 1;
187 } else
188 return -1;
189 }
190 *ptr = 0;
191
192 return n;
193 }
wrap.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 <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 }
client.c
1 /* client.c */
2 #include <stdio.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include "wrap.h"
8
9 #define MAXLINE 80
10 #define SERV_PORT 8000
11
12 int main(int argc, char *argv[])
13 {
14 struct sockaddr_in servaddr;
15 char buf[MAXLINE];
16 int sockfd, n;
17
18 sockfd = Socket(AF_INET, SOCK_STREAM, 0);
19
20 bzero(&servaddr, sizeof(servaddr));
21 servaddr.sin_family = AF_INET;
22 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr);
23 servaddr.sin_port = htons(SERV_PORT);
24
25 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
26
27 while (fgets(buf, MAXLINE, stdin) != NULL)
28 {
29 Write(sockfd, buf, strlen(buf));
30 n = Read(sockfd, buf, MAXLINE);
31 if (n == 0)
32 printf("the other side has been closed.\n");
33 else
34 Write(STDOUT_FILENO, buf, n);
35 }
36
37 Close(sockfd);
38
39 return 0;
40 }
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工作流程的理解:
- 点赞
- 收藏
- 关注作者
评论(0)