并发服务器的引入
对于
tcp
通信方式而言,目前的通信方式,只能实现一个服务器端对应一个客户端,不能实现一个服务器对应多个客户端,为了完成该操作,我们需要引入并发操作。
关于TCP通信的实现,请查看:C++网络通信:基于TCP面向连接的通信方式
一、TCP循环服务器
我们要实现一个可以实现多个客户端的TCP服务器,我们可以在accept加上循环操作,让服务端不断接收连接请求。
#include<myhead.h>
#define SER_IP "192.168.137.140"
#define SER_PORT 8888
int main(int argc, const char *argv[]){
//创建套接字文件描述符
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1){
perror("socket error");
return -1;
}
printf("socket success, sfd = %d\n",sfd);
//创建并填充服务端的地址信息结构体
sockaddr_in sin;
sin.sin_addr.s_addr=inet_addr(SER_IP);
sin.sin_family=AF_INET;
sin.sin_port=htons(SER_PORT);
//绑定IP和端口号
if(bind(sfd,(sockaddr*)&sin,sizeof(sin))==-1){
perror("bind error");
return -1;
}
printf("bind success\n");
//创建监听
if(listen(sfd,128)==-1){
perror("listen error");
return -1;
}
printf("listen success\n");
while(1){ //循环接收连接
sockaddr_in cin;
socklen_t len=sizeof(cin);
int newfd=accept(sfd,(sockaddr*)&cin,&len);
if(newfd==-1){
perror("accepct error");
return -1;
}
printf("[%s,%d]:connected\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
//循环实现数据收发
char buf[128]="";
while(1){
bzero(buf,sizeof(buf));
int res=recv(newfd,buf,sizeof(buf),0);
if(res==0){
printf("[%s,%d]:leave\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
break;
}
printf("[%s,%d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
strcat(buf,"*_*");
if(send(newfd,buf,strlen(buf),0)==-1){
perror("send error");
return -1;
}
}
//关闭文件描述符
close(newfd);
}
//关闭文件描述符
close(sfd);
return 0;
}
但是,这种方式有很大弊端,执行过程中,会在accept和recv处阻塞,当前一个客户端与服务端进行数据收发时,会处于内层while循环中,新的客户端与服务端请求连接时,服务端难以及时响应,因为此时服务端还处在与前一个客户端的数据收发过程。
二、基于多进程实现的TCP并发服务器
#include<myhead.h>
#define SER_IP "192.168.137.140"
#define SER_PORT 8888
void handler(int sig){
if(sig==SIGCHLD){
/*
while循环回收子进程资源
waitpid(-1, NULL, WNOHANG):
-1:回收任意子进程(类似wait)。
NULL:不获取子进程退出状态。
WNOHANG:非阻塞模式。如果没有僵尸进程,立即返回0。
当有僵尸进程时,waitpid返回其PID(>0),循环继续回收。
当无僵尸进程时,waitpid返回0,循环退出。
错误时返回-1(如无子进程),循环退出。
需要循环回收的原因:
当多个子进程同时退出时,内核可能只发送一次SIGCHLD信号
这时如果只执行一次waitpid,
会导致只能回收一个子进程的资源,
其他子进程资源无法回收
*/
while(waitpid(-1,NULL,WNOHANG)>0);
}
}
int main(int argc, const char *argv[]){
/*
子进程退出时,会向主进程发送一个SIGCHLD信号
这是可以根据该信号回收子进程资源
避免出现僵尸进程,占用资源
*/
if(signal(SIGCHLD,handler)==SIG_ERR){
perror("signal error");
return -1;
}
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1){
perror("socket error");
return -1;
}
printf("socket success, sfd = %d\n",sfd);
sockaddr_in sin;
sin.sin_addr.s_addr=inet_addr(SER_IP);
sin.sin_family=AF_INET;
sin.sin_port=htons(SER_PORT);
if(bind(sfd,(sockaddr*)&sin,sizeof(sin))==-1){
perror("bind error");
return -1;
}
printf("bind success\n");
if(listen(sfd,128)==-1){
perror("listen error");
return -1;
}
printf("listen success\n");
while(1){
sockaddr_in cin;
socklen_t len=sizeof(cin);
int newfd=accept(sfd,(sockaddr*)&cin,&len);
if(newfd==-1){
perror("accepct error");
return -1;
}
//有新的客户端与服务器端连接成功时,会创建一个子进程用于与子进程的通信
pid_t pid=fork();
if(pid>0){ //父进程
/*
由于父进程仍然存在
包括像newfd,sfd等文件描述符
但是父进程并不需要用到newfd
所以我们将其关闭
*/
close(newfd);
}else if(pid==0){
/*
同理,由于子进程会拷贝父进程所有数据
包括newfd,sfd等文件描述符
但是子进程并不需要用到sfd
所以我们将其关闭
*/
close(sfd);
printf("[%s,%d]:connected\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
char buf[128]="";
while(1){
bzero(buf,sizeof(buf));
int res=recv(newfd,buf,sizeof(buf),0);
if(res==0){
printf("[%s,%d]:leave\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
break;
}
printf("[%s,%d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
strcat(buf,"*_*\n");
if(send(newfd,buf,strlen(buf),0)==-1){
perror("send error");
return -1;
}
}
close(newfd);
exit(EXIT_SUCCESS);
}else{
perror("fork error");
return -1;
}
}
close(sfd);
return 0;
}
三、基于多线程实现的TCP并发服务器
#include<myhead.h>
#define SER_IP "192.168.137.140"
#define SER_PORT 8888
struct Info{
int newfd;
sockaddr_in cin;
};
void* task(void* arg){
int newfd=((Info*)arg)->newfd;
sockaddr_in cin=((Info*)arg)->cin;
char buf[128]="";
while(1){
bzero(buf,sizeof(buf));
int res=recv(newfd,buf,sizeof(buf),0);
if(res==0){
printf("[%s,%d]:leave\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
break;
}
printf("[%s,%d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
strcat(buf,"*_*\n");
if(send(newfd,buf,strlen(buf),0)==-1){
perror("send error");
return NULL;
}
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[]){
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1){
perror("socket error");
return -1;
}
printf("socket success, sfd = %d\n",sfd);
sockaddr_in sin;
sin.sin_addr.s_addr=inet_addr(SER_IP);
sin.sin_port=htons(SER_PORT);
sin.sin_family=AF_INET;
if(bind(sfd,(sockaddr*)&sin,sizeof(sin))==-1){
perror("bind error");
return -1;
}
printf("bind success\n");
if(listen(sfd,128)==-1){
perror("listen error");
return -1;
}
printf("listen success\n");
sockaddr_in cin;
socklen_t len=sizeof(cin);
while(1){
int newfd=accept(sfd,(sockaddr*)&cin,&len);
if(newfd==-1){
perror("accept error");
return -1;
}
printf("[%s,%d]:connected\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
pthread_t tid;
Info p{newfd,cin}; //Info可以将数据传到子线程中
//创建一个线程
if(pthread_create(&tid,NULL,task,&p)!=0){
perror("pthread error");
return -1;
}
//将线程设置成分离态,由系统自动回收资源
pthread_detach(tid);
}
return 0;
}
C++实现TCP并发服务器:多进程与多线程

被折叠的 条评论
为什么被折叠?



