【Linux网络编程】TCP Socket

前言: 继上一篇完成了 UDP 协议的复习后,最近梳理了 TCP 协议的底层实现。与 UDP "即发即忘"的特性不同,TCP 作为一种面向连接、可靠的字节流协议,虽然握手和挥手的过程增加了复杂性,但它是构建稳定网络服务(如 HTTP、RPC)的基石。

一、 TCP Socket 编程的核心

在Linux中,"一切皆文件"。网络通信本质上也是一种对文件的读写操作。要建立一个TCP连接,客户端和服务器必须遵循特定的"握手"流程。

核心API一览

TCP编程的核心函数都在 <sys/socket.h> 中

socket() : 创建通信端点

类似于文件操作的 open(),成功返回文件描述符,失败返回-1 。

关键参数 为 AF_INET (IPv4), SOCK_STREAM (TCP面向流) 。

bind() : 绑定IP和端口

服务器必须绑定固定的IP和端口,这样客户端才能找得到它 ,如果不显式绑定,内核会随机分配,这对服务器是不可接受的。

listen() : 设置监听状态

该函数的功能是告诉内核:"我准备好了,可以接收连接请求了"。其中 backlog 参数:指定连接等待队列的长度(一般设为5-10),处理不过来的连接会在这里排队 。

accept() : 获取连接

accept 返回的是一个新的文件描述符 (我们可以叫它 connfd),专门用于和当前这个客户端进行通信。而原本的 listenfd 继续监听新的连接。这就像饭店门口的迎宾小姐(listenfd)只负责把人领进来,具体服务由服务员(connfd)负责 。

connect() : 发起连接(客户端)

客户端调用此函数向服务器发起三次握手 。

上述API的使用将在下面的例子中详细介绍。

二、不同版本的TCP服务器

V1版本:单进程循环服务器

核心代码逻辑

单进程的服务器初始化时,我们需要填充 sockaddr_in 结构体:

cpp 复制代码
// 初始化服务器地址结构
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 清零
local.sin_family = AF_INET;   // 地址类型 
local.sin_port = htons(_port); // 端口号转网络字节序
// INADDR_ANY: 绑定本机所有IP,适合多网卡机器
local.sin_addr.s_addr = htonl(INADDR_ANY); 

// 1. Socket -> 2. Bind -> 3. Listen
socket(...);
bind(...);
listen(...);

在 Start() 启动循环中:

cpp 复制代码
while (true) {
    // 4. 获取连接
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    // accept 是阻塞的,直到有客户端连上来 [cite: 67]
    int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len); 
    
    if (sockfd < 0) continue;
    
    // 5. 提供服务
    Service(sockfd); // 执行读写循环
    close(sockfd);   // 服务结束关闭连接
}

为什么单进程是不科学的?

在 V1 版本中,如果 Service(sockfd) 内部是一个 while(true) 循环(为了持续和同一个客户端聊天),那么主程序的 while 循环就会卡在 Service 里,无法返回去调用 accept

结果:只要第一个客户端不退,第二个客户端就永远连不上,处于阻塞状态。这显然不符合服务器"并发"的要求 。

单进程TcpServer.hpp源码如下:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "Logger.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

static const uint16_t gdefaultsockfd = -1;
static const int gbacklog = 8;
static const int gport = 8080;

using task_t = std::function<void()>;
class TcpEchoServer
{
private:
    // 长任务 -> sockfd -> 长链接  不太适合线程池技术
    void Service(int sockfd, InetAddr client)
    {
        char buffer[1024];
        // ⼀直进⾏IO
        while (true)
        {
            buffer[0] = 0;
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                LOG(LogLevel::INFO) << "client [" << client.ToString() << "] say# " << buffer;
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // read如果返回值是0,表⽰读到了⽂件结尾(对端关闭了连接!)
            {
                LOG(LogLevel::INFO) << "client " << client.ToString() << " quit, close sockfd: " << sockfd;
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "read client " << client.ToString() << " error, sockfd: " << sockfd;
                break;
            }
        }
        close(sockfd);
    }

public:
    TcpEchoServer(uint16_t port = gport) : _listensockfd(gdefaultsockfd), _port(port)
    {
    }
    void Init()
    {
        // 1.创建套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // tcp
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create tcp socket error";
            exit(SOCK_CREATE_ERROR);
        }
        LOG(LogLevel::INFO) << "create tcp socket success: " << _listensockfd;

