0


【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解

✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】

前面几弹使用UDP协议实现了相关功能,此弹使用TCP协议实现客户端与服务端的通信,相比与UDP协议,TCP协议更加可靠,也更加复杂!与UDP类似,我们先写主函数,然后实现相关函数

1、TcpServerMain.cc

服务端主函数****使用智能指针构造Server对象,然后调用初始化与执行函数调用主函数使用该可执行程序 + 端口号

// ./tcpserver 8888
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);

    tsvr->InitServer();
    tsvr->Loop();
    return 0;
}

2、TcpServer.hpp

TcpServer.hpp封装TcpServer类!

枚举常量:

enum 
{
    SOCKET_ERROR,
    BIND_ERROR,
    LISTEN_ERROR
};

全局静态变量:

const static uint16_t gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;

2.1、TcpServer类基本结构

TcpServer类的基本成员有端口号,文件描述符,与运行状态!

// 面向字节流
class TcpServer
{
public:
    TcpServer(uint16_t port = gport);
    void InitServer();
    void Loop();
    ~TcpServer();
private:
    uint16_t _port;
    int _sockfd; // TODO
    bool _isrunning;
};

2.2、构造析构函数

构造函数初始化成员变量,析构函数无需处理!

注意:此处需要用到两个全局静态变量!

TcpServer(uint16_t port = gport)
    :_port(port),_sockfd(gsockfd),_isrunning(false)
{}

~TcpServer()
{}

2.3、InitServer()

InitServer() 初始化服务端!

初始化函数主要分为三步:

  • 1、创建socket(类型与UDP不同)

类型需要使用 SOCK_STREAM

  • 2、bind sockfd 和 socket addr
  • 3、获取连接(与UDP不同)

获取连接需要使用listen函数(将套接字设置为监听模式,以便能够接受进入的连接请求)

listen()

#include <sys/types.h>          
#include <sys/socket.h>

int listen(int sockfd, int backlog);

参数

  • sockfd:这是一个已创建的套接字文件描述符,它应该是一个绑定到某个地址和端口的套接字。
  • backlog:这个参数**定义了内核应该为相应套接字排队的最大连接数(此处暂时使用8)**。如果队列已满,新的连接请求可能会被拒绝。需要注意的是,这个值只是内核用于优化性能的一个提示,实际实现可能会有所不同。

返回值

  • 成功时,listen 函数返回 0。
  • 失败时,返回 -1,并设置 errno 以指示错误类型。

注意:此处需要用到全局静态变量和枚举常量!

