TCP网络编程
1.version 0 单进程程序
为了给后面的类做铺垫,先要封装两个功能类:
1.Common.hpp:
1. 枚举类型 ExitCode
enum ExitCode
{OK = 0,USAGE_ERR,SOCKET_ERR,BIND_ERR,LISTEN_ERR,CONNECT_ERR,FORK_ERR
};
- 作用:定义程序退出时的错误码,用于区分不同的错误类型(如套接字创建失败、绑定端口失败等)。
- 值分配:
OK
显式赋值为0
,表示成功。- 其他错误码从
1
开始依次递增(未显式赋值时,枚举值默认比前一个大 1)。
- 使用场景:在函数出错时返回对应的错误码,便于调用者处理异常。
2. 防止拷贝的类 NoCopy
class NoCopy
{
public:NoCopy() {}~NoCopy() {}NoCopy(const NoCopy &) = delete; // 禁用拷贝构造函数const NoCopy &operator=(const NoCopy &) = delete; // 禁用赋值运算符
};
- 设计目的:阻止类的实例被拷贝或赋值,常用于单例模式、资源管理类(如套接字、文件句柄)等不希望被复制的场景。
- 实现方式:
- 使用
= delete
显式删除拷贝构造函数和赋值运算符,这是 C++11 的特性,比传统的声明为私有且不实现更清晰。
- 使用
- 继承使用:其他类可以通过继承
NoCopy
来自动获得防拷贝能力,例如:class Server : public NoCopy { ... }; // Server类无法被拷贝
3. 宏定义 CONV
#define CONV(addr) ((struct sockaddr *)&addr)
- 作用:将
sockaddr_in
结构体指针强制转换为sockaddr *
类型,用于兼容套接字 API(如bind
、accept
等函数需要sockaddr *
类型的参数)。 - 背景:
sockaddr_in
是 IPv4 地址的具体结构体,而sockaddr
是通用的地址结构体(二者内存布局兼容)。- 系统套接字 API 使用
sockaddr *
作为参数类型,因此需要强制转换。
- 使用示例:
struct sockaddr_in addr; bind(sockfd, CONV(addr), sizeof(addr)); // 等价于 (struct sockaddr *)&addr
完整代码实现:
#pragma once#include <iostream>
#include <functional>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread>enum ExitCode
{OK = 0;USAGE_ERR,SOCKET_ERR,BIND_ERR,LISTEN_ERR,CONNECT_ERR,FORK_ERR
};class NoCopy
{public:NoCopy() {}~NoCopy() {}NoCopy(const NoCopy &) = delete;const NoCopy &operator=(const NoCopy &) = delete;
};#define CONV(addr) ((struct sockaddr *)&addr)
2.InetAddr
1. 类的作用与设计目标
InetAddr
类的核心功能是:
- 封装底层网络结构:将系统原生的
sockaddr_in
结构体(IPv4 地址)封装为 C++ 对象。 - 地址格式转换:在二进制格式(网络字节序)和字符串格式(点分十进制)之间转换。
- 字节序转换:处理端口号在网络字节序(大端序)和主机字节序(可能是小端序)之间的转换。
- 线程安全:使用线程安全的函数(如
inet_ntop
)替代旧的非线程安全函数(如inet_ntoa
)。
2. 成员变量
private:struct sockaddr_in _addr; // 网络字节序的地址结构string _ip; // 点分十进制的字符串风格的IP地址uint16_t _port; // 端口号(主机字节序)
sockaddr_in
:Linux/Unix 系统中表示 IPv4 地址的标准结构体,包含:sin_family
:地址族(固定为AF_INET
)。sin_port
:网络字节序的端口号。sin_addr
:网络字节序的 IP 地址(32 位整数)。
3. 构造函数
(1)默认构造函数
InetAddr() {}
- 创建未初始化的
InetAddr
对象,需后续手动赋值。
(2)从 sockaddr_in
构造
InetAddr(struct sockaddr_in &addr) : _addr(addr)
{_port = ntohs(_addr.sin_port); // 网络字节序 → 主机字节序// 使用线程安全的 inet_ntop 替代 inet_ntoachar buffer[64];inet_ntop(AF_INET, &_addr.sin_addr, buffer, sizeof(buffer));_ip = buffer;
}
- 功能:从系统原生的
sockaddr_in
结构体创建InetAddr
对象。 - 关键转换:
ntohs()
:将端口号从网络字节序(大端序)转换为主机字节序。inet_ntop()
:将二进制 IP 地址转换为点分十进制字符串(替代旧的inet_ntoa()
)。
- 线程安全:
inet_ntop
使用局部缓冲区,避免多线程冲突。
(3)从 IP 字符串和端口号构造
InetAddr(string ip, uint16_t port) : _ip(ip), _port(port)
{memset(&_addr, 0, sizedf(_addr)); // 错误:应使用 sizeof(_addr)_addr.sin_family = AF_INET;inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr); // 字符串 → 二进制 IP_addr.sin_port = htons(_port); // 主机字节序 → 网络字节序
- 功能:从点分十进制字符串(如
"192.168.1.1"
)和端口号创建对象。 - 关键转换:
inet_pton()
:将点分十进制字符串转换为二进制 IP 地址。htons()
:将端口号从主机字节序转换为网络字节序。
(4)仅指定端口号的构造函数
InetAddr(uint16_t port) : _port(port)
{memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有可用 IP_addr.sin_port = htons(_port);
}
- 功能:创建绑定到所有可用 IP 地址(
0.0.0.0
)的地址对象,常用于服务器监听。 INADDR_ANY
:特殊值,表示监听所有网络接口。
4. 访问器方法
uint16_t Port() { return _port; } // 获取主机字节序的端口号
std::string Ip() { return _ip; } // 获取点分十进制的 IP 字符串
const struct sockaddr_in &NetAddr() { return _addr; } // 获取底层结构体引用
const struct sockaddr *NetAddrPtr() { return CONV(_addr); } // 转换为通用地址类型
socklen_t NetAddrLen() { return sizeof(_addr); } // 获取地址结构体大小
NetAddrPtr()
:返回sockaddr*
指针,用于兼容系统 API(如bind()
、connect()
)。CONV()
是宏,定义为(struct sockaddr*)&addr
。
5. 运算符重载与字符串表示
bool operator==(const InetAddr &addr)
{return addr._ip == _ip && addr._port == _port;
}std::string StringAddr()
{return _ip + ":" + std::to_string(_port);
}
operator==
:比较两个地址是否相同(IP 和端口均相等)。StringAddr()
:返回地址的字符串表示(如"192.168.1.1:8080"
)。
6. 地址转换函数解析
(1)网络字节序与主机字节序
- 网络字节序:大端序(高位字节在前),用于网络传输。
- 主机字节序:可能是大端序或小端序(如 x86 为小端序),由硬件决定。
- 转换函数:
htons()
:主机字节序 → 网络字节序(16 位,如端口号)。ntohs()
:网络字节序 → 主机字节序(16 位)。htonl()
和ntohl()
:用于 32 位值(如 IP 地址)。
(2)IP 地址格式转换
- 二进制 → 字符串:
inet_ntop(AF_INET, &binary_ip, buffer, size)
。 - 字符串 → 二进制:
inet_pton(AF_INET, ip_string, &binary_ip)
。
完整代码:
#pragma once#include <Commom.hpp>
#include <string>
#include <iostream>
#include <functional>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread>using namespace std;class InetAddr
{public:InetAddr() {}InetAddr(struct sockaddr_in &addr) : _addr(addr){// 网络转主机_port = ntohs(_addr.sin_port); // 网络字节序转主机字节序// 获取IP地址有两种方式:// 1.inet_ntoa()函数,将网络字节序的IP地址转换为点分十进制的字符串风格的IP地址// _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP// 2.inet_ntop()函数,将网络字节序的IP地址转换为字符串风格的IP地址,可以指定地址的格式,如IPv4、IPv6等// 但是在多线程的情况下,由于线程之间共享内存,所以不能直接使用inet_ntoa()函数,因为它会修改全局变量,导致其他线程的结果不确定。// 所以,这里使用inet_ntop()函数,将网络字节序的IP地址转换为字符串风格的IP地址,并保存到_ip成员变量中。// 因为此时的buffer是local变量,所以不会影响其他线程的结果。char buffer[64];inet_ntop(AF_INET, &_addr.sin_addr, buffer, sizeof(buffer));_ip = buffer;}InetAddr(string ip, uint16_t port) : _ip(ip), _port(port){// 主机转网络memset(&_addr, 0, size(_addr)); // 清空地址结构_addr.sin_family = AF_INET; // 地址族为IPv4inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr); // 点分十进制的字符串风格的IP地址转网络字节序的IP地址_addr.sin_port = htons(_port); // 主机字节序转网络字节序}InetAddr(uint16_t port) : _port(port){// 主机转网络memset(&_addr, 0, sizeof(_addr)); // 清空地址结构_addr.sin_family = AF_INET; // 地址族为IPv4_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到任何地址_addr.sin_port = htons(_port); // 主机字节序转网络字节序}uint16_t Port() { return _port; }std::string Ip() { return _ip; }const struct sockaddr_in &NetAddr() { return _addr; }const struct sockaddr *NetAddrPtr(){return CONV(_addr);//#define CONV(addr) ((struct sockaddr*)&addr)}socklen_t NetAddrLen(){return sizeof(_addr);}bool operator==(const InetAddr &addr){return addr._ip == _ip && addr._port == _port;}std::string StringAddr(){return _ip + ":" + std::to_string(_port);}~InetAddr(){}private:struct sockaddr_in _addr; // 网络字节序的地址结构string _ip; // 点分十进制的字符串风格的IP地址uin16_t _port; // 端口号
};
有了上面两个类的铺垫,下面的三段代码就是运行的主逻辑
1.TcpServer.hpp
1. 类的整体结构与功能概述
TcpServer
类是一个单进程 TCP 服务器的实现,主要功能包括:
- 创建监听套接字并绑定端口
- 接收客户端连接
- 处理客户端请求(简单的 echo 回显)
- 类继承自
NoCopy
,防止对象被拷贝
类的成员变量:
private:uint16_t _port; // 端口号int _listtensockfd; // 监听套接字描述符bool _isrunning; // 服务器运行状态
2. 核心方法解析
(1)构造函数与初始化
TcpServer(uint16_t port) : _port(port), _listtensockafd(defaultsockafd), _isrunning(false)
{
}void Init()
{// 1. 创建监听套接字_listtensockafd = socket(AF_INET, SOCK_STREAM, 0);if (_listtensockafd == -1) {cout << "create socket error" << endl;exit(SOCKET_ERR);}// 2. 绑定端口InetAddr local(_port);int n = bind(_listtensocket, local.BetAddrPtr(), local.NetAddrlen()); // 注意拼写错误if (n < 0) {cout << "bind socket error" << endl;exit(BIND_ERR);}// 3. 设置监听状态int m = listen(_listtensockefd, backlong); // 注意拼写错误if (m < 0) {cout << "listen socket error" << endl;exit(LISTEN_ERR);}
}
- 构造函数:初始化端口号、套接字描述符和运行状态。
- Init () 方法:
- 使用
socket()
创建 TCP 监听套接字(SOCK_STREAM
表示 TCP)。 - 使用
InetAddr
类封装本地地址,通过bind()
绑定端口。 - 使用
listen()
将套接字设为监听状态,backlong
为等待连接队列长度。
- 使用
(2)客户端请求处理
void Service(int sockfd, struct sockaddr_in peer)
{InetAddr addr(peer);char buffer[1024];while (true){ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;string echo = "echo:" + buffer;write(sockfd, echo.c_str(), echo.size());}else if (n == 0){cout << "client EXIT" << endl;close(sockfd);break;}else{cout << "read error" << endl;close(sockfd);break;}}
}
- 功能:处理客户端连接的具体逻辑(简单的 echo 服务)。
- 流程:
- 读取客户端数据(
read()
)。 - 若读取成功,添加 "echo:" 前缀后回传给客户端(
write()
)。 - 若客户端关闭连接(
read()
返回 0)或读取失败,关闭套接字并退出循环。
- 读取客户端数据(
(3)服务器运行逻辑
void Run()
{_isrunning = true;while (_isrunning){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listtensockfd, CONV(peer), &len);if (sockfd < 0){continue;}// 单进程处理:同一时间只能处理一个客户端Service(socket, peer); // 注意:应为 sockfd}
}
- 功能:启动服务器,循环接受客户端连接。
- 关键步骤:
- 使用
accept()
阻塞等待客户端连接,返回客户端套接字。 - 调用
Service()
处理客户端请求(单进程模式,同一时间只能处理一个客户端)。
- 使用
完整代码:
#pragma once#include "Common.hpp"
#include <signal.h>
#include <InetAddr.hpp>using namespace std;const static int defaultsockafd = -1; // 默认套接字
const static int backlong = 8; //class TcpServer : public : NoCopy // 防止拷贝
{public:TcpServer(uint16_t port) : _port(port), _listtensockafd(defaultsockafd), _isrunning(false){}void Init(){// 1.和UDP不同,TCP需要绑定端口,所以需要创建监听套接字// 参数:AF_INET表示使用IPv4协议,SOCK_STREAM表示使用TCP协议_listtensockafd = socket(AF_INET, SOCK_STREAM, 0);if (_listtensockfd == -1){cout << "create socket error" << endl;exit(SOCKET_ERR);}// 2.bind都知道的端口,工作交给分装的InetAddrInetAddr local(_port);int n = bind(_listtensocket, local.BetAddrPtr(), local.NetAddrlen());if (n < 0){cout << "bind socket error" << endl;exit(BIND_ERR);}// 3.将监听套接字变为监听状态,等待客户端的连接// 这个操作就相当于打开店门,等待顾客进入int m = listen(_listtensockefd, backlong);if (m < 0){cout << "listen socket error" << endl;exit(LISTEN_ERR);}}// 适合短服务,不适合长连接,长连接,多进程多线程比较合适void Service(int sockfd, struct sockaddr_in peer){InetAddr addr(peer);char buffer[1024];while (true){// 1. 先读取数据// a. n>0: 读取成功// b. n<0: 读取失败// c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipessize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;string echo = "echo:" + buffer;// 2. 再发送数据write(sockfd, echo.c_str(), echo.size());}else if (n == 0){cout << "client EXIT" << endl;close(sockfd);break;}else{cout << "read error" << endl;close(sockfd);break;}}}void Run(){_isrunning = true;while (_isrunning){// 4.等待客户端的连接struct sockaddr_in peer;socklen_t len = sizeof(peer);// 如果没有客户端连接,则阻塞在accept处// 如果有客户端连接,则返回一个新的套接字,用来与客户端通信,所以sockfd就是客户端的套接字int sockfd = accept(_listtensockfd, CONV(peer), &len); // #define CONV(addr) ((struct sockaddr *)&addr)if (sockfd < 0){// 这个顾客不来,我们接待下一个continue;}// 单进程的版本。此时接待顾客只能一个一个接待Service(socket, peer);}}private:uint15_t _port; // 端口号int _listtensockfd; // 监听套接字bool _isrunning; // 是否运行
}
2.TcpServer.cc
程序的核心流程是:
- 检查命令行参数,获取端口号
- 创建 TCP 服务器对象
- 初始化服务器(创建套接字、绑定端口等)
- 启动服务器,开始接受客户端连接
#include "TcpServer.hpp"#include <iosream>using namespace std;// 远程命令执行的功能!
// ./tcpserver port
int main(int argc, char* argv[]){if(argc!=2){cout<<"Usage: "<<argv[0]<<" port"<<endl;return 1;}uint16_t port=stoi(argv[1]);unique_ptr<TcpServer> server=make_unique<TcpServer>(port);server->Init();server->Run();return 0;}
3.TcpClient.cc
1. 程序整体结构与功能
该客户端程序的主要功能是:
- 解析命令行参数,获取服务器 IP 和端口
- 创建 TCP 套接字
- 连接到目标服务器
- 实现简单的交互式通信(发送消息给服务器并接收响应)
2. 关键步骤详解
(1)头文件与命名空间
#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <string>using namespace std;
Common.hpp
:可能包含自定义的公共头文件(如错误码、宏定义等)。InetAddr.hpp
:包含网络地址封装类(用于解析和操作 IP 地址与端口)。iostream
和string
:标准库头文件,用于输入输出和字符串处理。using namespace std;
:使用标准命名空间,简化代码书写。
(2)命令行参数处理
if (argc != 3)
{std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;exit(USAGE_ERR);
}string server_ip = argv[1];
uint16_t server_port = stoi(argv[2]);
- 检查参数数量是否正确(需提供服务器 IP 和端口)。
- 使用
stoi()
将字符串形式的端口号转换为uint16_t
类型,可能抛出异常(需注意错误处理)。
(3)创建 TCP 套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{std::cerr << "create socket error" << std::endl;exit(SOCKFD_ERR);
}
socket()
函数创建 TCP 套接字:AF_INET
:使用 IPv4 协议。SOCK_STREAM
:表示 TCP 流套接字。- 返回值为套接字描述符,出错时返回 - 1。
(4)连接到服务器
InetAddr addr(server_ip, server_port); // 解析服务器地址
int n = connect(sockfd, addr.NetAddrPtr(), addr.NetAddrLen());
if (n < 0)
{std::cerr << "connect error" << std::endl;exit(CONNECT_ERR);
}
- 使用
InetAddr
类封装服务器地址(IP + 端口)。 connect()
函数发起 TCP 连接:- 成功时返回 0,失败返回 - 1。
- 连接过程会经历 TCP 三次握手,若服务器未启动或端口错误,会连接失败。
(5)交互式通信循环
while (true)
{string line;cout << "Please input message to server:";getline(cin, line); // 注意:原代码缺少分号// 发送消息write(sockfd, line.c_str(), line.size());// 接收消息char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;cout << "receive message from server:" << buffer << endl;}
}
- 输入处理:从标准输入读取用户输入的消息。
- 发送消息:使用
write()
将消息发送到服务器。 - 接收响应:使用
read()
读取服务器返回的数据,并打印到控制台。
完整代码:
#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <string>using namespace std;// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;exit(USAGE_ERR);}string server_ip = argv[1];uint16_t server_port = stoi(argv[2]);// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "create socket error" << std::endl;exit(SOCKFD_ERR);}// 2. bind吗??需要。显式的bind?不需要!随机方式选择端口号// 2. 我应该做什么呢?listen?accept?都不需要!!// 2. 直接向目标服务器发起建立连接的请求InetAddr addr(server_ip, server_port); // 解析服务器地址int n = connect(sockfd, addr.NetAddrPtr(), addr.NetAddrLen());if (n < 0){std::cerr << "connect error" << std::endl;exit(CONNECT_ERR);}// 连接成功,开始通信while (true){string line;cout << "Please input message to server:" getline(cin, line);// 发送消息write(sockfd, line.c_str(), line.size());// 接收消息char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;cout << "receive message from server:" << buffer << endl;}}close(sockfd);return 0;
}
2. version 1 多进程版本
version版本的弊端也明显,就是在同一时刻只能处理一个客户端,十分的鸡肋,下面就是依据于多线程的实现:
整体结构与功能概述
Run
方法的核心流程:
- 进入服务器主循环,持续接受客户端连接
- 对每个客户端连接,使用
fork
创建子进程处理 - 采用 "孙子进程" 模型处理请求,避免僵尸进程
- 父进程等待子进程退出,回收资源
关键系统调用:
accept
:接受客户端连接fork
:创建子进程waitpid
:等待子进程退出
关键功能模块详解
1. 客户端连接接受
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listtensockfd, CONV(peer), &len);
accept
函数阻塞等待客户端连接,成功时返回客户端套接字描述符sockfd
peer
结构体存储客户端地址信息CONV
宏将sockaddr_in
转换为sockaddr*
,兼容系统 API
2. 多进程模型设计
pid_t pid = fork();
if (pid < 0) { /* 错误处理 */ }
else if (pid == 0) { /* 子进程逻辑 */ }
else { /* 父进程逻辑 */ }
fork()
创建子进程,返回值:- 父进程中返回子进程 PID
- 子进程中返回 0
- 失败时返回 - 1
3. 孙子进程模型
if (fork() > 0) { exit(0); }
Service(sockfd, peer);
- 子进程中再次调用
fork()
创建孙子进程 - 子进程立即退出,使孙子进程成为孤儿进程(由 init 进程收养)
- 孙子进程处理客户端请求,退出时由系统自动回收,避免僵尸进程
4. 资源回收机制
pid_t rid = waitpid(pid, nullptr, 0);
- 父进程使用
waitpid
等待子进程退出 - 由于子进程创建后立即再次 fork 并退出,
waitpid
会很快返回 - 确保父进程回收子进程资源,避免僵尸进程
void Run(){_isrunning = true;while (_isrunning){// 4.等待客户端的连接struct sockaddr_in peer;socklen_t len = sizeof(peer);// 如果没有客户端连接,则阻塞在accept处// 如果有客户端连接,则返回一个新的套接字,用来与客户端通信,所以sockfd就是客户端的套接字int sockfd = accept(_listtensockfd, CONV(peer), &len); // #define CONV(addr) ((struct sockaddr *)&addr)if (sockfd < 0){// 这个顾客不来,我们接待下一个continue;}// 1.单进程的版本。此时接待顾客只能一个一个接待// Service(socket, peer);// 2.多进程的版本。此时可以开多个进程,每个进程负责接待一个顾客pid_t pid = fork();if (pid < 0){// fork失败cout << "fork error" << endl;exit(FORK_ERR);}else if (pid == 0){// 子进程接待顾客close(_listtensockfd); // 关闭监听套接字,子进程拿着这个套接字没有用if (fork() > 0){ // 再次fork(),子进程退出exit(0);}Service(sockfd, peer); // 孙子进程接待顾客,因为子进程已经退出了,所以孙子进程变成了孤儿进程,由系统进行回收exit(0);}else{// 父进程// 此事父进程是不是要等待子进程啊,要不然僵尸了??pid_t rid = waitpid(pid, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了}}
}
运行流程图:
多进程模型的优缺点
优点
- 并发处理能力:每个客户端连接由独立进程处理,互不阻塞
- 稳定性:某个客户端进程崩溃不影响其他进程和服务器
- 资源隔离:进程间资源隔离,安全性更高
缺点
- 资源消耗大:每个进程独立分配内存空间
- 上下文切换开销:进程切换比线程切换开销大
- 编程复杂度高:需要处理进程间通信和资源回收
3. version 2 多线程版本
这改变下面一点:
多线程创建与管理
InetAddr addr(peer);
std::thread t(&TcpServer::Service, sockfd, addr);
t.detach();
线程创建:
std::thread
构造函数接收函数指针 (&TcpServer::Service
) 和参数 (sockfd
,addr
)- 这里假设
Service
是TcpServer
的静态成员函数或普通函数
线程分离:
detach()
方法使线程成为守护线程- 分离后线程独立运行,主线程无需调用
join()
等待 - 线程结束时资源自动回收,避免内存泄漏
void Run(){_isrunning = true;while (_isrunning){// 4.等待客户端的连接struct sockaddr_in peer;socklen_t len = sizeof(peer);// 如果没有客户端连接,则阻塞在accept处// 如果有客户端连接,则返回一个新的套接字,用来与客户端通信,所以sockfd就是客户端的套接字int sockfd = accept(_listtensockfd, CONV(peer), &len); // #define CONV(addr) ((struct sockaddr *)&addr)if (sockfd < 0){// 这个顾客不来,我们接待下一个continue;}// version 3.0 多线程版本InsetAddr addr(peer);thread t(&TcpServer::Service,sockfd,addr);t.detach();// 线程分离,不用等待线程结束,可以继续处理下一个客户端的连接}}
4. version 3 线程池版本
void Run(){_isrunning = true;while (_isrunning){// 4.等待客户端的连接struct sockaddr_in peer;socklen_t len = sizeof(peer);// 如果没有客户端连接,则阻塞在accept处// 如果有客户端连接,则返回一个新的套接字,用来与客户端通信,所以sockfd就是客户端的套接字int sockfd = accept(_listtensockfd, CONV(peer), &len); // #define CONV(addr) ((struct sockaddr *)&addr)if (sockfd < 0){// 这个顾客不来,我们接待下一个continue;}// version 4.0 线程池版本InetAddr addr(peer);ThreadPool<func_t>::GetInstance()->Equeue([this,sockfd,&addr](){this->Service(sockfd,addr);});_isruning=false;}