        // 2.bind套接字
        InetAddr addr(_port);

        if (bind(_listensockfd, addr.Addr(), addr.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "bind socker error";
            exit(SOCK_BIND_ERROR);
        }
        LOG(LogLevel::INFO) << "bind socker success: " << _listensockfd;

        // 3.listen套接字
        if (listen(_listensockfd, gbacklog) != 0) // tcp
        {
            LOG(LogLevel::FATAL) << "listen socket error";
            exit(SOCK_LISTEN_ERROR);
        }
        LOG(LogLevel::INFO) << "listen socket success: " << _listensockfd;
    }

    void Start()
    {
        // signal(SIGCHLD, SIG_IGN);  // 防止父进程一直阻塞等待
        while (true)
        {
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len); // 默认阻塞
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept client error";
                continue;
            }
            InetAddr clientaddr(peer);
            LOG(LogLevel::INFO) << "获取新链接成功, sockfd is : " << sockfd << "client addr: " << clientaddr.ToString();

            //  1. 单进程处理
            Service(sockfd,clientaddr);   // 单进程

        }
    }
    ~TcpEchoServer()
    {
    }

private:
    int _listensockfd;
    int _port;
};

V2版本:多进程并发

为了让服务器能同时服务多个人,利用 fork() 创建子进程是经典的Unix解法。

核心思想

  • 父进程只负责 accept 获取连接。
  • 一旦获取连接,fork 一个子进程。
  • 子进程 继承了父进程的文件描述符,负责运行 Service(sockfd)
  • 父进程 关闭 sockfd(因为它不需要通信),继续回去 accept

僵尸进程的处理

多进程编程必须处理子进程退出后的资源回收,否则会产生僵尸进程。使用一种巧妙的双重Fork(孤儿进程) 技巧:

cpp 复制代码
pid_t id = fork();
if (id == 0) { // Child
    close(_listensock); // 子进程不需要监听socket 
    
    if (fork() > 0) exit(0); // 孙子进程被创建,儿子进程直接退出
    
    // 这里是孙子进程,由于父进程(儿子进程)已死,它被系统领养(init/systemd)
    // 系统会自动回收它的资源,无需我们就操心waitpid了
    Service(sockfd); 
    close(sockfd);
    exit(0);
}
// Father
close(sockfd); // 父进程切记关闭sockfd,否则文件描述符泄露 
waitpid(id, nullptr, 0); // 回收儿子进程(因为儿子进程立刻退出了,不会阻塞很久)

多进程TcpServer.hpp源码如下:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "Logger.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

static const uint16_t gdefaultsockfd = -1;
static const int gbacklog = 8;
static const int gport = 8080;

using task_t = std::function<void()>;
class TcpEchoServer
{
private:
    // 长任务 -> sockfd -> 长链接  不太适合线程池技术
    void Service(int sockfd, InetAddr client)
    {
        char buffer[1024];
        // ⼀直进⾏IO
        while (true)
        {
            buffer[0] = 0;
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                LOG(LogLevel::INFO) << "client [" << client.ToString() << "] say# " << buffer;
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // read如果返回值是0,表⽰读到了⽂件结尾(对端关闭了连接!)
            {
                LOG(LogLevel::INFO) << "client " << client.ToString() << " quit, close sockfd: " << sockfd;
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "read client " << client.ToString() << " error, sockfd: " << sockfd;
                break;
            }
        }
        close(sockfd);
    }

public:
    TcpEchoServer(uint16_t port = gport) : _listensockfd(gdefaultsockfd), _port(port)
    {
    }
    void Init()
    {
        // 1.创建套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // tcp
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create tcp socket error";
            exit(SOCK_CREATE_ERROR);
        }
        LOG(LogLevel::INFO) << "create tcp socket success: " << _listensockfd;

        // 2.bind套接字
        InetAddr addr(_port);

        if (bind(_listensockfd, addr.Addr(), addr.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "bind socker error";
            exit(SOCK_BIND_ERROR);
        }
        LOG(LogLevel::INFO) << "bind socker success: " << _listensockfd;

        // 3.listen套接字
        if (listen(_listensockfd, gbacklog) != 0) // tcp
        {
            LOG(LogLevel::FATAL) << "listen socket error";
            exit(SOCK_LISTEN_ERROR);
        }
        LOG(LogLevel::INFO) << "listen socket success: " << _listensockfd;
    }

