linux网络编程-并发服务器

并发服务器

并发服务器

多进程并发服务器

使用多进程并发服务器时要考虑以下几点:

  • 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)

  • 系统内创建进程个数(与内存大小相关)

  • 进程创建过多是否降低整体服务性能(进程调度)

框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//框架
Socket(); //创建监听套接字lfd
Bind(); //绑定服务器地址结构
Listen(); //设置监听上限
while(1){
cfd=Accept();
pid=fork();
if(pid==0){
close(lfd); //子进程用不到lfd
read(cfd);
数据处理;
write(cfd);
}else if(pid>0){
close(cfd); //父进程用不到cfd
}
}
/*
子进程
close(lfd)
read()
数据处理
wirte()

父进程
注册信号捕捉函数:SIGNAL
在回调函数中完成子进程回收:while(waitpid())
*/

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 800

/*信号捕捉函数:回收子进程*/
void do_sigchild(int num)
{
while (waitpid(0, NULL, WNOHANG) > 0)
;
}

int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
pid_t pid;

struct sigaction newact;
newact.sa_handler = do_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGCHLD, &newact, NULL);

listenfd = Socket(AF_INET, SOCK_STREAM, 0);
/*服务器地址结构*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
/*绑定服务器地址结构*/
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
/*设置监听上限*/
Listen(listenfd, 20);

printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

pid = fork();
if (pid == 0) {
Close(listenfd);
while (1) {
n = Read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(connfd, buf, n);
}
Close(connfd);
return 0;
} else if (pid > 0) {
Close(connfd);
} else
perr_exit("fork");
}
Close(listenfd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;

sockfd = Socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);

Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
} else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}

多线程并发服务器

在使用线程模型开发服务器时需考虑以下问题:

  • 调整进程内最大文件描述符上限

  • 线程如有共享数据,考虑线程同步

  • 服务于客户端线程退出时,退出处理。(退出值,分离态)

  • 系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU

思路分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Socket();		//创建监听套接字lfd
Bind(); //绑定服务器地址结构
Listen(); //设置监听上限
while(1){
cfd=Accept(lfd,);
pthread_create(&tid,NULL,&tfn,NULL);
/*
*detach设置线程分离,但是这样不能获取线程退出状态
*如果想获取子线程退出状态,用pthread_join()函数,但是这样会造成主线程阻塞
*解决方案:create出一个新的子线程调用pthread_join()专门用于回收
*/
pthread_detach(tid);
}

//子线程:
void* tfn(void* arg){
close(lfd);
read(cfd,);
数据处理;
write(cfd,);
pthread_exit((void*)out); //线程退出状态
}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666

/*将客户端的地址结构和对应的套接字封装到一个结构体中用于向子线程传参*/
struct s_info {
struct sockaddr_in cliaddr;
int connfd;
};

/*子线程的回调函数,注意参数类型*/
void *do_work(void *arg)
{
int n,i;
/*将参数接收下来*/
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
/* 可以在创建线程前设置线程创建属性,设为分离态,*/
pthread_detach(pthread_self());
while (1) {
n = Read(ts->connfd, buf, MAXLINE);
/*读到0,说明客户端已经断开连接*/
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
/*打印客户端的信息*/
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(ts->connfd, buf, n);
}
/*从循环跳出时,关闭套接字,退出线程*/
Close(ts->connfd);
}

int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
int i = 0;
pthread_t tid;
struct s_info ts[256];
/*创建监听套接字*/
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
/*初始化服务器地址结构*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

/*绑定服务器地址结构并设置监听上限*/
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);

printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
/*拿到客户端信息后,填写到结构体中*/
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
i++;
}
return 0;
}