基于线程池的TCP套接字通信
还是只改变server.cpp
其中main
函数, 也就是主线程中的处理流程:
- 创建监听的套接字
- 绑定IP和端口
- 设置监听
- 创建线程池实例对象
- 添加监听任务
acceptConn
- 主线程退出
监听任务函数的处理流程如下:
- 阻塞等待并接受客户端连接
- 检测有客户端连接时, 添加通信任务
worker
- 释放资源
通信任务函数的处理流程:
- 打印客户端的信息
- 和客户端通信
- 释放资源
其中添加监听和通信任务函数的时候的参数问题需要创建两个结构体, 具体细节在代码中已经体现出来了
cpp
//
// Created by 47468 on 2024/1/22.
//
#include <iostream>
#include "arpa/inet.h"
using namespace std;
#include "cstring"
#include "unistd.h"
#include "pthread.h"
#include "ThreadPool.h"
// 监听任务函数传递给通信任务函数参数时用到的结构体
struct SockInfo{
int fd; // 通信描述符
pthread_t tid; // 子线程的线程id
struct sockaddr_in addr; // 把客户端的ip和端口信息传递给通信任务函数
};
// 主函数传递给监听任务函数参数时用到的结构体
struct PoolInfo{
ThreadPool* pool; // 主线程中创建的线程池实例
int fd; // 监听描述符
};
// 函数声明
void acceptConn(void* arg);
void worker(void* arg);
int main() {
// 1. 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket");
exit(0);
}
// 2. 将socket()返回值和本地的IP端口绑定到一起
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);
}
// 创建线程池
auto* pool = new ThreadPool(3, 10);
auto* poolInfo = new PoolInfo;
poolInfo->fd = lfd;
poolInfo->pool = pool;
// 添加监听任务
pool->addTask(Task(acceptConn, poolInfo));
pthread_exit(nullptr);
}
// 监听任务函数
void acceptConn(void* arg){
auto* poolInfo = static_cast<PoolInfo*>(arg);
// 4. 阻塞等待并接受客户端连接
int len = sizeof(sockaddr);
while (true) {
auto* pinfo = new SockInfo;
pinfo->fd = accept(poolInfo->fd, (struct sockaddr *) &pinfo->addr, (socklen_t *) (&len));
if (pinfo->fd == -1) {
perror("accept");
// exit(0);
break;
}
// 添加通信任务
poolInfo->pool->addTask(Task(worker, pinfo));
}
// 释放资源
close(poolInfo->fd);
}
// 通信任务函数
void worker(void* arg){
auto* 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(true){
// 接收数据
char buf[1024];
memset(buf, 0, sizeof(buf));
auto 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);
}
客户端的代码和线程池代码在之前的文章里已经有了, 都不变
测试情况:
实现了一台服务器和4个客户端的通信(最多的时候5个线程), 因为我们输入参数中最少三个线程, 所以在最后只删除了两个线程, 留有三个live线程, 其中一个主线程是busy线程, 还有两个线程没事做