    void Start()
    {
        signal(SIGCHLD, SIG_IGN);  // 防止父进程一直阻塞等待
        while (true)
        {
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len); // 默认阻塞
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept client error";
                continue;
            }
            InetAddr clientaddr(peer);
            LOG(LogLevel::INFO) << "获取新链接成功, sockfd is : " << sockfd << "client addr: " << clientaddr.ToString();

             //  2.多进程处理
             pid_t id = fork();
             if (id < 0)
             {
                 LOG(LogLevel::FATAL) << "资源不足, 创建子进程失败";
                 exit(FORK_ERROR);
             }
             else if (id == 0)
             {
                 // 子进程
                 close(_listensockfd);  // 防御性编程
                 if(fork() > 0)
                     exit(OK);

                 // 孙子进程
                 Service(sockfd, clientaddr);
                 exit(OK);
             }
             else
             {
                 close(sockfd); // 1. 关闭无用fd 2. 规避fd泄漏(可用fd越来越少)
                 pid_t rid = waitpid(id, nullptr, 0);
                 (void)rid;
             }

        }
    }
    ~TcpEchoServer()
    {
    }

private:
    int _listensockfd;
    int _port;
};

V3版本:多线程轻量级并发

进程创建的开销比较大,且进程间通信复杂。多线程是更现代的写法。

遇到的坑:参数传递

我们使用 pthread_create 启动线程,但线程函数 static void *Routine(void args) 只能接收一个 void 参数。 我们需要传递 sockfd,最好还有客户端的 sockaddr_in 信息。

我们可以定义一个 ThreadData 类来封装这些参数 。

cpp 复制代码
class ThreadData {
public:
    int _sockfd;
    InetAddr _addr; // 封装了IP和Port的类
    ThreadData(int sockfd, struct sockaddr_in addr) : _sockfd(sockfd), _addr(addr) {}
};

线程分离

原因在于主线程不希望阻塞在 pthread_join 上等待子线程结束。

解决方案为在线程函数内部调用 pthread_detach(pthread_self())。这样线程结束后,资源自动由OS回收 。

cpp 复制代码
static void *Routine(void *arg)
{
     ThreadData *td = static_cast<ThreadData *>(arg);
     pthread_detach(pthread_self()); // 线程分离
     td->_self->Service(td->_sockfd, td->_client);
     delete td;
     return nullptr;
}

多线程TcpServer.hpp源码如下:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "Logger.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

static const uint16_t gdefaultsockfd = -1;
static const int gbacklog = 8;
static const int gport = 8080;

using task_t = std::function<void()>;
class TcpEchoServer
{
private:
    // 长任务 -> sockfd -> 长链接  不太适合线程池技术
    void Service(int sockfd, InetAddr client)
    {
        char buffer[1024];
        // ⼀直进⾏IO
        while (true)
        {
            buffer[0] = 0;
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                LOG(LogLevel::INFO) << "client [" << client.ToString() << "] say# " << buffer;
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // read如果返回值是0,表⽰读到了⽂件结尾(对端关闭了连接!)
            {
                LOG(LogLevel::INFO) << "client " << client.ToString() << " quit, close sockfd: " << sockfd;
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "read client " << client.ToString() << " error, sockfd: " << sockfd;
                break;
            }
        }
        close(sockfd);
    }

public:
    TcpEchoServer(uint16_t port = gport) : _listensockfd(gdefaultsockfd), _port(port)
    {
    }
    void Init()
    {
        // 1.创建套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // tcp
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create tcp socket error";
            exit(SOCK_CREATE_ERROR);
        }
        LOG(LogLevel::INFO) << "create tcp socket success: " << _listensockfd;

        // 2.bind套接字
        InetAddr addr(_port);

        if (bind(_listensockfd, addr.Addr(), addr.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "bind socker error";
            exit(SOCK_BIND_ERROR);
        }
        LOG(LogLevel::INFO) << "bind socker success: " << _listensockfd;

        // 3.listen套接字
        if (listen(_listensockfd, gbacklog) != 0) // tcp
        {
            LOG(LogLevel::FATAL) << "listen socket error";
            exit(SOCK_LISTEN_ERROR);
        }
        LOG(LogLevel::INFO) << "listen socket success: " << _listensockfd;
    }

    static void *Routine(void *arg)
    {
        ThreadData *td = static_cast<ThreadData *>(arg);
        pthread_detach(pthread_self()); // 线程分离
        td->_self->Service(td->_sockfd, td->_client);
        delete td;
        return (void*)1;

    }

    class ThreadData
    {
    public:
        int _sockfd;
        TcpEchoServer *_self;
        InetAddr _client;
        ThreadData(int sockfd, TcpEchoServer *self, InetAddr client) : _sockfd(_sockfd), _self(self), _client(client)
        {
        }
    };
    void Start()
    {
        while (true)
        {
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len); // 默认阻塞
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept client error";
                continue;
            }
            InetAddr clientaddr(peer);
            LOG(LogLevel::INFO) << "获取新链接成功, sockfd is : " << sockfd << "client addr: " << clientaddr.ToString();

            //  3.多线程处理
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this, clientaddr);
            pthread_create(&tid, nullptr, Routine, (void *)td);

        }
    }
    ~TcpEchoServer()
    {
    }

private:
    int _listensockfd;
    int _port;
};

V4版本:线程池优化

虽然V3版本解决了并发,但如果有海量短连接(比如几万个请求瞬间涌入),频繁创建和销毁线程会极大地消耗CPU资源。

核心逻辑

  • 预先创建好一批线程(Worker Threads)。
  • 主线程 accept 成功后,不再创建新线程,而是将 Service 任务封装成一个 Task(可以使用 std::bind 适配器)。
  • 将任务 Push 到线程池的任务队列中。
  • 空闲的线程抢占任务并执行 。
cpp 复制代码
// 伪代码示例
using func_t = std::function<void()>;
// 将Service函数和参数绑定,变成无参的函数对象
func_t func = std::bind(&TcpServer::Service, this, sockfd, addr);
// 扔进线程池
ThreadPool<func_t>::GetInstance()->Enqueue(func);

线程池TcpServer.hpp源码如下:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "Logger.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

static const uint16_t gdefaultsockfd = -1;
static const int gbacklog = 8;
static const int gport = 8080;

using task_t = std::function<void()>;
class TcpEchoServer
{
private:
    // 长任务 -> sockfd -> 长链接  不太适合线程池技术
    void Service(int sockfd, InetAddr client)
    {
        char buffer[1024];
        // ⼀直进⾏IO
        while (true)
        {
            buffer[0] = 0;
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                LOG(LogLevel::INFO) << "client [" << client.ToString() << "] say# " << buffer;
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // read如果返回值是0,表⽰读到了⽂件结尾(对端关闭了连接!)
            {
                LOG(LogLevel::INFO) << "client " << client.ToString() << " quit, close sockfd: " << sockfd;
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "read client " << client.ToString() << " error, sockfd: " << sockfd;
                break;
            }
        }
        close(sockfd);
    }

public:
    TcpEchoServer(uint16_t port = gport) : _listensockfd(gdefaultsockfd), _port(port)
    {
    }
    void Init()
    {
        // 1.创建套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // tcp
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create tcp socket error";
            exit(SOCK_CREATE_ERROR);
        }
        LOG(LogLevel::INFO) << "create tcp socket success: " << _listensockfd;

        // 2.bind套接字
        InetAddr addr(_port);

        if (bind(_listensockfd, addr.Addr(), addr.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "bind socker error";
            exit(SOCK_BIND_ERROR);
        }
        LOG(LogLevel::INFO) << "bind socker success: " << _listensockfd;

        // 3.listen套接字
        if (listen(_listensockfd, gbacklog) != 0) // tcp
        {
            LOG(LogLevel::FATAL) << "listen socket error";
            exit(SOCK_LISTEN_ERROR);
        }
        LOG(LogLevel::INFO) << "listen socket success: " << _listensockfd;
    }

    void Start()
    {
        // signal(SIGCHLD, SIG_IGN);  // 防止父进程一直阻塞等待
        while (true)
        {
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len); // 默认阻塞
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept client error";
                continue;
            }
            InetAddr clientaddr(peer);
            LOG(LogLevel::INFO) << "获取新链接成功, sockfd is : " << sockfd << "client addr: " << clientaddr.ToString();

            //  多进程 多线程
            //  1.效率问题 connect成功才创建进程线程
            //  2.执行流个数没有上限