// _sockfd 版本
void InitServer()
{
    // 1.创建socket
    _sockfd = ::socket(AF_INET,SOCK_STREAM,0);
    if(_sockfd < 0)
    {
        LOG(FATAL,"socket create eror\n");
        exit(SOCKET_ERROR);
    }
    LOG(INFO,"socket create success,sockfd: %d\n",_sockfd); // 3

    struct sockaddr_in local;
    memset(&local,0,sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = INADDR_ANY;

    // 2.bind sockfd 和 socket addr
    if(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
    {
        LOG(FATAL,"bind eror\n");
        exit(BIND_ERROR);
    }
    LOG(INFO,"bind success\n");

    // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
    // 老板模式,随时等待被连接
    if(::listen(_sockfd,gblcklog) < 0)
    {
        LOG(FATAL,"listen eror\n");
        exit(LISTEN_ERROR);
    }
    LOG(INFO,"listen success\n");
}

为了测试该函数,先将Loop函数设计成死循环!

Loop()

// 测试
void Loop()
{
    _isrunning = true;
    while(_isrunning)
    {
        sleep(1);
    }
    _isrunning = false;
}

2.4、Loop()

Loop() 函数一直执行服务!

执行服务函数主要分为两步:

  • 1、获取新连接(accept函数[从已完成连接队列的头部返回下一个已完成连接,如果队列为空,则阻塞调用进程])

accept()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数

  • sockfd:这是一个监听套接字的文件描述符,它应该是一个已经通过 socket 函数创建,并通过 bind 函数绑定到特定地址和端口,以及通过 listen 函数设置为监听模式的套接字
  • addr:这是一个指向 sockaddr 结构的指针,该结构用于存储接受连接的客户端的地址信息。如果不需要这个信息,可以传递 NULL
  • addrlen:这是一个指向 socklen_t 类型的变量的指针,用于存储 addr 结构的大小。在调用 accept 之前,应该将该变量的值设置为 addr 结构的大小。在调用返回后,该变量将包含实际返回的地址信息的长度。如果 addrNULL,则这个参数也可以是 NULL

返回值

  • 成功时,accept 函数返回一个新的套接字文件描述符,用于与接受的连接进行通信。这个新的套接字是原始监听套接字的子套接字,它继承了许多属性(如套接字选项),但与原始套接字是独立的。
  • 失败时,返回 -1,并设置 errno 以指示错误类型。

因此TcpServer类的_sockfd应该改为_listensockfd!!!

TcpServer类

// 面向字节流
class TcpServer
{
public:
    TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false)
    {}
    
    void InitServer()
    {
        // 1.创建socket
        _listensockfd = ::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd < 0)
        {
            LOG(FATAL,"socket create eror\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd: %d\n",_listensockfd); // 3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        // 2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
        {
            LOG(FATAL,"bind eror\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
        // 老板模式,随时等待被连接
        if(::listen(_listensockfd,gblcklog) < 0)
        {
            LOG(FATAL,"listen eror\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }
    ~TcpServer()
    {}
private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};
  • 2、执行服务(前提是获取到新连接)

执行服务总共有四个版本!

2.4.1、Server 0(不靠谱版本)

Server 0版本直接执行长服务

Loop()

Loop()函数先获取新连接,获取成功则执行服务函数!

void Loop()
{
    _isrunning = true;
    while(_isrunning)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // 1.获取新连接
        int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
        // 获取失败继续获取
        if(sockfd < 0)
        {
            LOG(WARNING,"sccept reeor\n");
            continue;
        }
        InetAddr addr(client);
        LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
        
        // 获取成功
        // version 0 -- 不靠谱版本
        Server(sockfd,addr);
    }
    _isrunning = false;
}

Server()

注意:tcp协议可以直接使用read,write函数读写文件描述符的内容(因为tcp是面向字节流的)!

Server()执行服务,先从文件描述符中读数据,再写数据到文件描述符中!

void Server(int sockfd,InetAddr addr)
{
    // 长服务
    while(true)
    {
        char inbuffer[1024]; // 当做字符串
        // 1.读文件
        ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            LOG(INFO,"get message from client [%s],message: %s\n",addr.AddrStr().c_str(),inbuffer);
            std::string echo_string = "[server echo]# ";
            echo_string += inbuffer;

            // 2.写文件
            write(sockfd,echo_string.c_str(),echo_string.size());
        }
        // 读到文件结尾
        else if(n == 0)
        {
            LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
            break;
        }
        else
        {
            LOG(ERROR,"read error\n",addr.AddrStr().c_str());
            break;
        }
    }
    ::close(sockfd);
}

2.4.2、Server 1(多进程版本)

多进程版本创建子进程,让子进程执行服务函数,父进程回收子进程,但是如果以阻塞等待回收子进程会有一个问题,如果子进程一直没有退出,那么父进程会一直阻塞!为了解决这个问题,我们可以让子进程再创建一个孙子进程,让孙子进程去执行服务函数,子进程直接退出,父进程回收子进程,孙子进程此时会成为孤儿进程,孤儿进程退出OS会自动回收

void Loop()
{
    _isrunning = true;
    while(_isrunning)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // 1.获取新连接
        int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
        // 获取失败继续获取
        if(sockfd < 0)
        {
            LOG(WARNING,"sccept reeor\n");
            continue;
        }
        InetAddr addr(client);
        LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
        
        // 获取成功
        // version 1 -- 多进程版本
        pid_t id = fork();
        if(id == 0)
        {
            // child
            ::close(_listensockfd); // 建议! 

            if(fork() > 0) exit(0); // 让孙子进程执行服务,保证能不阻塞

            Server(sockfd,addr);
            exit(0);
        }
        // father
        ::close(sockfd); // 防止文件描述符泄漏(打开的不关闭)
        int n = waitpid(id,nullptr,0); // 0阻塞等待  
        if(n > 0)
        {
            LOG(INFO,"wait child success\n");
        }
    }
    _isrunning = false;
}

2.4.3、Server 2(多线程版本)

多线程版本让新线程去执行服务函数,但是主线程需要回收新线程,为了做到主线程无需回收新线程,可以让新线程分离,此时无需回收新线程!还有一个问题,类内的成员函数有this指针,而新线程的函数只能有一个参数,此时需要使用静态成员函数,但是使用静态之后还有一个问题,不能看到类内的成员,此处可以使用地址传参将一个包含sockfd,TcpServer类的指针和InetAddr类的 成员变量的地址传入

内部类

// 内部类
class ThreadData
{
public:
    int _sockfd;
    TcpServer* _self;
    InetAddr _addr;
public:
    ThreadData(int sockfd,TcpServer* self,const InetAddr &addr)
        :_sockfd(sockfd),_self(self),_addr(addr)
    {}
};

Loop()

void Loop()
{
    _isrunning = true;
    while(_isrunning)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // 1.获取新连接
        int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
        // 获取失败继续获取
        if(sockfd < 0)
        {
            LOG(WARNING,"sccept reeor\n");
            continue;
        }
        InetAddr addr(client);
        LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
        
        // 获取成功
        // version 2 -- 多线程版 -- 不能关闭fd了,也不需要 
        pthread_t tid;
        ThreadData *td = new ThreadData(sockfd, this,addr);
        pthread_create(&tid,nullptr,Execute,td); // 新线程分离
    }
    _isrunning = false;
}

新线程执行函数

// 无法调用类内成员 无法看到sockfd
static void *Execute(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
    td->_self->Server(td->_sockfd,td->_addr);
    delete td;
    return nullptr;
}

2.4.4、Server 3(线程池版本)

线程池版本将执行服务的函数入线程池队列,该函数需要是参数为空和返回值为void的函数,因此需要bind绑定函数

声明函数类型

using task_t = std::function<void()>;

Loop()

void Loop()
{
    _isrunning = true;
    while(_isrunning)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // 1.获取新连接
        int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
        // 获取失败继续获取
        if(sockfd < 0)
        {
            LOG(WARNING,"sccept reeor\n");
            continue;
        }
        InetAddr addr(client);
        LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
        
        // 获取成功
        // version 3 -- 线程池版本
        task_t t = std::bind(&TcpServer::Server,this,sockfd,addr);
        ThreadPool<task_t>::GetInstance()->Equeue(t);
    }
    _isrunning = false;
}

3、TcpClientMain.cc

客户端主函数主要实现向服务端发送消息的功能,调用主函数使用该可执行程序 + IP + 端口号

主函数主要分为四步:

  • 1、创建socket(与服务端一样)
  • 2、与服务端建立连接(使用connect[客户端与服务器建立TCP连接])

connect()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数

  • sockfd:这是一个socket函数返回的套接字描述符
  • addr:这是一个指向sockaddr结构的指针,它包含了目标服务器的地址和端口信息。对于IPv4地址,通常使用sockaddr_in结构;对于IPv6地址,使用sockaddr_in6结构。
  • addrlen:这是**addr参数的长度,以字节为单位**。对于sockaddr_in,它通常是sizeof(struct sockaddr_in);对于sockaddr_in6,它通常是sizeof(struct sockaddr_in6)

返回值

  • 成功时,connect返回0。

  • 失败时,返回-1,并设置errno以指示错误类型。

  • 3、发送消息

  • 4、关闭socket

// ./tcpclient server-ip server-ip
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }
    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 << "create socket error" << std::endl;
        exit(1);
    }

    // 不需要显示的bind,但是一定要有自己的IP和port,需要隐式的绑定(OS用自己的IP和随机端口号)
    // 什么时候进行bind? If the connection or binding succeeds
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    // server.sin_addr.s_addr = 
    ::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);

    // 2.与服务端建立连接
    int n = ::connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    // 也可以重连
    if(n < 0)
    {
        std::cerr << "connect socket error" << std::endl;
        exit(2);
    }

    // 3.发送消息
    while(true)
    {
        std::string message;
        std::cout << "Enter#";
        std::getline(std::cin,message);

        write(sockfd,message.c_str(),message.size());
        char echo_buffer[1024];
        n = read(sockfd,echo_buffer,sizeof(echo_buffer));
        if(n > 0)
        {
            echo_buffer[n] = 0;
            std::cout << echo_buffer << std::endl;
        }
        else
        {
            break;
        }
    }

    // 4.关闭socket
    ::close(sockfd);
    return 0;
}

