C++网络通信:基于多进程、多线程实现的TCP并发服务器

C++实现TCP并发服务器:多进程与多线程

并发服务器的引入

对于 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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值