1.1 多进程并发服务器
1.1 使用多进程并发服务器考虑因素
- 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
- 系统内创建进程个数(与内存大小相关)
- 进程创建过多是否降低整体服务性能(进程调度)
1.2 多进程并发服务器实现 demo (将客户端小写转大写发送)
多进程并发服务器实现总体逻辑:
- socket 创建 listenfd 套接字用来监听连接
- 服务器地址初始化后, bind 绑定服务器地址结构
- listen 设置同时与服务器连接的上限数
- 循环 accept 与客户端建立套接字 cfd
- fork 创建父子进程
子进程:用来与客户端连接,进行读写操作
- close 关闭 listenfd 监听套接字
- read 读
- toupper 实现小写变大写转换
- write 写
- break 子进程跳出创建连接循环
父进程:
- close 关连接套接字 cfd,以保证子进程正常通信
- 注册信号捕捉函数,捕捉 SIGCHLD 信号回收子进程(防止僵尸进程的产生)
- continue 进入下一轮循环, lfd等待监听
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<errno.h>
#include<signal.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/wait.h>
#define SRV_PORT 9537
void catch_child(int signum)
{
while(waitpid(0, NULL, WNOHANG) > 0);
return ;
}
int main(int argc, char * argv [ ])
{
int lfd, cfd;
int ret, i;
//int opt = 1;
char buf[BUFSIZ], client_IP[1024];
pid_t pid;
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
clt_addr_len = sizeof(clt_addr);
//memset(&srv_addr, 0, sizeof(srv_addr)); // 设置端口复用
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET; // 初始化服务器地址
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET, SOCK_STREAM, 0);
//setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt)); // 设置端口复用
bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
listen(lfd, 128);
while (1)
{
cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len); // 建立连接 cfd
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),
ntohs(clt_addr.sin_port));
pid = fork();
if(pid < 0)
{
perror("fork error\n");
exit(1);
}
if (pid == 0) // 子进程
{
close(lfd);
while(1)
{
ret = read(cfd, buf, sizeof(buf));
if(ret == 0)
{
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
break;
}
else // 父进程
{
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL); // 注册信号捕捉
if(ret != 0)
{
perror("sigaction errno\n");
exit(1);
}
close(cfd);
continue;
}
}
return 0;
}
1.2 多线程并发服务器
1.2.1 使用多线程并发服务器注意事项:
- 调整进程内最大文件描述符上限
- 线程如有共享数据,考虑线程同步
- 服务于客户端线程退出时,退出处理。(退出值,分离态)
- 系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
1.2.2 实现多线程并发服务器 demo
多线成并发服务器实现总体逻辑:
- socket 创建 listenfd 套接字用来监听
- 初始化服务器端的地址结构,并用 bind 来绑定
- listen 设置同时监听上限
- 循环 accept 阻塞监听建立连接
- 将连接的 connfd 客户端信息传递给线程回调函数
线程回调函数:
- 循环 read 内容
- toupper 小写转大写
- write 写
- close 关闭连接, 线程结束,分离
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<sys/socket.h>
#include<ctype.h>
#include<arpa/inet.h>
#define SERV_PORT 9537 // 服务器通信端口号
struct s_info{
// 结构体 用来保存与服务器连接的客户端的 地址结构 和 连接套接字的文件描述符
struct sockaddr_in cliaddr;
int connfd;
};
void* do_work(void* arg)
{
int n, i;
struct