如下图所示, 当一个客户端与服务器建立连接以后,服务器端 accept()返回,进而准备循环接收客户端发过来的数据。
如果客户端暂时没发数据,服务端会在 recv()阻塞。此时,其他客户端向服务器发起连接后,由于服务器阻塞了,无法执行 accept()接受连接,也就是其他客户端发送的数据,服务器无法读取。服务器也就无法并发同时处理多个客户端。
这个问题可以通过引入多线程和多进程来解决。
服务端接受一个客户端的连接后,创建一个线程或者进程,然后在新创建的线程或进程中循环处理数据。主线程(父进程)只负责监听客户端的连接,并使用 accept()接受连接,不进行数据的处理。如下图所示:
多线程处理并发的服务器端示例代码 MultiThread.c 如下:
#include <stdio.h> // 引入标准输入输出头文件
#include <stdlib.h> // 引入标准库头文件
#include <unistd.h> // 引入Unix标准函数定义头文件
#include <string.h> // 引入字符串处理头文件
#include <assert.h> // 引入断言头文件
#include <sys/socket.h> // 引入套接字接口头文件
#include <netinet/in.h> // 引入互联网地址族头文件
#include <arpa/inet.h> // 引入互联网定义头文件
#include <pthread.h> // 引入POSIX线程头文件
void* fun(void * arg) // 线程函数
{
int c = (int)arg; // 客户端套接字
while( 1 ) // 持续处理客户端消息
{
char buff[128] = {0}; // 消息缓冲区
if ( recv(c,buff,127,0) <= 0 ) // 接收消息
{
break; // 接收失败或连接关闭则退出循环
}
printf("recv(%d)=%s\n",c,buff); // 打印接收到的消息
send(c,"ok",2,0); // 向客户端发送响应
}
printf("one client over(%d)\n",c); // 打印客户端结束信息
close(c); // 关闭客户端套接字
}
int main() // 主函数
{
int sockfd = socket(AF_INET,SOCK_STREAM,0); // 创建套接字
assert( sockfd != -1 ); // 断言套接字创建成功
struct sockaddr_in saddr,caddr; // 服务器和客户端地址结构
memset(&saddr,0,sizeof(saddr)); // 初始化服务器地址结构
saddr.sin_family = AF_INET; // 设置地址族为IPv4
saddr.sin_port = htons(6000); // 设置端口号
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr)); // 绑定地址
assert( res != -1 ); // 断言绑定成功
listen(sockfd, 5); // 监听套接字
while( 1 ) // 持续接受客户端连接
{
int len = sizeof(caddr); // 客户端地址长度
int c = accept(sockfd,(struct sockaddr*)&caddr,&len); // 接受客户端连接
if ( c < 0 )
{
continue; // 接受失败则继续
}
printf("accept c = %d\n",c); // 打印接受的客户端套接字描述符
pthread_t id; // 线程ID
pthread_create(&id,NULL,fun,(void*)c); // 创建线程处理客户端
}
close(sockfd); // 关闭服务器套接字
exit(0); // 退出程序
}
执行结果如下所示:

多进程处理并发的服务器端示例代码 MultiProcess.c 如下:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
void DealClientLink(int c, struct sockaddr_in caddr)
{
while(1)
{
char buff[128] = {0};
int n = recv(c, buff, 127, 0); // 从客户端接收数据
if(n <= 0)
{
break; // 如果接收失败或客户端断开连接,则退出循环
}
printf("%s:%d %s\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buff); // 打印客户端IP、端口和消息
send(c, "OK", 2, 0); // 向客户端发送响应消息
}
printf("one client unlink\n"); // 打印客户端断开连接的消息
close(c); // 关闭与客户端的连接
}
void sigfun(int sign)
{
wait(NULL); // 处理僵尸进程
}
int main()
{
signal(SIGCHLD, sigfun); // 设置信号处理函数,处理子进程结束信号,防止僵尸进程
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
assert(-1 != sockfd); // 确保套接字创建成功
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr)); // 初始化地址结构
saddr.sin_family = AF_INET; // 设置地址类型为IPv4
saddr.sin_port = htons(6000); // 设置端口号为6000
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址为本地地址
int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定套接字
assert(-1 != res); // 确保绑定成功
listen(sockfd, 5); // 监听套接字,最大连接数为5
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd, (struct sockaddr*)&caddr, &len); // 接受客户端连接
assert(-1 != c); // 确保接受连接成功
printf("%s:%d Link Success\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port)); // 打印客户端IP和端口
pid_t pid = fork(); // 创建子进程
assert(-1 != pid); // 确保子进程创建成功
if(0 == pid)
{
DealClientLink(c, caddr); // 子进程处理客户端连接
exit(0); // 子进程结束
}
else
{
close(c); // 父进程关闭客户端套接字
}
}
close(sockfd); // 关闭服务器套接字
exit(0); // 退出程序
}
客户端代码 TcpClient.c 如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
assert(sockfd != -1); // 确保套接字创建成功
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr)); // 初始化地址结构
saddr.sin_family = AF_INET; // 设置地址类型为IPv4
saddr.sin_port = htons(6000); // 设置端口号为6000
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址为本地地址
int res = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 连接服务器
assert(res != -1); // 确保连接成功
while(1)
{
char buff[128] = {0};
printf("input:\n"); // 提示用户输入
fgets(buff, 128, stdin); // 获取用户输入
if(strncmp(buff, "end", 3) == 0) // 如果输入为"end",则退出循环
{
break;
}
send(sockfd, buff, strlen(buff), 0); // 发送数据到服务器
memset(buff, 0, 128); // 清空缓冲区
recv(sockfd, buff, 127, 0); // 接收服务器响应
printf("buff=%s\n", buff); // 打印服务器响应
}
close(sockfd); // 关闭套接字
}
执行结果如下所示: