【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编程的任督二脉!

相关推荐
Watermelo6176 小时前
【前端实战】从 try-catch 回调到链式调用:一种更优雅的 async/await 错误处理方案
前端·javascript·网络·vue.js·算法·vue·用户体验
Andre_BWM99927 小时前
跨境电商防关联技术实践:美客多自养号环境搭建(指纹浏览器/IP隔离/支付合规)
网络·网络协议·tcp/ip
倔强的石头1067 小时前
Linux 进程深度解析(三):调度算法、优先级调整与进程资源回收(wait与waitpid)
linux·服务器·算法
hh.h.7 小时前
无网络也能用!Flutter+开源鸿蒙构建轻量级应急通信系统
网络·flutter·开源
熊文豪7 小时前
Ubuntu 安装 Oracle 11g XE 完整指南
linux·ubuntu·oracle
老兵发新帖7 小时前
Ubuntu如何判断获取到的IP地址是静态IP还是动态?
chrome·tcp/ip·ubuntu
_OP_CHEN7 小时前
【Linux系统编程】(十五)揭秘 Linux 环境变量:从底层原理到实战操作,一篇吃透命令行参数与全局变量!
linux·运维·操作系统·bash·进程·环境变量·命令行参数
橘子真甜~7 小时前
C/C++ Linux网络编程14 - 传输层TCP协议详解(保证可靠传输)
linux·服务器·网络·网络协议·tcp/ip·滑动窗口·拥塞控制
小云小白7 小时前
Bash /dev/tcp、nc 与 nmap:端口检测的定位与取舍
linux·端口检测