Linux之网络学习第三部分:网络套接字编程之TCP协议

本节为介绍TCP的接口,一共有四种版本的TCP通信分别为:
1.单进程版,没人使用,与UDP不同,TCP单进程只能与一个人进行通信!!
2.多进程版2.0父子进程与2.1版本父子孙进程
3.普通多线程版
4.单例模式线程池的多线程版本
代码里面的注释很重要,要仔细看!!!

TCP接口(有的部分与UDP相同,在此不介绍了)

对于服务端需要以下函数
1.listen接口,用来与对端的套接字建立连接,服务端要始终保持listen,随时等待与客户连接
在这里插入图片描述
2.accept接口,listen负责与对端建立连接, accept负责与对端进行通信,注意函数返回的是真正与对端通信的套接字(文件描述符)
在这里插入图片描述
3.read,因为套接字本身就是文件描述符,因此可以用read接收信息,与UDP中介绍的recvfrom函数效果相同
在这里插入图片描述

4.write,因为套接字本身就是文件描述符,因此可以用write发送信息,与UDP中介绍的sendto函数效果相同
在这里插入图片描述
5.ip地址的主机序转网络字节序与反过来
在这里插入图片描述

对于客户端需要以下函数
1.connect与服务端连接接口
在这里插入图片描述

大白话总结TCP流程

服务端:前两步与UDP相同
1.创建通信套接字socket,本质是打开文件–仅仅与系统相关
2.bind,struct sockaddr_in—> ip + port,本质是将ip + port和文件信息相关联
3.listen建立连接, 本质是设置socket文件的状态,允许别人来连接我
4.accept开始进行通信服务, 本质是获取新连接到应用层,以fd为代表。因为OS会对所有的连接“先描述,在组织”,将所有信息组成结构体
用文件描述符来代表每个结构体,找到文件描述符,就能找到与之对应的结构体信息。
所谓的“连接”,在OS层面,本质上就是一个描述链接结构体的《文件》
5.read/write 本质就是进行网络通信,对于用户来讲,相当于我们在对文件进行正常的读写
6.connect,本质是发起连接,在系统层面,就是构建一个请求报文发送过去,在网络层面,发起TCP三次握手(以后详解握手与挥手)
7.close,client && server 关闭文件,a.系统层面,释放已经申请的文件资源,连接资源等。b.网络层面,通知对方我的连接已经关闭了!
本质在传输层面,进行四次挥手(以后详解)

四个例子了解TCP接口(四种例子的客户端与Makefile一样主要区别在服务端)

1.单进程版,没人使用,与UDP不同,TCP单进程只能与一个人进行通信!!

Makefile

.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 <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>

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 <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <string>

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;
}

下面三个例子的子进程或线程,套接字使用过后记得关闭,否则会被占用,套接字数量是有上限的。

2.多进程版2.0父子进程与2.1版本父子孙进程

tcp_server.cpp

#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
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;
        }
    }
    close(newsock);
}

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)
        {
        	close(newsock);
            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 <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <pthread.h>
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 <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <pthread.h>

#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 <iostream>
using namespace std;
#include <pthread.h>
#include <queue>
#include <unistd.h>
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 <iostream>
using namespace std;
#include <pthread.h>

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(){};
    };
}

线程池效果如下:当第一次建立连接,建立多个线程。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值