            //  4.线程池
            ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, clientaddr]()
                                                       { this->Service(sockfd, clientaddr); });
        }
    }
    ~TcpEchoServer()
    {
    }

private:
    int _listensockfd;
    int _port;
};

根据如上几个版本,使用TcpServer.cc进行服务器构建:

cpp 复制代码
#include <memory>
#include "TcpServer.hpp"

void Usage(std::string proc)
{
    std::cerr << "Usage : " << proc << " serverport" << std::endl;
}

// ./tcp_server serverport
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    //EnableConsoleLogStrategy(); // 启动日志
    std::unique_ptr<TcpEchoServer> ptr = std::make_unique<TcpEchoServer>();
    ptr->Init();
    ptr->Start();
    return 0;
}

三、客户端的实现细节

客户端相对简单,TcpClient.cc源码如下:

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include "Comm.hpp"
#include "InetAddr.hpp"

void Usage(std::string proc)
{
    std::cerr << "Usage : " << proc << " serverip serverport" << std::endl;
}

// ./tcp_client serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "create client sockfd error" << std::endl;
        exit(SOCK_CREATE_ERROR);
    }

    InetAddr server(serverport, serverip);
    if (connect(sockfd, server.Addr(), server.Length()) != 0)
    {
        std::cerr << "connect sockfd error" << std::endl;
        exit(SOCK_CONNECT_ERROR);
    }
    std::cout << "connect " << server.ToString() << " success" << std::endl;

    while (true)
    {
        std::cout << "Please Enter@ ";
        std::string line;
        std::getline(std::cin, line);
        ssize_t n = write(sockfd, line.c_str(), line.size());
        if(n >= 0)
        {
            char buffer[1024];
            buffer[0] = 0;
            ssize_t m = read(sockfd,buffer,sizeof(buffer));
            if(m > 0)
            {
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
        }       
    }

    return 0;
}

客户端需要bind吗?

答案是当然需要bind,但不需要也不建议用户显式调用 bind

原因 在于如果客户端强行绑定 9999 端口,那么这台机器上就只能启动一个客户端实例了(端口冲突)。在调用 connect() 时,如果OS发现socket没有绑定,会自动为它分配一个临时的、唯一的端口号 。

四、总结与思考

版本 描述 优点 缺点
V1 单进程循环 简单易懂 无法并发,一次只能服务一人
V2 多进程 (Fork) 隔离性好,健壮 进程创建销毁开销大,上下文切换慢
V3 多线程 轻量,并发度高 线程不安全,单线程崩溃可能导致整个进程挂掉
V4 线程池 资源复用,性能稳 实现复杂度稍高,需配合任务队列

重点细节在于

  • 务必理解 INADDR_ANY 的作用 。
  • 注意 read 返回值为0表示对端关闭连接(EOF),这是TCP连接断开的重要标志 。
  • 如果不熟悉 htons/htonl,请复习网络字节序与主机字节序的大小端问题。

希望这篇笔记能帮你打通Linux TCP编程的任督二脉!

相关推荐
Fnetlink136 分钟前
Fnet 云网安 260904
网络·人工智能·安全·web安全·网络安全
tangwangbi1 小时前
Linux 系统配置文件:/etc/profile、~/.bashrc 和 ~/.bash_profile 三者之间的区别与作用
linux·运维·bash
天远数科1 小时前
风险治理实战:基于天远车信盟出险构建自动化理赔核保流水线
java·网络·人工智能·自动化
蜀道山老天师1 小时前
Shell Bash变量与运算符(含条件测试与流程控制)
linux·运维·bash
Mr.朱鹏1 小时前
Linux 服务器 LVM 根分区在线动态扩容
linux·服务器·数据库
mengge.cloud2 小时前
存储技术基础小白教程
linux·运维·服务器·wpf·存储
志栋智能2 小时前
安全超自动化如何支持大规模安全运营?
网络·安全·自动化
星恒讯工业路由器2 小时前
农村宽带覆盖难?无线专网方案正在改写“最后一公里”规则
网络·物联网·网桥·无线网桥·农村宽带覆盖·无线专网·户外网络
tang777892 小时前
反爬虫场景下代理IP池的动态扩容与失效IP自动清理方案实现
linux·服务器·网络·爬虫代理·住宅代理ip
yuewell_ai3 小时前
机器人调度通讯怎么设计才不会被网络抖动瘫痪
网络·机器人·php