4、测试结果

4.1、不靠谱版本

该版本是一个只能执行一个客户端的版本,因此称为不靠谱版本!

4.2、多进程版本

该版本是一个能执行多客户端的版本,但是创建进程的开销比较大,也不是很完美!

4.3、多线程版本

该版本是一个能执行多客户端的版本,相比与多进程版本效果会更好,因为创建线程的开销比进程更少!

4.4、线程池版本

该版本是一个能执行多客户端的版本,与线程池版本差不太多,此处只是使用以前实现的线程池!

5、完整代码

前面一弹就有且没有修改的代码此处就没有再放上来了!

5.1、Makefile

.PHONY:all
all:tcpserver tcpclient

tcpserver:TcpServerMain.cc
    g++ -o $@ $^ -std=c++14

tcpclient:TcpClientMain.cc
    g++ -o $@ $^ -std=c++14

.PHONY:clean 
clean:
    rm -rf tcpserver tcpclient

5.2、TcpClientMain.cc

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// ./tcpclient server-ip server-ip
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }
    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 << "create socket error" << std::endl;
        exit(1);
    }

    // 不需要显示的bind,但是一定要有自己的IP和port,需要隐式的绑定(OS用自己的IP和随机端口号)
    // 什么时候进行bind? If the connection or binding succeeds
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    // server.sin_addr.s_addr = 
    ::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);

    // 2.与服务端建立连接
    int n = ::connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    // 也可以重连
    if(n < 0)
    {
        std::cerr << "connect socket error" << std::endl;
        exit(2);
    }

    // 3.发送消息
    while(true)
    {
        std::string message;
        std::cout << "Enter#";
        std::getline(std::cin,message);

        write(sockfd,message.c_str(),message.size());
        char echo_buffer[1024];
        n = read(sockfd,echo_buffer,sizeof(echo_buffer));
        if(n > 0)
        {
            echo_buffer[n] = 0;
            std::cout << echo_buffer << std::endl;
        }
        else
        {
            break;
        }
    }

    // 4.关闭socket
    ::close(sockfd);
    return 0;
}

