Linux网络--Socket 编程 TCP

大家好,上次我们学习了有关UDP的socket编程,今天我们来继续学习一下关于TCP的socket编程,那么我们开始今天的学习。

目录

[1. TCP socket API 详解](#1. TCP socket API 详解)

[1.1 socket](#1.1 socket)

[1.2 bind](#1.2 bind)

[1.3 listen](#1.3 listen)

[1.4 accept](#1.4 accept)

[1.5 connect](#1.5 connect)

[2. Echo Server](#2. Echo Server)

[2.1 Echo Server 多进程版本](#2.1 Echo Server 多进程版本)

[2.2 Echo Server 多线程版本](#2.2 Echo Server 多线程版本)

[2.3 Echo Server 线程池版本](#2.3 Echo Server 线程池版本)


1. TCP socket API 详解

首先我们来看一下关于TCP的socket编程的相关接口:

1.1 socket
  1. socket() 打开一个网络通讯端口 , 如果成功的话 , 就像 open() 一样返回一个文件描述符;
  2. 应用程序可以像读写文件一样用 read/write 在网络上收发数据 ;
  3. 如果 socket() 调用出错则返回 -1;
  4. 对于 IPv4, family 参数指定为 AF_INET;
  5. 对于 TCP 协议 ,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
  6. protocol 参数的介绍从略 , 指定为 0 即可。

与UDP不同的是,在type参数中,UDP传入的是SOCK_DRGAM,TCP传入的是SOCK_STREAM,其余相同。

1.2 bind
  1. 服务器程序所监听的网络地址和端口号通常是固定不变的 , 客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号;
  2. bind() 成功返回 0, 失败返回 -1 。
  3. bind() 的作用是将参数 sockfd 和 myaddr 绑定在一起 , 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号 ;
  4. 前面讲过 ,struct sockaddr * 是一个通用指针类型 ,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体 , 而它们的长度各不相同 , 所以需要第三个参数 addrlen指定结构体的长度;

此接口使用与UDP处无异。

1.3 listen
  1. listen() 声明 sockfd 处于监听状态 , 并且最多允许有 backlog 个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略 , 这里设置不会太大 ( 一般是 5) ;
  2. listen() 成功返回 0, 失败返回 -1;
1.4 accept
  1. 三次握手完成后 , 服务器调用 accept() 接受连接 ;
  2. 如果服务器调用 accept() 时还没有客户端的连接请求 , 就阻塞等待直到有客户端连接上来;
  3. addr 是一个传出参数 ,accept() 返回时传出客户端的地址和端口号 ;
  4. 如果给 addr 参数传 NULL, 表示不关心客户端的地址 ;
  5. addrlen 参数是一个传入传出参数 (value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题 , 传出的是客户端地址结构体的实际长度( 有可能没有占满调用者提供的缓冲区 );

服务器端server通过accept来与客户端client进行连接。

1.5 connect
  1. 客户端需要调用 connect() 连接服务器 ;
  2. connect 和 bind 的参数形式一致 , 区别在于 bind 的参数是自己的地址 , 而 connect 的参数是对方的地址 ;
  3. connect() 成功返回 0, 出错返回 -1;

客户端client通过connect来与服务器端server进行连接。

介绍完了这些接口,下面我们继续看几个有关TCP的服务器实现:

2. Echo Server

Common.hpp:

复制代码
#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>

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)

由于服务器对象通常无法进行赋值和拷贝,所以我们定义基类将拷贝构造和赋值重载进行删除,且在Common.hpp中枚举了一些退出码,当socket等接口运行出错时,使用这些退出码进行退出。

InetAddr.hpp:

复制代码
#pragma once
#include "Common.hpp"
// 网络地址和主机地址之间进行转换的类

class InetAddr
{
public:
    InetAddr(){}
    InetAddr(struct sockaddr_in &addr) : _addr(addr)
    {
        // 网络转主机
        _port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列
        // _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }
    InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_port);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
    }
    InetAddr(uint16_t port) :_port(port),_ip()
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _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);
    }
    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;
    std::string _ip;
    uint16_t _port;
};

我们将各种有关网络和主机间的转换接口都封装在InetAddr.hpp中。

TcpServer.hpp:

复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <sys/wait.h>
#include <signal.h>

// 服务器往往是禁止拷贝的
using namespace LogModule;
using namespace ThreadPoolModule;

// using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string&, InetAddr &)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port, func_t func) : _port(port),
                                            _listensockfd(defaultsockfd),
                                            _isrunning(false),
                                            _func(func)
    {
    }
    void Init()
    {
        // signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号,推荐的做法
        // 1. 创建套接字文件
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 2. bind众所周知的端口号
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 3. 设置socket状态为listen
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                std::string echo_string = _func(buffer, peer);

                // // 2. 写回数据
                // std::string echo_string = "echo# ";
                // echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // a. 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(sockaddr_in);
            // 如果没有连接,accept就会阻塞
            int sockfd = accept(_listensockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();

            单进程程序 --- 不会存在的!
            Service(sockfd, addr);
        }
        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listensockfd; // 监听socket
    bool _isrunning;

    func_t _func; // 设置回调处理
};

TcpServer.cc

复制代码
#include "TcpServer.hpp"

std::string defaulthandler(const std::string &word, InetAddr &addr)
{
    LOG(LogLevel::DEBUG) << "回调到了defaulthandler";
    std::string s = "haha, ";
    s += word;
    return s;
}

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

// 远程命令执行的功能!
// ./tcpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port,defaulthandler);
                                                                  
    tsvr->Init();
    tsvr->Run();

    return 0;
}

TcpClient.cc

复制代码
#include <iostream>
#include "Common.hpp"
#include "InetAddr.hpp"

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

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

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(SOCKET_ERR);
    }
    // 2. bind吗??需要。显式的bind?不需要!随机方式选择端口号
    // 2. 我应该做什么呢?listen?accept?都不需要!!
    // 2. 直接向目标服务器发起建立连接的请求
    InetAddr serveraddr(serverip, serverport);
    int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
    if(n < 0)
    {
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    // 3. echo client
    while(true)
    {
        std::string line;
        std::cout << "Please Enter@ ";
        std::getline(std::cin, line);

        write(sockfd, line.c_str(), line.size());

        char buffer[1024];
        ssize_t size = read(sockfd, buffer, sizeof(buffer)-1);
        if(size > 0)
        {
            buffer[size] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }

    close(sockfd);
    return 0;
}

这就是一个回显Echo Server服务器的代码,但是这其中有些问题,就是当我们服务器端server接收到客户端client之后会转到Service函数当中,此时若有新的客户端client请求连接,那服务器端server是无法接收的,所以这里是不可行的,我们要对其进行改造升级。

2.1 Echo Server 多进程版本

这里只给出TcpServer.hpp文件的内容,其他部分基本相同,就不再次给出了。

复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <sys/wait.h>
#include <signal.h>

// 服务器往往是禁止拷贝的
using namespace LogModule;
using namespace ThreadPoolModule;

// using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string&, InetAddr &)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port, func_t func) : _port(port),
                                            _listensockfd(defaultsockfd),
                                            _isrunning(false),
                                            _func(func)
    {
    }
    void Init()
    {
        // signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号,推荐的做法
        // 1. 创建套接字文件
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 2. bind众所周知的端口号
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 3. 设置socket状态为listen
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    // 短服务
    // 长服务: 多进程多线程比较合适
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                std::string echo_string = _func(buffer, peer);

                // // 2. 写回数据
                // std::string echo_string = "echo# ";
                // echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // a. 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(sockaddr_in);
            // 如果没有连接,accept就会阻塞
            int sockfd = accept(_listensockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();

             version1 --- 多进程版本
             pid_t id = fork(); // 父进程
             if(id < 0)
             {
                 LOG(LogLevel::FATAL) << "fork error";
                 exit(FORK_ERR);
             }
             else if(id == 0)
             {
                 // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
                 // 我们不想让子进程访问listensock!
                 close(_listensockfd);
                 if(fork() > 0) // 再次fork,子进程退出
                     exit(OK);

                 Service(sockfd, addr); // 孙子进程,孤儿进程,1, 系统回收我
                 exit(OK);
             }
             else
             {
                 //父进程
                 close(sockfd);

                 //父进程是不是要等待子进程啊,要不然僵尸了??
                 pid_t rid = waitpid(id, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了
                 (void)rid;
             }

        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listensockfd; // 监听socket
    bool _isrunning;

    func_t _func; // 设置回调处理
};

父进程负责用来接收客户端client的连接,故不需要accept函数的返回值;子进程负责调用Service函数,不需要_listensockfd,这里将其进行回收。

父进程需要对子进程进行等待,否则子进程资源不会释放编程僵尸进程,但若是父进程调用waitpid函数还要进行阻塞,所以我们在子进程中继续创建子进程,创建之后让父进程直接退出,而它的子进程成为了孤儿进程交给操作系统进行回收。

2.2 Echo Server 多线程版本
复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

// 服务器往往是禁止拷贝的
using namespace LogModule;
using namespace ThreadPoolModule;

// using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string&, InetAddr &)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port, func_t func) : _port(port),
                                            _listensockfd(defaultsockfd),
                                            _isrunning(false),
                                            _func(func)
    {
    }
    void Init()
    {
        // signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号,推荐的做法
        // 1. 创建套接字文件
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 2. bind众所周知的端口号
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 3. 设置socket状态为listen
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    class ThreadData
    {
    public:
        ThreadData(int fd, InetAddr &ar, TcpServer *s) : sockfd(fd), addr(ar), tsvr(s)
        {
        }

    public:
        int sockfd;
        InetAddr addr;
        TcpServer *tsvr;
    };

    // 短服务
    // 长服务: 多进程多线程比较合适
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                std::string echo_string = _func(buffer, peer);

                // // 2. 写回数据
                // std::string echo_string = "echo# ";
                // echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->addr);
        delete td;
        return nullptr;
    }

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // a. 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(sockaddr_in);
            // 如果没有连接,accept就会阻塞
            int sockfd = accept(_listensockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();

            // version2: 多线程版本
            ThreadData *td = new ThreadData(sockfd, addr, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);

        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listensockfd; // 监听socket
    bool _isrunning;

    func_t _func; // 设置回调处理
};

这里直接创建线程来进行Service函数的调用。

2.3 Echo Server 线程池版本
复制代码
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

// 服务器往往是禁止拷贝的
using namespace LogModule;
using namespace ThreadPoolModule;

// using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string&, InetAddr &)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port, func_t func) : _port(port),
                                            _listensockfd(defaultsockfd),
                                            _isrunning(false),
                                            _func(func)
    {
    }
    void Init()
    {
        // signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号,推荐的做法
        // 1. 创建套接字文件
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 2. bind众所周知的端口号
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 3. 设置socket状态为listen
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    // 短服务
    // 长服务: 多进程多线程比较合适
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;

                std::string echo_string = _func(buffer, peer);

                // // 2. 写回数据
                // std::string echo_string = "echo# ";
                // echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // a. 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(sockaddr_in);
            // 如果没有连接,accept就会阻塞
            int sockfd = accept(_listensockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();


            // version3:线程池版本,线程池一般比较适合处理短服务
            // 将新链接和客户端构建一个新的任务,push线程池中
            ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
                this->Service(sockfd, addr);
            });
        }
        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listensockfd; // 监听socket
    bool _isrunning;

    func_t _func; // 设置回调处理
};

因为线程池中的线程个数是有限的,所以当服务器server中连接的客户端client多起来的时候,用线程池就有些麻烦了,所以线程池时候短服务,而多进程和多线程的模式更适合长服务。

总之,TCP的使用与UDP基本无异,所以就不再写其他服务器实现了,以上就是今天的内容,我们下次再见!

相关推荐
menge23332 小时前
Linux网站搭建
linux·运维·网络
Bruce_Liuxiaowei2 小时前
Kali Linux 加入 Windows 域实战指南:解决域发现与加入失败问题
linux·运维·windows
LumenL1u2 小时前
CentOS 7/8/9 上安装 MySQL 8.0+ 完整指南
linux·mysql
梁正雄2 小时前
linux服务-nginx原理与安装-1
linux·运维·nginx
伊卡洛斯az3 小时前
Linux veth
linux·服务器
brucelee1863 小时前
在 Linux Ubuntu 24.04 安装 IntelliJ IDEA
linux·ubuntu·intellij-idea
2301_821727173 小时前
nfs服务
网络·笔记
老蒋新思维3 小时前
紧跟郑滢轩,以 “学习力 +” 驱动 AI 与 IP 商业变革
网络·人工智能·学习·tcp/ip·企业管理·创始人ip·创客匠人
Linux技术芯3 小时前
金刚经修心课 你的生活指南
linux