多进程版2.0父子进程与2.1版本父子孙进程(摘要)

本节介绍 TCP 的套接字。 TCP通信有四种版本:

1.单进程版,没人用,不像UDP,TCP单进程只能和一个人通信! !

2.多进程版本2.0父子进程和2.1版本兄弟孙进程

3.普通多线程版本

4.单例模式线程池的多线程版本

代码上面的注释很重要应用编程接口和套接字,请仔细阅读! ! !

TCP socket(部分部分与UDP相同,不再介绍)

服务器需要以下功能

1.监听套接字用于与对端的套接字建立连接。服务器必须始终保持监听并等待随时与客户连接

2.accept socket应用编程接口和套接字,listen负责与peer建立连接,accept负责与peer通信,注意函数返回真正与peer通信的socket(文件描述符

3.read,因为socket本身是一个文件描述符,所以可以使用read来接收信息,和UDP中引入的recvfrom函数是一样的

4.write,因为socket本身是一个文件描述符,所以可以使用write来发送信息,和UDP中引入的sendto函数是一样的

5.ip地址的主机顺序转换为网络字节顺序,反之亦然

客户端需要以下功能

1.与服务器连接套接字连接

白话总结TCP流程

Server:前两步同UDP

1.创建一个通信套接字,本质上是打开一个文件——只和系统有关

2.bind, struct sockaddr_in—> ip + port,本质是将ip + port与文件信息关联起来

3.listen建立连接,本质上是设置socket文件的状态,允许别人连接我

4.accept 启动通信服务,本质是获取到应用层的新连接,以fd为代表。因为操作系统会“先描述,组织”所有的连接,把所有的信息组织成结构

使用文件描述符来表示每个结构体,找到文件描述符,就可以找到对应的结构体信息。

所谓的“链接”,在OS层面,本质上是一个描述链接结构的“文件”

5.读/写的本质是网络通信。对于用户来说,相当于正常读写文件

6.connect 本质上是发起一个连接。在系统层面,就是建立一个请求消息,发送过去。在网络层面,发起 TCP 三向握手(握手和挥手后面会解释)

7.close,客户端&&服务器关闭文件,a。系统级,释放已申请的文件资源、连接资源等。 b.在网络层面,通知对方我的连接已经关闭!

本质在网络层面,挥手四次(后文解读)

四个反例理解TCP socket(四个反例的客户端和Makefile一样,主要区别是服务端)1.单进程版,没人用,不像UDP、TCP单进程只能一个人通信! !

生成文件

.PHONY:all
all: tcp_client tcp_server
tcp_client:tcp_client.cpp
	g++ -o $@ $^ -std=c++11 -pthread
tcp_server:tcp_server.cpp
	g++ -o $@ $^ -std=c++11 -pthread
.PHONY:clean
clean:
	rm -rf tcp_client tcp_server

tcp_server.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
void Usage()
{
    cout << "输入格式有误"
         << "应为:./tcp_server + 端口号" << endl;
}
int main(int argv, char *argc[])
{
    if (argv != 2)
    {
        Usage();
        return 1;
    }
    // 1.创建套接字
    // SOCK_STREAM流式操作表示TCP
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        cerr << "sock create fail" << errno << endl;
        return 2;
    }
    // 2.bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local)); //为安全着想,将结构体变量初始化
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argc[1])); //此处的端口号,是我们计算机上的变量,是主机序列, 因为在栈上创建的,我们要转为网络字节序需要利用htons函数前面有介绍
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind  fail" << errno << endl;
        return 3;
    }
    //前面除了SOCK_STREAM都和UDP相同,现在开始有区别了
    // 3. 因为tcp是面向连接的, a.在通信前,需要建连接 b. 然后才能通信
    //   一定有人主动建立(客户端,需要服务),一定有人被动接受连接(服务器,提供服务)
    //   我们当前写的是一个server, 周而复始的不间断的等待客户到来
    //   我们要不断的给用户提供一个建立连接的功能
    //
    //   设置套接字是Listen状态, 本质是允许用户连接
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        cerr << "listen  fail" << errno << endl;
        return 3;
    }
    //version 1 : 现在做的是单进程版,没人使用,与UDP不同,TCP单进程只能与一个人进行通信!!
    for (;;)
    {
        //我们需要提供一段空间来接收客户端的信息
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //注意返回值也是个文件描述符,listen_sock负责用来等待客户端连接,newsock是真正与客户端进行IO通信的套接字,具体看函数介绍!
        int newsock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (newsock < 0)
        {
            continue;
        }
        cout<<"与客户端连接成功!"<<endl;
        //链接成功开始服务
        while (true)
        {
            //应为套接字也是文件描述符,所以当然可以用read与write来进行通信,效果与UDP中介绍的recvfrom与sendto相同
            char buffer[1024];
            memset(&buffer, 0, sizeof(buffer));
            ssize_t s = read(newsock, buffer, sizeof(buffer));
            if (s > 0) //说明接收到了信息
            {
                buffer[s] = 0; //将获取的内容当成字符串,尾部加0
                cout << "成功接收到客户端信息:" << buffer << endl;
                string message = "老哥我收到了!";
                write(newsock, message.c_str(), message.size());
            }
            else if (s == 0) //说明客户端推出了链接
            {
                cout << " 客户端已退出 " << endl;
                break;
            }
            else
            {
                cout << "read errno..." << endl;
                break;
            }
        }
    }
    return 0;
}