5.3、TcpServer.hpp

#pragma once
#include <iostream>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace log_ns;

enum 
{
    SOCKET_ERROR,
    BIND_ERROR,
    LISTEN_ERROR
};

const static uint16_t gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;

using task_t = std::function<void()>;

// 面向字节流
class TcpServer
{
public:
    // _sockfd 版本
    // TcpServer(uint16_t port = gport):_port(port),_sockfd(gsockfd),_isrunning(false)
    // {}

    TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false)
    {}
    
    // _sockfd 版本
    // void InitServer()
    // {
    //     // 1.创建socket
    //     _sockfd = ::socket(AF_INET,SOCK_STREAM,0);
    //     if(_sockfd < 0)
    //     {
    //         LOG(FATAL,"socket create eror\n");
    //         exit(SOCKET_ERROR);
    //     }
    //     LOG(INFO,"socket create success,sockfd: %d\n",_sockfd); // 3

    //     struct sockaddr_in local;
    //     memset(&local,0,sizeof(local));
    //     local.sin_family = AF_INET;
    //     local.sin_port = htons(_port);
    //     local.sin_addr.s_addr = INADDR_ANY;

    //     // 2.bind sockfd 和 socket addr
    //     if(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
    //     {
    //         LOG(FATAL,"bind eror\n");
    //         exit(BIND_ERROR);
    //     }
    //     LOG(INFO,"bind success\n");

    //     // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
    //     // 老板模式,随时等待被连接
    //     if(::listen(_sockfd,gblcklog) < 0)
    //     {
    //         LOG(FATAL,"listen eror\n");
    //         exit(LISTEN_ERROR);
    //     }
    //     LOG(INFO,"listen success\n");
    // }

    void InitServer()
    {
        // 1.创建socket
        _listensockfd = ::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd < 0)
        {
            LOG(FATAL,"socket create eror\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd: %d\n",_listensockfd); // 3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        // 2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
        {
            LOG(FATAL,"bind eror\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
        // 老板模式,随时等待被连接
        if(::listen(_listensockfd,gblcklog) < 0)
        {
            LOG(FATAL,"listen eror\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }
    // 内部类
    class ThreadData
    {
    public:
        int _sockfd;
        TcpServer* _self;
        InetAddr _addr;
    public:
        ThreadData(int sockfd,TcpServer* self,const InetAddr &addr)
            :_sockfd(sockfd),_self(self),_addr(addr)
        {}
    };
    // 测试
    // void Loop()
    // {
    //     _isrunning = true;
    //     while(_isrunning)
    //     {
    //         sleep(1);
    //     }
    //     _isrunning = false;
    // }
    void Loop()
    {
        _isrunning = true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 1.获取新连接
            int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
            // 获取失败继续获取
            if(sockfd < 0)
            {
                LOG(WARNING,"sccept reeor\n");
                continue;
            }
            InetAddr addr(client);
            LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
            
            // 获取成功
            // version 0 -- 不靠谱版本
            // Server(sockfd,addr);

            // version 1 -- 多进程版本
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // child
            //     ::close(_listensockfd); // 建议! 

            //     if(fork() > 0) exit(0); // 让孙子进程执行服务,保证能不阻塞

            //     Server(sockfd,addr);
            //     exit(0);
            // }
            // // father
            // ::close(sockfd); // 防止文件描述符泄漏(打开的不关闭)
            // int n = waitpid(id,nullptr,0); // 0阻塞等待  
            // if(n > 0)
            // {
            //     LOG(INFO,"wait child success\n");
            // }

            // version 2 -- 多线程版 -- 不能关闭fd了,也不需要 
            // pthread_t tid;
            // ThreadData *td = new ThreadData(sockfd, this,addr);
            // pthread_create(&tid,nullptr,Execute,td); // 新线程分离

            // version 3 -- 线程池版本
            task_t t = std::bind(&TcpServer::Server,this,sockfd,addr);
            ThreadPool<task_t>::GetInstance()->Equeue(t);
        }
        _isrunning = false;
    }
    // 无法调用类内成员 无法看到sockfd
    static void *Execute(void *args)
    {
        ThreadData *td = static_cast<ThreadData *>(args);
        pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
        td->_self->Server(td->_sockfd,td->_addr);
        delete td;
        return nullptr;
    }
    void Server(int sockfd,InetAddr addr)
    {
        // 长服务
        while(true)
        {
            char inbuffer[1024]; // 当做字符串
            // 1.读文件
            ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer) - 1);
            if(n > 0)
            {
                inbuffer[n] = 0;
                LOG(INFO,"get message from client [%s],message: %s\n",addr.AddrStr().c_str(),inbuffer);
                std::string echo_string = "[server echo]# ";
                echo_string += inbuffer;

                // 2.写文件
                write(sockfd,echo_string.c_str(),echo_string.size());
            }
            // 读到文件结尾
            else if(n == 0)
            {
                LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error\n",addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }
    ~TcpServer()
    {}
private:
    uint16_t _port;
    // int _sockfd; // TODO
    int _listensockfd;
    bool _isrunning;
};

5.4、TcpServerMain.cc

#include "TcpServer.hpp"
#include <memory>

// ./tcpserver 8888
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);

    tsvr->InitServer();
    tsvr->Loop();
    return 0;
}

注意:线程池只需将全局变量gdefaultnum改为10即可!

static const int gdefaultnum = 10; // 默认创建10个线程
标签: 网络 linux 运维

本文转载自: https://blog.csdn.net/2201_75584283/article/details/143091428
版权归原作者 小林熬夜学编程 所有, 如有侵权,请联系我们删除。

“【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解”的评论:

还没有评论