文章目录
- (三)多线程TCP服务端程序
- 多线程服务端
- 客户端
(三)多线程TCP服务端程序
图片来源:https://subingwen.cn/linux/concurrence
主线程负责监听和连接多个客户端,子线程负责和对应的客户端进行通信(完成数据的接收和发送)。
服务端与3个客户端连接并进行通信:
多线程服务端
server.cpp
#include <iostream>
#include <thread>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <vector>
// SockInfo 结构体,包含文件描述符、线程ID和地址信息
struct SockInfo {
int fd; // 通信文件描述符
sockaddr_in addr; // 地址信息
};
// 最大连接数128
std::vector<SockInfo> infos(128);
// 线程工作函数,处理客户端通信
void working(SockInfo* info) {
while (true) {
char buf[1024]; // 每一个线程都有一个独立的缓冲区
int len = recv(info->fd, buf, sizeof(buf), 0); // 从客户端读取数据
if (len == 0) {
std::cout << "端口号" << ntohs(info->addr.sin_port)<< "的客户端已经关闭连接..." << std::endl;
info->fd = -1; // 关闭连接,释放资源
break;
} else if (len == -1) {
std::cerr << "接收数据失败..." << std::endl;
info->fd = -1;
break;
} else {
std::cout << "端口号" << ntohs(info->addr.sin_port)<< "的客户端: " << buf << std::endl; // 打印客户端发送的消息
sprintf(buf, "你好, 客户端\n"); // 格式化字符串
send(info->fd, buf, strlen(buf), 0); // 回应客户端
}
}
}
int main() {
// 1. 创建用于监听的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 绑定套接字
sockaddr_in addr;
addr.sin_family = AF_INET; // ipv4
addr.sin_port = htons(10000); // 设置端口号并转换为网络字节序
inet_pton(AF_INET, "172.31.108.107", &addr.sin_addr.s_addr); // 指定IP地址
int ret = bind(fd, (sockaddr*)(&addr), sizeof(addr));
if (ret == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 3. 设置监听(主线程)
ret = listen(fd, 100);
if (ret == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
// 4. 等待连接请求
socklen_t len = sizeof(sockaddr);
// 初始化连接信息
for (auto& info : infos) {
info.fd = -1;
}
while (true) {
// 查找一个可用的 SockInfo
SockInfo* pinfo = nullptr;
for (auto& info : infos) {
if (info.fd == -1) {
pinfo = &info;
break;
}
}
if (!pinfo) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待空闲连接位
continue;
}
// 5. 接受客户端连接(主线程)
int connfd = accept(fd, reinterpret_cast<sockaddr*>(&pinfo->addr), &len);
std::cout << "parent thread, connfd: " << connfd << std::endl;
if (connfd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
pinfo->fd = connfd; // 保存客户端的文件描述符
// 6. 创建子线程与客户端进行通信
std::thread t(working, pinfo);
// 7. 分离线程
t.detach();
}
// 关闭监听套接字
close(fd);
return 0;
}
编译和运行:
g++ server.cpp -o server -pthread
./server
客户端
client.cpp
#include <iostream> // std::cout, std::cerr
#include <cstdlib> // std::exit
#include <unistd.h> // close, sleep
#include <cstring> // memset, strlen
#include <arpa/inet.h> // socket, connect, inet_pton, htons
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个TCP套接字
if (fd == -1)
{
perror("socket"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 2. 连接服务器
sockaddr_in addr; // 用于存储服务器地址信息
addr.sin_family = AF_INET; // 地址族,IPv4
addr.sin_port = htons(10000); // 大端端口转换
inet_pton(AF_INET, "172.31.108.107", &addr.sin_addr.s_addr); // 将IP地址转换为网络字节顺序
int ret = connect(fd, (sockaddr*)&addr, sizeof(addr)); // 连接到服务器
if (ret == -1)
{
perror("connect"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 3. 和服务器端通信
int number = 0;
while (true)
{
// 发送数据
char buf[1024]; // 数据缓冲区
sprintf(buf, "你好, 服务器...%d", number++); // 格式化字符串
send(fd, buf, strlen(buf), 0); // 发送数据
// 接收数据
memset(buf, 0, sizeof(buf)); // 清空缓冲区
int len = recv(fd, buf, sizeof(buf), 0); // 从服务器读取数据
if (len > 0)
{
std::cout << "服务器: " << buf; // 打印服务器发送的消息
}
else if (len == 0)
{
std::cout << "服务器断开了连接..." << std::endl; // 服务器断开连接
break;
}
else
{
perror("recv"); // 错误处理
break;
}
sleep(1); // 每隔1秒发送一条数据
}
close(fd); // 关闭套接字
return 0;
}
编译和运行:
g++ client.cpp -o client
./client