多进程并发服务器
服务器server执行accept函数等待客户端A,B,C,D与之建立连接,假设客户端A与服务器建立连接时,服务器在就创建一个子进程,子进程负责与客户端A进行数据交互, (如果其他客户端再与服务器进行连接,而服务器忙着与A进行数据交互,则不会顾及其他客户端,这也是他的缺点),而父进程则继续监听其他客户端,当其他客户端进行连接时,服务器再创建一个新的子进程与之进行交互.
当父进程与与客户端连接完成后,会产生一个cfd和lfd , 当fork()产生一个子进程时,则会将这个cfd与lfd 继承,cfd可以用于与客户端进行数据交互,而lfd对于子进程来说并没有用处,所以子进程要将继承的lfd关闭.对于父进程来说父进程不需要与其他客户端进行数据交互,所以父进程需要关闭cfd.
子进程完成与客户端的数据交互后,子进程会退出变成僵尸进程,所以父进程要对子进程进行回收.可以用回调机制,利用信号进行处理:利用SIGCHLD注册信号捕捉函数,在捕捉函数内调用waitpid()回收子进程
使用多进程并发服务器时要考虑以下几点:
- 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
- 系统内创建进程个数(与内存大小相关)
- 进程创建过多是否降低整体服务性能(进程调度)
多线程高并发服务器实现将字符串转化大写
客户端往服务器端发送字符串,服务器把字符串转换成大写之后再发送回给客户端。
大致流程:
server.cpp
#include <iostream>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <strings.h>
#include <assert.h>
using namespace std;
#define MAXLINE 4096
#define SERV_PORT 8000
//信号捕捉函数,信号:SIGCHLD:当子进程状态发生变化(退出或暂停)时产生
void do_sigchild(int num){
while(waitpid(0,NULL,WNOHANG) > 0){
//回收子进程
}
}
int main(){
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int listenfd,connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
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);
assert(listenfd >= 0);
//重用本地地址,socket之后bind之前
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
int ret = bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
assert(ret != -1);
ret = listen(listenfd,20);
assert(ret != -1);
cout << "Accepting connections ..." << endl;
while(1){
connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
pid_t pid = fork();
if(pid == 0){
close(listenfd);//子进程将继承得来的listenfd关闭
while(1){
int n = read(connfd,buf,MAXLINE);//读取客户端数据
if(n == 0){
cout << "the other side has been closed. " << endl;
break;
}
//输出客户端IP地址和PORT
cout << "Received from "
<< inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str))
<< " at POTR "
<< ntohs(cliaddr.sin_port)
<< endl;
//将小写转换为大写
for(int i = 0;i < n;i++){
buf[i] = toupper(buf[i]);
}
write(STDOUT_FILENO,buf,n);//输出到服务器的屏幕
write(connfd,buf,n);//输出到客户端
}
close(connfd);
return 0;
}else if(pid > 0){
close(connfd);//父进程不需要与客户端进行数据交互,所以关闭connfd
}else{
perror("fork error");
}
}
return 0;
}
client.cpp
#include <iostream>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <strings.h>
#include <string.h>
using namespace std;
#define SERV_PORT 8000
#define MAXLINE 4096
int main(){
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd >= 0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
int ret = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(sockaddr));
if(ret < 0){
perror("connect error ");
exit(1);
}
while(fgets(buf,MAXLINE,stdin) != NULL){
//读取键盘数据
write(sockfd,buf,strlen(buf));//将数据写到服务器
int n = read(sockfd,buf,MAXLINE);//从服务器读取数据
if(n == 0){
cout << "the other side has been closed" << endl;
break;
}else{
write(STDOUT_FILENO,buf,n);//将从服务器读到的数据显示到客户端屏幕上
}
}
close(sockfd);
return 0;
}