tcp_client.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void Usage()
{
    cout << "输入格式有误"
         << "应为:./tcp_client + ip + 端口号" << endl;
}
int main(int argv, char *argc[])
{
    if (argv != 3)
    {
        Usage();
        return 1;
    }
    // 1. 创建socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "sock create fail" << errno << endl;
        return 2;
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons((uint16_t)atoi(argc[2]));//server_port
    // inet_addr 该函数做两件事情
    // 1. 将点分十进制的字符串风格的IP,转化成为4字节IP
    // 2. 将4字节由主机序列转化成为网络序列
    server.sin_addr.s_addr = inet_addr(argc[1]);//server_ip
    //与UDP一样客户端不需要显示bind,OS会自动帮我们安全的绑定
    // TCP的客户端需要用connect函数与服务端进行连接
    // 2. 发起链接
    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        cerr << "connect  fail" << errno << endl;
        return 3;
    }
    cout << "connect success!" << endl;
    while (true)
    {
        cout << "请输入内容:";
        char buffer[1024];
        memset(&buffer, 0, sizeof(buffer));
        fgets(buffer, sizeof(buffer) - 1, stdin);
        write(sock, buffer, sizeof(buffer));
        char recvbuf[1024];
        memset(&recvbuf, 0, sizeof(recvbuf));
        ssize_t s = read(sock, recvbuf, sizeof(recvbuf));
        if (s > 0)
        {
            recvbuf[s] = 0;
            cout << "收到服务端返回信息:" << recvbuf << endl;
        }
    }
    return 0;
}

对于下面三个反例的子进程或线程,使用后记得关闭socket,否则会被占用,并且socket数量有限。 2.多进程版本2.0兄弟进程和2.1版本兄弟进程

