我们来看一下之前的代码框架:
while (1){accept(); // 吸收连接while (1){/ 读写处理 /···recv();send();}close(); // 关闭连接}
通过上述框架,我们很随意马虎得出下述结论:
详细过程演示如下:

想要提高技能,或者进互换群学习的小伙伴
私信我:学习
多线程办法实现客户端并发访问通过上述剖析,我们创造了问题所在,即如何能使做事器不断的处理连接要求(即实行accept函数)
那么我们可以利用多线程来并发处理,即每收到一个连接要求,就创建一个新线程来处理它,我们把之前的读写处理模块放入线程实行函数中,即可实现多客户真个并发访问:
// TCP客户端 做事器端通信(多客户机) :多线程实现#include <stdio.h>#include <unistd.h>#include <assert.h>#include <string.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <pthread.h>/ 线程函数,新连接的读写处理 /void thread_fun(void arg){int connfd = (int)arg;while (1){char buffer[128] = { 0 };int n = recv(connfd, buffer, 127, 0);if (n <= 0) / recv返回0表示通信对方已经关闭连接 /{break;}printf(\公众buffer(%d) = %s\n\公众, n, buffer);send(connfd, \公众OK\公众, 2, 0);}printf(\"大众One client over!\n\公众);/ close关闭套接字 /close(connfd);}int main(){/ 1、通过socket函数创建监听套接字 /int sockfd = socket(AF_INET, SOCK_STREAM, 0);assert(sockfd != -1);/ 定义做事器和客户真个专用socket地址/struct sockaddr_in saddr, caddr;memset(&saddr, 0, sizeof(saddr));// 将其内存清空/ 设置地址族、端口号、ip地址 /saddr.sin_family = AF_INET; // saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr(\"大众127.0.0.1\公众);/ 2、通过bind将套接字与socket地址绑定起来 /int res = bind(sockfd, (struct sockaddr) & saddr, sizeof(saddr));assert(res != -1);/ 3、创建监听行列步队,监听行列步队长度设为5 /listen(sockfd, 5);while (1){int len = sizeof(caddr);/ accept监听socket,若存在,则从监听行列步队中接管一个连接 函数返回一个链接套接字,否则,没有新连接要求,则壅塞等待/int connfd = accept(sockfd, (struct sockaddr) & caddr, (socklen_t)& len);/ accept失落败返回-1表示没有获取到连接,连续循环 /if (connfd < 0){continue;}printf(\"大众accept connfd = %d, ip = %s, port = %d\n\公众,connfd, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pthread_t id; // 定义线程号/ 创建新线程,将连接套接字connfd作为参数传入 /pthread_create(&id, NULL, thread_fun, (void)& connfd);}close(sockfd);}
上述程序我们采取了多线程的办法实现了客户真个并发访问,思路便是,每吸收到一个连接要求,就创建一个线程来处理它,实行结果如下:
我们可以看到,第一个连接没有关闭前,第二个客户端到达,也可以收发数据,即我们利用多线程办法实现了客户端对做事器的并发访问。
多进程办法实现客户端并发访问
和上面一样的思路:
我们采取多进程的办法,每次收到连接要求都创建一个子进程,子进程卖力处理读写模块。
// TCP客户端 做事器端通信(多客户机) :多进程实现#include <stdio.h>#include <unistd.h>#include <assert.h>#include <string.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <pthread.h>#include <signal.h>#include <sys/wait.h>void sig_fun(int sig){//int val = 0;while (waitpid(-1, &sig, 0) > 0) {}}int main(){signal(SIGCHLD, sig_fun);/ 通过socket函数创建监听套接字 /int sockfd = socket(AF_INET, SOCK_STREAM, 0);assert(sockfd != -1);/ 定义做事器和客户真个专用socket地址/struct sockaddr_in saddr, caddr;memset(&saddr, 0, sizeof(saddr));// 将其内存清空/ 设置地址族、端口号、ip地址 /saddr.sin_family = AF_INET; // saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr(\"大众127.0.0.1\"大众);/ 通过bind将套接字与socket地址绑定起来 /int res = bind(sockfd, (struct sockaddr) & saddr, sizeof(saddr));assert(res != -1);/ 创建监听行列步队,监听行列步队长度设为5 /listen(sockfd, 5);while (1){int len = sizeof(caddr);/ accept监听socket,若存在,则从监听行列步队中接管一个连接 函数返回一个链接套接字,否则,没有新连接要求,则壅塞等待/int connfd = accept(sockfd, (struct sockaddr) & caddr, (socklen_t)& len);/ accept失落败返回-1表示没有获取到连接,连续循环 /if (connfd < 0){continue;}printf(\公众accept connfd = %d, ip = %s, port = %d\n\"大众,connfd, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));/ 每收到一个连接要求都创建一个子进程 /pid_t pid = fork();if (pid == -1){continue;}else if (pid == 0) // 在子进程中实现读写处理{while (1){char buffer[128] = { 0 };int n = recv(connfd, buffer, 127, 0);if (n <= 0) / recv返回0表示通信对方已经关闭连接 /{break;}printf(\"大众buffer(%d) = %s\n\"大众, n, buffer);send(connfd, \"大众OK\公众, 2, 0);}printf(\"大众One client over!\n\"大众);/ close关闭套接字 /close(connfd);}}close(sockfd);}
运行结果如下:
办理多进程办法下僵尸进程问题
我们可以看到上述程序虽然运行精确,但仍存在问题,即若由一方客户端退出,那么在做事器程序中,就会产生子进程先于父进程结束的情形,即会产生僵尸进程,若客户端非常多,那么这就会产生严重的问题。
我们有以下两种方法办理:
1、通过wait方法,办理僵尸进程
signal(SIGCHLD, sig_fun);
wait(NULL);
2、通过waitpid方法,办理僵尸进程
signal(SIGCHLD, sig_fun);
waitpid(-1, NULL, WNOHANG); // WNOHANG表示该系统调用非壅塞模式
想要提高技能,或者进互换群学习的小伙伴
私信我:学习