之前的套接字通信, 一个服务器只能和一个客户端进行通信, 不能实现并发的效果, 主要原因是:
解决这种问题有多种方法, 我们先来看多线程解决
多线程中的线程有两大类:主线程(父线程)和子线程,他们分别要在服务器端处理监听和通信流程。
在多线程版的服务器端程序中,多个线程共用同一个地址空间,有些数据是共享的,有些数据的独占的
多线程版TCP通信的客户端代码不变, 服务器端代码如下:
//
// Created by 47468 on 2024/1/19.
//
#include <iostream>
#include "arpa/inet.h"
using namespace std;
#include "cstring"
#include "unistd.h"
#include "thread"
struct SockInfo{
int fd;
pthread_t tid;
struct sockaddr_in addr;
};
struct SockInfo infos[128];
// 任务函数
void* worker(void* arg){
struct SockInfo* info = static_cast<SockInfo*>(arg);
// 打印子线程id
cout << "子线程id: " << info->tid << endl;
// 打印客户端的地址信息
char ip[24] = {0};
cout << "客户端的ip地址: " << inet_ntop(AF_INET, &info->addr.sin_addr.s_addr, ip, sizeof(ip))
<< ", 端口: " << ntohs(info->addr.sin_port) << endl;
// 5. 和客户端通信
while(1){
// 接收数据
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = read(info->fd, buf, sizeof(buf));
if(len > 0){
cout << "客户端say: " << buf << endl;
write(info->fd, buf, len);
} else if (len == 0){
cout << "客户端断开了连接" << endl;
break;
} else {
perror("read");
break;
}
}
close(info->fd);
}
int main() {
// 1. 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket");
exit(0);
}
// 2. 将socket()返回值和本地的IP端口绑定到一起
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
// INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
// 这个宏可以代表任意一个IP地址
// 这个宏一般用于本地的绑定操作
addr.sin_addr.s_addr = INADDR_ANY;
// inet_pton(AF_INET, "192.168.237.131", &addr.sin_addr.s_addr);
int ret = bind(lfd, (struct sockaddr *) &addr, sizeof(addr));
if (ret == -1) {
perror("bind");
exit(0);
}
// 3. 设置监听
ret = listen(lfd, 128);
if (ret == -1) {
perror("listen");
exit(0);
}
// 4. 阻塞等待并接受客户端连接
int len = sizeof(sockaddr);
// 数据初始化
int max = sizeof(infos) / sizeof(infos[0]); // 同时最大连接数
for (int i = 0; i < max; ++i) {
memset(&infos[i], 0, sizeof(infos[i]));
infos[i].fd = -1;
infos[i].tid = -1;
}
while (1) {
struct SockInfo* pinfo;
for (int i = 0; i < max; ++i) {
if(infos[i].fd == -1){
pinfo = &infos[i];
break;
}
if(i == max - 1){
sleep(1);
--i;
}
}
int cfd = accept(lfd, (struct sockaddr *) &pinfo->addr, (socklen_t *) (&len));
if (cfd == -1) {
perror("accept");
// exit(0);
break;
}
pinfo->fd = cfd;
pthread_create(&pinfo->tid, nullptr, worker, pinfo);
// 回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept(),直接做线程分离即可。
// 这里用join就不行, 因为用join的话, 子线程不退出, 主线程就会一直阻塞在这里, 无法监听新的连接
pthread_detach(pinfo->tid);
}
// 释放资源
close(lfd);
return 0;
}
测试情况:
就是三个服务器和客户端在通信