tcp_server.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void Usage()
{
    cout << "输入格式有误"
         << "应为:./tcp_server + 端口号" << endl;
}
void ServiceIO(int newsock)
{
    //链接成功开始服务
    while (true)
    {
        //应为套接字也是文件描述符,所以当然可以用read与write来进行通信,效果与UDP中介绍的recvfrom与sendto相同
        char buffer[1024];
        memset(&buffer, 0, sizeof(buffer));
        ssize_t s = read(newsock, buffer, sizeof(buffer));
        if (s > 0) //说明接收到了信息
        {
            buffer[s] = 0; //将获取的内容当成字符串,尾部加0
            cout << "成功接收到客户端信息:" << buffer << endl;
            string message = "老哥我收到了!";
            write(newsock, message.c_str(), message.size());
        }
        else if (s == 0) //说明客户端推出了链接
        {
            cout << " 客户端已退出 " << endl;
            break;
        }
        else
        {
            cout << "read errno..." << endl;
            break;
        }
    }
}
int main(int argv, char *argc[])
{
    if (argv != 2)
    {
        Usage();
        return 1;
    }
    // 1.创建套接字
    // SOCK_STREAM流式操作表示TCP
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        cerr << "sock create fail" << errno << endl;
        return 2;
    }
    // 2.bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local)); //为安全着想,将结构体变量初始化
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argc[1])); //此处的端口号,是我们计算机上的变量,是主机序列, 因为在栈上创建的,我们要转为网络字节序需要利用htons函数前面有介绍
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind  fail" << errno << endl;
        return 3;
    }
    //前面除了SOCK_STREAM都和UDP相同,现在开始有区别了
    // 3. 因为tcp是面向连接的, a.在通信前,需要建连接 b. 然后才能通信
    //   一定有人主动建立(客户端,需要服务),一定有人被动接受连接(服务器,提供服务)
    //   我们当前写的是一个server, 周而复始的不间断的等待客户到来
    //   我们要不断的给用户提供一个建立连接的功能
    //
    //   设置套接字是Listen状态, 本质是允许用户连接
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        cerr << "listen  fail" << errno << endl;
        return 3;
    }
    //多进程2.0版本   2.1版本不需要写,因为资源会自动被OS释放掉
    signal(SIGCHLD, SIG_IGN); //在Linux中父进程忽略子进程的SIGCHLD信号,子进程会自动退出释放资源
    for (;;)
    {
        //我们需要提供一段空间来接收客户端的信息
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //注意返回值也是个文件描述符,listen_sock负责用来等待客户端连接,newsock是真正与客户端进行IO通信的套接字,具体看函数介绍!
        int newsock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (newsock < 0)
        {
            continue;
        }
        uint16_t cli_port = ntohs(peer.sin_port); //将网络序转为主机序
        string cli_ip = inet_ntoa(peer.sin_addr); //将网络序转主机序,再将主机序的4字节转为字符串序列
        cout << "与客户端连接成功,套接字为:" << newsock << " ip :" << cli_ip << " port :" << cli_port << endl;
        pid_t id = fork();
        if (id < 0)
        {
            cout << "创建子进程失败" << endl;
            continue;
        }
        //多进程2.0版本
        else if (id == 0) //曾经被父进程打开的fd,是否会被子进程继承呢? 无论父子进程中的哪一个,强烈建议关闭掉不需要的fd
        {
            // child
            close(listen_sock);
            ServiceIO(newsock);
            //子进程使用完要把文件描述符关闭了,不然文件描述符数量是有上限的
            //如果不关闭不需要的文件描述符,会造成文件描述符泄露
            exit(0);
        }
        else
        {
            //正常父进程要阻塞等待子进程wait_pid(),为了提高效率,利用信号学的知识signal,忽略掉子进程,让子进程结束了自己退出释放资源。
            // father
            // do nothing
            close(newsock);
        }
        //多进程2.1版本
        // else if (id == 0) //曾经被父进程打开的fd,是否会被子进程继承呢? 无论父子进程中的哪一个,强烈建议关闭掉不需要的fd
        // {
        //     close(listen_sock);
        //     if (fork() > 0)
        //         exit(0); //退出的是子进程
        //     //向后走的是孙子进程, 因为孙子的父亲进程已经退出了,孙子成为孤儿进程,会由操作系统对他进行资源释放
        //     ServiceIO(newsock);
        //     exit(0);
        // }
        // else
        // {
        //     //正常父进程要阻塞等待子进程wait_pid(),为了提高效率,利用信号学的知识signal,忽略掉子进程,让子进程结束了自己退出释放资源。
        //     // father
        //     // do nothing
        //     //2.1版本需要接收子进程,但不会造成堵塞,因为子进程刚创建就结束了,主要是孙子进程在工作,与父进程无关!
        //     waitpid(id, nullptr, 0);
        //     close(newsock);
        // }
    }
    return 0;
}

运行结果如下:因为子2.0进程或者2.1子进程在完成任务后关闭了文件描述符4,所以重连还是会使用4,不会占用多个。

3.普通多线程版本

