当前位置: 首页 > news >正文

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(如 bindaccept 等函数需要 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 服务)。
  • 流程
    1. 读取客户端数据(read())。
    2. 若读取成功,添加 "echo:" 前缀后回传给客户端(write())。
    3. 若客户端关闭连接(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

程序的核心流程是:

  1. 检查命令行参数,获取端口号
  2. 创建 TCP 服务器对象
  3. 初始化服务器(创建套接字、绑定端口等)
  4. 启动服务器,开始接受客户端连接
#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. 程序整体结构与功能

该客户端程序的主要功能是:

  1. 解析命令行参数,获取服务器 IP 和端口
  2. 创建 TCP 套接字
  3. 连接到目标服务器
  4. 实现简单的交互式通信(发送消息给服务器并接收响应)

2. 关键步骤详解

(1)头文件与命名空间

#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <string>using namespace std;

  • Common.hpp:可能包含自定义的公共头文件(如错误码、宏定义等)。
  • InetAddr.hpp:包含网络地址封装类(用于解析和操作 IP 地址与端口)。
  • iostreamstring:标准库头文件,用于输入输出和字符串处理。
  • 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方法的核心流程:

    1. 进入服务器主循环,持续接受客户端连接
    2. 对每个客户端连接,使用fork创建子进程处理
    3. 采用 "孙子进程" 模型处理请求,避免僵尸进程
    4. 父进程等待子进程退出,回收资源

    关键系统调用:

    • 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); // 阻塞的吗?不会,因为子进程立马退出了}}
    }

    运行流程图:

    多进程模型的优缺点

    优点

    1. 并发处理能力:每个客户端连接由独立进程处理,互不阻塞
    2. 稳定性:某个客户端进程崩溃不影响其他进程和服务器
    3. 资源隔离:进程间资源隔离,安全性更高

    缺点

    1. 资源消耗大:每个进程独立分配内存空间
    2. 上下文切换开销:进程切换比线程切换开销大
    3. 编程复杂度高:需要处理进程间通信和资源回收

    3. version 2  多线程版本

    这改变下面一点:

    多线程创建与管理

    InetAddr addr(peer);
    std::thread t(&TcpServer::Service, sockfd, addr);
    t.detach();
    

    • 线程创建

      • std::thread构造函数接收函数指针 (&TcpServer::Service) 和参数 (sockfdaddr)
      • 这里假设ServiceTcpServer的静态成员函数或普通函数
    • 线程分离

      • 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;}

    http://www.dtcms.com/a/336866.html

    相关文章:

  1. Json A12 计算总和
  2. Git版本控制与协作
  3. 【秋招笔试】2025.08.16美团算法岗秋招机考真题
  4. Cell Metab. (IF=30.9)|上海交大刘军力研究员团队:DLAT抑制亮氨酸分解驱动肿瘤发生
  5. 朝花夕拾(七)--------从混淆矩阵到分类报告全面解析​
  6. LeetCode 刷题【45. 跳跃游戏 II】
  7. 云计算-云上实例部署 RocketChat:Mongodb、主从数据库、Node 环境配置指南
  8. 生信分析自学攻略 | R软件和Rstudio的安装
  9. 今日行情明日机会——20250818
  10. 华为服务器设置bios中cpu为性能模式
  11. week2-[循环结构]找出正数
  12. element-plus:el-tree ref初始化异常记录
  13. 【前端面试题】JavaScript 核心知识点解析(第一题到第三十题)
  14. MQTT(轻量级消息中间件)基本使用指南
  15. 套接字超时控制与服务器调度策略
  16. JavaScript基础语法three
  17. 时序数据库 Apache IoTDB:从边缘到云端Apache IoTDB 全链路数据管理能力、部署流程与安全特性解读
  18. UTMatrix VS VideoLingo 到底哪个好?
  19. 在openEuler系统中如何查看文件夹下每个文件的大小
  20. 从零到GPT:Transformer如何引领大模型时代
  21. 基于C语言实现的HRV分析方法 —— 与Kubios和MATLAB对比
  22. 力扣70:爬楼梯
  23. Java基础(九):Object核心类深度剖析
  24. 【Linux指南】gcc/g++编译器:从源码到可执行文件的全流程解析
  25. DELL服务器 R系列 IPMI的配置
  26. Linux 编译器 gcc 与 g++
  27. Linux磁盘阵列
  28. 开源Verilog仿真即波形模拟工具iVerilog初步教程
  29. 香港数据合集:建筑物、手机基站、POI、职住数据、用地类型
  30. Java 中表示数据集的常用集合类