tcp_server.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void Usage()
{
    cout << "输入格式有误"
         << "应为:./tcp_server + 端口号" << endl;
}
void ServiceIO(int newsock)
{
    //链接成功开始服务
    while (true)
    {
        //应为套接字也是文件描述符,所以当然可以用read与write来进行通信,效果与UDP中介绍的recvfrom与sendto相同
        char buffer[1024];
        memset(&buffer, 0, sizeof(buffer));
        ssize_t s = read(newsock, buffer, sizeof(buffer));
        if (s > 0) //说明接收到了信息
        {
            buffer[s] = 0; //将获取的内容当成字符串,尾部加0
            cout << "成功接收到客户端信息:" << buffer << endl;
            string message = "老哥我收到了!";
            write(newsock, message.c_str(), message.size());
        }
        else if (s == 0) //说明客户端推出了链接
        {
            cout << " 客户端已退出 " << endl;
            break;
        }
        else
        {
            cout << "read errno..." << endl;
            break;
        }
    }
}
void *Handler(void * argc)
{
    //线程分离,自动释放资源
    pthread_detach(pthread_self());
    int sock = *(int *)argc;
    ServiceIO(sock);
    close(sock);
}
int main(int argv, char *argc[])
{
    if (argv != 2)
    {
        Usage();
        return 1;
    }
    // 1.创建套接字
    // SOCK_STREAM流式操作表示TCP
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        cerr << "sock create fail" << errno << endl;
        return 2;
    }
    // 2.bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local)); //为安全着想,将结构体变量初始化
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argc[1])); //此处的端口号,是我们计算机上的变量,是主机序列, 因为在栈上创建的,我们要转为网络字节序需要利用htons函数前面有介绍
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind  fail" << errno << endl;
        return 3;
    }
    //前面除了SOCK_STREAM都和UDP相同,现在开始有区别了
    // 3. 因为tcp是面向连接的, a.在通信前,需要建连接 b. 然后才能通信
    //   一定有人主动建立(客户端,需要服务),一定有人被动接受连接(服务器,提供服务)
    //   我们当前写的是一个server, 周而复始的不间断的等待客户到来
    //   我们要不断的给用户提供一个建立连接的功能
    //
    //   设置套接字是Listen状态, 本质是允许用户连接
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        cerr << "listen  fail" << errno << endl;
        return 3;
    }
    //多线程版
    for (;;)
    {
        //我们需要提供一段空间来接收客户端的信息
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //注意返回值也是个文件描述符,listen_sock负责用来等待客户端连接,newsock是真正与客户端进行IO通信的套接字,具体看函数介绍!
        int newsock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (newsock < 0)
        {
            continue;
        }
        uint16_t cli_port = ntohs(peer.sin_port); //将网络序转为主机序
        string cli_ip = inet_ntoa(peer.sin_addr); //将网络序转主机序,再将主机序的4字节转为字符串序列
        cout << "与客户端连接成功,套接字为:" << newsock << " ip :" << cli_ip << " port :" << cli_port << endl;
        //曾经被主线程打开的fd,新线程是共享的,因此不能关闭文件描述符,只要在线程执行函数中关闭就好了
        pthread_t tid;
        pthread_create(&tid, nullptr, Handler, (void *)(&newsock));
    }
    return 0;
}

运行结果如下:因为文件描述符在线程完成任务后关闭,所以多个连接不会占用多个描述符。通常只需要打开 4 个,因为 4 个用完然后关闭。回来,显示5的问题可能是复制问题。

4.单例模式线程池的多线程版本

tcp_server.cpp

#include 
using namespace std;
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include"task.hpp"
#include"thread_pool.hpp"
using namespace ns_task;
using namespace ns_thread;
void Usage()
{
    cout << "输入格式有误"
         << "应为:./tcp_server + 端口号" << endl;
}
int main(int argv, char *argc[])
{
    if (argv != 2)
    {
        Usage();
        return 1;
    }
    // 1.创建套接字
    // SOCK_STREAM流式操作表示TCP
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        cerr << "sock create fail" << errno << endl;
        return 2;
    }
    // 2.bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local)); //为安全着想,将结构体变量初始化
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argc[1])); //此处的端口号,是我们计算机上的变量,是主机序列, 因为在栈上创建的,我们要转为网络字节序需要利用htons函数前面有介绍
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind  fail" << errno << endl;
        return 3;
    }
    //前面除了SOCK_STREAM都和UDP相同,现在开始有区别了
    // 3. 因为tcp是面向连接的, a.在通信前,需要建连接 b. 然后才能通信
    //   一定有人主动建立(客户端,需要服务),一定有人被动接受连接(服务器,提供服务)
    //   我们当前写的是一个server, 周而复始的不间断的等待客户到来
    //   我们要不断的给用户提供一个建立连接的功能
    //
    //   设置套接字是Listen状态, 本质是允许用户连接
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        cerr << "listen  fail" << errno << endl;
        return 3;
    }
    //线程池版
    for (;;)
    {
        //我们需要提供一段空间来接收客户端的信息
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //注意返回值也是个文件描述符,listen_sock负责用来等待客户端连接,newsock是真正与客户端进行IO通信的套接字,具体看函数介绍!
        int newsock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (newsock < 0)
        {
            continue;
        }
        uint16_t cli_port = ntohs(peer.sin_port); //将网络序转为主机序
        string cli_ip = inet_ntoa(peer.sin_addr); //将网络序转主机序,再将主机序的4字节转为字符串序列
        cout << "与客户端连接成功,套接字为:" << newsock << " ip :" << cli_ip << " port :" << cli_port << endl;
        Task t(newsock);
        ThreadPool<Task>::GetInstance()->Push(t);
        
    }
    return 0;
}

thread_pool.hpp

#pragma once
#include 
using namespace std;
#include 
#include 
#include 
namespace ns_thread
{
    const int default_num = 5;
    pthread_mutex_t mtx;
    pthread_cond_t c_cnd;
    template <class T>
    class ThreadPool
    {
    private:
        //线程池中线程个数
        int _num;
        queue<T> task_queue;
        //静态成员变量要在类外初始化!!
        static ThreadPool<T> *ins;
        //单例模式,构造函数必须得实现,但是必须的私有化
        ThreadPool(int num = default_num)
            : _num(num)
        {
            pthread_mutex_init(&mtx, nullptr);
            pthread_cond_init(&c_cnd, nullptr);
        }
        //拷贝构造
        ThreadPool(const ThreadPool<T> &tp) = delete;
        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;
    public:
        void Lock()
        {
            pthread_mutex_lock(&mtx);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&c_cnd, &mtx);
        }
        void WakeUp()
        {
            pthread_cond_signal(&c_cnd);
        }
        bool IsEmpty()
        {
            return task_queue.empty();
        }
    public:
        static ThreadPool<T> *GetInstance()
        {
            static pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;
            //双判断,减少锁的征用,提高获取单例的效率
            if (ins == nullptr)
            {
                pthread_mutex_lock(&_lock);
                if (ins == nullptr)
                {
                    //初始化
                    ins = new ThreadPool<T>();
                    ins->InitThreadPool();
                    cout << "首次加载对象" << endl;
                }
                pthread_mutex_unlock(&_lock);
            }
            return ins;
        }
        // 在类中要让线程执行类内成员方法,是不可行的!!因为会有隐藏this指针就不符合创建线程的格式了
        // 必须让线程执行静态方法,静态成员函数无法访问私有属性
        static void *Rountine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T> *)args;
            while (true)
            {
                tp->Lock();
                while (tp->IsEmpty())
                {
                    //挂起等待
                    tp->Wait();
                }
                //处理任务
                T t;
                tp->Pop(&t);
                tp->Unlock();
                //这里先释放锁再进行任务处理,能达到一个线程处理任务,另一个线程可以抢锁获取数据,达到了并行的效果。
                //如果先处理任务再释放锁,那么每个线程处理任务就是串行的,效率肯定没有并行的高
                t.Run();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < _num; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this);
            }
        }
        void Push(const T &in)
        {
            Lock();
            task_queue.push(in);
            Unlock();
            //唤醒线程处理任务
            WakeUp();
        }
        void Pop(T *out)
        {
            *out = task_queue.front();
            task_queue.pop();
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx);
            pthread_cond_destroy(&c_cnd);
        }
    };
    template <class T>
    ThreadPool<T> *ThreadPool<T>::ins = nullptr;
}

task.hpp

#pragma once
#include 
using namespace std;
#include 
namespace ns_task
{
    class Task
    {
    private:
        int _sock;
    public:
        Task(){};
        Task(int sock)
            : _sock(sock)
        {
        }
        void Run()
        {
            //链接成功开始服务
            while (true)
            {
                //应为套接字也是文件描述符,所以当然可以用read与write来进行通信,效果与UDP中介绍的recvfrom与sendto相同
                char buffer[1024];
                memset(&buffer, 0, sizeof(buffer));
                ssize_t s = read(_sock, buffer, sizeof(buffer));
                if (s > 0) //说明接收到了信息
                {
                    buffer[s] = 0; //将获取的内容当成字符串,尾部加0
                    cout << "成功接收到客户端信息:" << buffer << endl;
                    string message = "老哥我收到了!";
                    write(_sock, message.c_str(), message.size());
                }
                else if (s == 0) //说明客户端推出了链接
                {
                    cout << " 客户端已退出 " << endl;
                    break;
                }
                else
                {
                    cout << "read errno..." << endl;
                    break;
                }
            }
            close(_sock);
        }
        ~Task(){};
    };
}

线程池的作用如下:第一次建立连接时,建立多个线程。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 网站程序 多进程版2.0父子进程与2.1版本父子孙进程(摘要) https://www.wkzy.net/game/8236.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务