【Linux】网络编程之套接字 --TCP

news2025/2/25 8:21:38

目录

  • 🌈前言
  • 🌸1、TCP相关API
    • 🍡1.1、socket函数
    • 🍢1.2、bind函数
    • 🍧1.3、listen函数
    • 🍨1.4、accept函数
    • 🍰1.5、connect函数
  • 🌺2、TCP网络编程
    • 🍡2.1、简单TCP通信程序 -- 多进程版本
    • 🍢2.2、简单TCP通信程序 -- 多线程版本
    • 🍧2.3、简单TCP通信程序 -- 线程池版本
  • 🍀3、部署服务器
    • 🍡3.1、会话和进程组
    • 🍢3.2、守护/精灵进程
    • 🍧3.3、实现守护进程
    • 🍨3.4、部署服务器

🌈前言

这篇文章给大家带来套接字的学习!!!


🌸1、TCP相关API

      • 【UDP中已经详细介绍了背景知识和struct sockaddr结构:跳转】

使用TCP网络通信所用到的接口比UDP多了几个,因为TCP是需要进行连接的(面向连接)

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

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

// 将套接字设置为监听用户到来的连接请求状态,监听是否有客户到来
int listen(int sockfd, int backlog);

// 服务器启动后,获取新的客户端的连接,该函数返回新的服务套接字,用于服务客户的请求(流式IO服务)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

// 客户端向指定服务器发送连接请求
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 指定填充确定IP地址, 转化字符串风格IP("xx.zz.yy.sss"),并且自动进行主机字节序转换网络字节序
in_addr_t inet_addr(const char *cp));


🍡1.1、socket函数

在这里插入图片描述

函数解析

  • 作用:它是用来创建套接字文件的,创建成功返回一个文件描述符失败返回-1,并且设置errno

  • domain:它需要我们填充协议家族(地址类型)来指定网络协议,IPv4指定为:AF_INET

  • type:它需要我们填充通信类型,UDP协议是面向数据报的,我们填充SOCK_DGRAM即可,如果是TCP协议,那么填充SOCK_STREAM(其他详细查看man手册)

  • protocol:套接口所用的协议,一般为0,不指定

void Test()
{
	// SOCK_STREAM: 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,网络地址族使用TCP协议
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0)
    {
            std::cout << "socket error: " << strerror(errno) << std::endl;
            exit(1);
    }
}

🍢1.2、bind函数

在这里插入图片描述

函数解析

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,服务器需要调用bind绑定一个固定的网络地址和端口号

  • 作用:绑定网络信息(地址类型、端口号和IP地址)到内核中,成功返回0,错误返回-1,并且设置errno

  • addr:传sockaddr_in/sockaddr_un的地址,并且需要强转为通用类型sockaddr*

  • addrlen:sockaddr_in/sockaddr_un结构体所占内存空间的大小

void Test(uint16_t port, const string &ip)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        cout << "socket error: " << strerror(errno) << endl;
        exit(1);
    }
    // 2. 绑定主机基本信息到内核
    struct sockaddr_in Server;
    socklen_t len = sizeof(Server);
    memset(&Server, 0, len);
    Server.sin_family = AF_INET;                                                     // "地址类型/协议家族"
    Server.sin_port = htons(port);                                                   // 端口号
    Server.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str()); // IP地址
    if (bind(fd, (const struct sockaddr *)&Server, len) < 0)
    {
        std::cout << "bind error: " << strerror(errno) << std::endl;
        exit(1);
    }
}

🍧1.3、listen函数

在这里插入图片描述

函数解析

  • 作用:将套接字fd设置为监听到来的“连接请求状态”监听是“否有”客户到来(TCP -> 面向连接)

  • sockfd:套接字文件描述符,socket函数的返回值

  • backlog:套接字的挂起"连接队列"的最大长度(一般由2到4),用SOMAXCONN则为系统给出的最大值(最多允许有backlog个客户端处于连接等待状态)

  • 返回值:成功返回0,失败返回-1,并且设置全局errno错误码

void Test(uint16_t port, const string &ip)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        cout << "socket error: " << strerror(errno) << endl;
        exit(1);
    }
    // 2. 绑定主机基本信息到内核
    struct sockaddr_in Server;
    socklen_t len = sizeof(Server);
    memset(&Server, 0, len);
    Server.sin_family = AF_INET;                                                     // "地址类型/协议家族"
    Server.sin_port = htons(port);                                                   // 端口号
    Server.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str()); // IP地址
    if (bind(fd, (const struct sockaddr *)&Server, len) < 0)
    {
        std::cout << "Server bind error: " << strerror(errno) << std::endl;
        exit(BIND_ERR);
    }

    // 3. 将套接字设置为监听到来的连接请求状态,监听是否有客户到来(TCP -> 面向连接)
    // 第二个参数:套接字的挂起"连接队列"的最大长度(一般由2到4), 用SOMAXCONN则为系统给出的最大值
    if (listen(fd, SOMAXCONN) < 0)
    {
        std::cout << "listen error: " << strerror(errno) << std::endl;
        exit(3);
    }
}

🍨1.4、accept函数

在这里插入图片描述

函数解析

  • 作用:三次握手完成后(后面网络基础再解析), 服务器调用accept()接受连接

  • 如果服务器调用accept()时还没有客户端的连接请求,就一直阻塞等待直到有客户端连接到来

  • sockfd:套接字文件描述符,socket函数的返回值

  • addr是一个输出型参数,它由accept函数内部自动填充,它用于获取客户端的IP地址和端口号

  • addrlen参数是一个输入输出参数,输入需要调用者提供缓冲区addr的长度,以避免缓冲区溢出问题,输出的是客户端地址结构体的实际长度

  • 该函数返回新的“服务”套接字,用于服务客户端的请求和回应(流式IO服务)

void Test(uint16_t port, const string &ip)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        cout << "socket error: " << strerror(errno) << endl;
        exit(1);
    }
    // 2. 绑定主机基本信息到内核
    struct sockaddr_in Server;
    socklen_t len = sizeof(Server);
    memset(&Server, 0, len);
    Server.sin_family = AF_INET;                                                     // "地址类型/协议家族"
    Server.sin_port = htons(port);                                                   // 端口号
    Server.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str()); // IP地址
    if (bind(fd, (const struct sockaddr *)&Server, len) < 0)
    {
        std::cout << "Server bind error: " << strerror(errno) << std::endl;
        exit(2);
    }

    // 3. 将套接字设置为监听到来的连接请求状态,监听是否有客户到来(TCP -> 面向连接)
    if (listen(fd, SOMAXCONN) < 0)
    {
        std::cout << "Server listen error: " << strerror(errno) << std::endl;
        exit(3);
    }
	
	// 4. 获取客户端的连接请求 -- 这里没有写循环
    struct sockaddr_in Client;
    socklen_t len = sizeof(Client);
    memset(&Client, 0, len);
    int Serverfd = accept(fd, (sockaddr*)&Client, &len);
    if (Serverfd < 0)
    {
        cout << "accept error: " << strerror(errno) << endl;
        exit(4);
    }
    close(Serverfd );
}

🍰1.5、connect函数

在这里插入图片描述

函数解析

  • 作用:客户端向指定服务器发送连接请求,默认自动绑定本主机的sockaddr_in信息

  • 最好不要自己bind,因为我们绑定的固定端口号可能被其他客户端给占用了,由OS自动分配即可

  • connect和bind的参数一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址

  • 返回值:connect()成功返回0,出错返回-1,并且设置全局errno

void Init(uint16_t port_, const string ip_)
{
    // 1. 获取sockfd
    int sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd_ < 0)
    {
        std::cout << "Server make socket error: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }
    
    // 填充指定Server的sockaddr_in信息
    struct sockaddr_in Server;
    socklen_t len = sizeof(Server);
    memset(&Server, 0, len);
    Server.sin_family = AF_INET;
    Server.sin_port = htons(port_);
    Server.sin_addr.s_addr = inet_addr(ip_.c_str());
    
    // 2. 向指定服务器发送连接请求 -- connet默认会自动绑定本主机的sockaddr_in信息
    // 最好不要自己bind,因为我们绑定的固定端口号可能被其他客户端给占用了,由OS自动分配即可
    if (connect(sockfd_, (const struct sockaddr *)&Server, len) < 0)
    {
        std::cout << "Client connet Server error: " << strerror(errno) << std::endl;
        exit(1);
    }
}

🌺2、TCP网络编程

🍡2.1、简单TCP通信程序 – 多进程版本

实现大小写转换服务器 – 使用TCP协议

  • 注意:TCP于UDP不同,UDP是不需要连接的,服务器请求到来时会立刻处理(recvfrom和sendto),多用户发送请求时,是不会阻塞住的!!!

  • TCP是需要连接的,设置监听套接字后,在等待获取用户连接时是会阻塞的,如果只用一个死循环进行获取请求和服务,那么将会导致其他用户连接不了,因为要等第一个用户的请求工作完了才行

  • 这里我们设置一个多进程,父进程进行获取请求,子进程用来进行服务

下面服务器提供服务的API使用多进程来实现的

util.h – 用于保存相同的头文件

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <cassert>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#define BUFFER 1024
#define SOCKET_ERR 1
#define BIND_ERR 2
#define LISTEN_ERR 3
#define FORM_ERR 4
#define CONNET_ERR 5

TcpServer.cc – 服务器源代码

#include "until.h"

class TcpServer
{
public:
    TcpServer(uint16_t port, std::string ip = "")
        : ListenSockfd_(-1), port_(port), ip_(ip)
    {
    }
    ~TcpServer()
    {
        if (ListenSockfd_ > 2)
        {
            close(ListenSockfd_);
        }
    }

public:
    void Init()
    {
        // 1. 获取sockfd
        // SOCK_STREAM: 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,网络地址族使用TCP协议
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "Server make socket error: " << strerror(errno) << std::endl;
            exit(SOCKET_ERR);
        }
        // 2. 绑定主机基本信息到内核
        struct sockaddr_in Server;
        socklen_t len = sizeof(Server);
        memset(&Server, 0, len);
        Server.sin_family = AF_INET;                                                       // "地址类型/协议家族"
        Server.sin_port = htons(port_);                                                    // 端口号
        Server.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str()); // IP地址
        if (bind(ListenSockfd_, (const struct sockaddr *)&Server, len) < 0)
        {
            std::cout << "Server bind error: " << strerror(errno) << std::endl;
            exit(BIND_ERR);
        }
        // 3. 将套接字设置为监听到来的连接请求状态,监听是否有客户到来(TCP -> 面向连接)
        // 第二个参数:套接字的挂起"连接队列"的最大长度(一般由2到4), 用SOMAXCONN则为系统给出的最大值
        if (listen(ListenSockfd_, SOMAXCONN) < 0)
        {
            std::cout << "Server listen error: " << strerror(errno) << std::endl;
            exit(LISTEN_ERR);
        }
    }

    void Start()
    {
        int ServerSockfd;
        while (true)
        {
            //-------------------------------------------------------------------------------------
            // 4. 服务器启动后,获取新的客户端的连接
            // 该函数返回新的服务套接字,用于服务客户的请求(流式IO服务)
            struct sockaddr_in Client;
            socklen_t len = sizeof(Client);
            ServerSockfd = accept(ListenSockfd_, (struct sockaddr *)&Client, &len);
            if (ServerSockfd < 0)
            {
                std::cout << "Server get connection for error" << strerror(errno) << std::endl;
                continue;
            }
            // 获取Client网络信息 -- port、ip address -- 网络序列 <-> 主机序列
            std::string ClientPort = std::to_string(ntohs(Client.sin_port));
            std::string ClientIp = inet_ntoa(Client.sin_addr);
            //-------------------------------------------------------------------------------------

            //-------------------------------------------------------------------------------------
            // 实现多客户端连接 -- 如果不使用多进程,只能对单个进程提供服务
            int id = fork();
            signal(SIGCHLD, SIG_IGN); // 忽略子进程的退出状态, 子进程退出直接释放
            if (id == 0)
            {
                // 给客户端提供服务 -- 子进程做
                Proservice(ClientPort, ClientIp, ServerSockfd);
                exit(0);
            }
            // 父进程不能使用waitpid阻塞等待,上面已经设置捕捉SIGCHLD信号且忽略它了!
            //-------------------------------------------------------------------------------------
        }
    }

public:
    void Proservice(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd)
    {
        char inbuffer[BUFFER];
        std::string outbuffer;
        while (true)
        {
            // 5. 读取Client发送的数据 -- TCP->流式socket->文件流IO
            memset(inbuffer, 0, BUFFER);
            ssize_t Read = read(ServerSockfd, inbuffer, BUFFER - 1);
            if (Read > 0)
            {
                inbuffer[Read] = 0;
                // 客户端输入quit时退出本次服务 -- 不区分大小写
                if (strcasecmp(inbuffer, "quit") == 0)
                {
                    std::cout << "Client[" << ClientPort << ":" << ClientIp
                              << "]# exit server!!!" << std::endl;
                    break;
                }
                std::cout << "[" << ClientPort << ":" << ClientIp << "]echo# "
                          << inbuffer << std::endl;
            }
            else if (Read == 0) // 读端一直读,写端关闭,读端read返回0 -- Read == 0 -> 对方退出
            {
                std::cout << "[" << ClientPort << ":" << ClientIp << "]exit! ! !" << std::endl;
                break;
            }
            else
            {
                std::cout << "Server read data error" << strerror(errno) << std::endl;
                break;
            }

            // 提供服务 -- 小写英文 -> 大写英文 -- 大小写转换
            LowToUpp(inbuffer, outbuffer);

            // 6. 发送数据到Client -- TCP->流式socket->文件流IO
            ssize_t Write = write(ServerSockfd, (void *)outbuffer.c_str(), outbuffer.size());
            if (Write < 0)
            {
                std::cout << "Server write data error" << strerror(errno) << std::endl;
                continue;
            }
            outbuffer.clear(); // 清空数据
        }
        close(ServerSockfd); // 服务完成后,关闭"服务socket fd"
    }

	// 大小写转换服务
    void LowToUpp(const std::string &src, std::string &fast)
    {
        for (auto ch : src)
        {
            if (ch >= 'a' && ch <= 'z')
            {
                fast += ch - 32;
            }
            else
            {
                fast += ch;
            }
        }
    }

private:
    int ListenSockfd_; // 监听socket fd
    uint16_t port_;
    std::string ip_;
};

// (./tcpserver port [ip])
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        std::cout << "Command Line Format: ./tcpserver port [ip]" << std::endl;
        exit(FORM_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    TcpServer Server(port, ip);
    Server.Init();
    Server.Start();
    return 0;
}

TcpClient.cc – 客户端源代码

  • 这里还是要说一下:客户端最好不要自己bind网络信息,由OS自动分配即可,因为本机的其他客户端可能占用了你需要绑定的固定端口号

  • 然而,这只是一个建议,如果你要自己bind,那你可以试一下

#include "until.h"

class TcpClient
{
public:
    TcpClient(uint16_t port, std::string ip = "")
        : sockfd_(-1), port_(port), ip_(ip)

    {
    }
    ~TcpClient()
    {
        if (sockfd_ > 2)
        {
            close(sockfd_);
        }
    }

public:
    void Init()
    {
        // 1. 获取sockfd
        // SOCK_STREAM: 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,网络地址族使用TCP协议
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            std::cout << "Server make socket error: " << strerror(errno) << std::endl;
            exit(SOCKET_ERR);
        }
        
        // 填充指定Server的sockaddr_in信息
        struct sockaddr_in Server;
        socklen_t len = sizeof(Server);
        memset(&Server, 0, len);
        Server.sin_family = AF_INET;
        Server.sin_port = htons(port_);
        Server.sin_addr.s_addr = inet_addr(ip_.c_str());
        
        // 2. 向指定服务器发送连接请求 -- connet默认会自动绑定本主机的sockaddr_in信息
        // 最好不要自己bind,因为我们绑定的固定端口号可能被其他客户端给占用了,由OS自动分配即可
        if (connect(sockfd_, (const struct sockaddr *)&Server, len) < 0)
        {
            std::cout << "Client connet Server error: " << strerror(errno) << std::endl;
            exit(CONNET_ERR);
        }
    }

    void Start()
    {
        std::string outbuffer;
        char inbuffer[BUFFER];
        while (true)
        {
            // 3. 发送数据到Server -- TCP->流式socket->文件流IO
            std::cout << "echo# ";
            fflush(stdout);
            std::getline(std::cin, outbuffer);
            ssize_t Write = write(sockfd_, (void *)outbuffer.c_str(), outbuffer.size());
            if (Write < 0)
            {
                std::cout << "Client write data error" << strerror(errno) << std::endl;
                break;
            }

            // 输入quit时,退出客户端 -- 不区分大小写
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                std::cout << "Client exit!!!" << std::endl;
                break;
            }

            // 4. 读取Server发送的数据 -- TCP->流式socket->文件流IO
            memset(inbuffer, 0, BUFFER);
            ssize_t Read = read(sockfd_, inbuffer, BUFFER - 1);
            if (Read > 0)
            {
                inbuffer[Read] = 0;
                std::cout << "Server data echo# " << inbuffer << std::endl;
            }
            else if (Read == 0) // 读端一直读,写端关闭,读端read返回0 -- Read == 0 -> 对方退出
            {
                break;
            }
            else
            {
                std::cout << "Client read data error" << strerror(errno) << std::endl;
                break;
            }
        }
    }

private:
    int sockfd_;
    uint16_t port_;
    std::string ip_;
};

// (./tcpclient port ip)
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Command Line Format: ./tcpserver port ip" << std::endl;
        exit(FORM_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    ip = argv[2];

    TcpClient Client(port, ip);
    Client.Init();
    Client.Start();
    return 0;
}

🍢2.2、简单TCP通信程序 – 多线程版本

实现大小写转换服务器 – 使用TCP协议

  • 多线程版本:引入多线程,解决服务器提供服务时只能串行化的去执行代码

  • 使用多线程并发的去执行服务,不需要关心线程安全问题,线程执行函数(服务)是不需要访问临界资源的,它是一个可重入的函数!!!

  • 需要注意的是:在类中定义线程执行函数,函数内部会有隐藏this指针,需要使用static修饰,也可以在类外部定义线程执行函数

  • 思路:创建一个结构体保存客户端的网络信息和客户端类的this指针,Server类定义这个结构体保存Client数据和自己的this,然后将这个结构体地址传给线程,后面的代码详细查看


TcpServer.cc – 服务器源代码 – 其他不变

#include "until.h"

class TcpServer; // 前置声明,不开辟空间

class ClientData
{
public:
    ClientData(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd, TcpServer *This)
        : ClientPort_(ClientPort),
          ClientIp_(ClientIp),
          ServerSockfd_(ServerSockfd),
          This_(This)
    {
    }

public:
    std::string ClientPort_;
    std::string ClientIp_;
    int ServerSockfd_;
    TcpServer *This_;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, std::string ip = "")
        : ListenSockfd_(-1), port_(port), ip_(ip)
    {
    }
    ~TcpServer()
    {
        if (ListenSockfd_ > 2)
        {
            close(ListenSockfd_);
        }
    }

public:
    void Init()
    {
        // 1. 获取sockfd
        // SOCK_STREAM: 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,网络地址族使用TCP协议
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "Server make socket error: " << strerror(errno) << std::endl;
            exit(SOCKET_ERR);
        }
        // 2. 绑定主机基本信息到内核
        struct sockaddr_in Server;
        socklen_t len = sizeof(Server);
        memset(&Server, 0, len);
        Server.sin_family = AF_INET;                                                       // "地址类型/协议家族"
        Server.sin_port = htons(port_);                                                    // 端口号
        Server.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str()); // IP地址
        if (bind(ListenSockfd_, (const struct sockaddr *)&Server, len) < 0)
        {
            std::cout << "Server bind error: " << strerror(errno) << std::endl;
            exit(BIND_ERR);
        }
        // 3. 将套接字设置为监听到来的连接请求状态,监听是否有客户到来(TCP -> 面向连接)
        // 第二个参数:套接字的挂起"连接队列"的最大长度(一般由2到4), 用SOMAXCONN则为系统给出的最大值
        if (listen(ListenSockfd_, SOMAXCONN) < 0)
        {
            std::cout << "Server listen error: " << strerror(errno) << std::endl;
            exit(LISTEN_ERR);
        }
    }

    void Start()
    {
        int ServerSockfd;
        while (true)
        {
            //-------------------------------------------------------------------------------------
            // 4. 服务器启动后,获取新的客户端的连接
            // 该函数返回新的服务套接字,用于服务客户的请求(流式IO服务)
            struct sockaddr_in Client;
            socklen_t len = sizeof(Client);
            ServerSockfd = accept(ListenSockfd_, (struct sockaddr *)&Client, &len);
            if (ServerSockfd < 0)
            {
                std::cout << "Server get connection for error" << strerror(errno) << std::endl;
                continue;
            }
            // 获取Client网络信息 -- port、ip address -- 网络序列 <-> 主机序列
            std::string ClientPort = std::to_string(ntohs(Client.sin_port));
            std::string ClientIp = inet_ntoa(Client.sin_addr);
            //-------------------------------------------------------------------------------------

            //-------------------------------------------------------------------------------------
            // 实现多客户端连接 -- 多线程版本 -- 提供服务需要Client的port、ip、sock fd
            ClientData* pcd = new ClientData(ClientPort, ClientIp, ServerSockfd, this);
            pthread_t tid;
            // 因为在类中定义线程执行函数,需要加static,不能访问服务函数,只能在ClientData添加本类的this指针
            pthread_create(&tid, nullptr, ThreadHandler, pcd);
            //-------------------------------------------------------------------------------------
        }
    }

public:
    static void *ThreadHandler(void *args)
    {
        // 分离线程 -- 如果主线程进行等待会阻塞住
        pthread_detach(pthread_self());
        ClientData *pcd = static_cast<ClientData*>(args);
        // 提供客户服务
        pcd->This_->Proservice(pcd->ClientPort_, pcd->ClientPort_, pcd->ServerSockfd_);
        delete pcd;
        return nullptr;
    }

    void Proservice(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd)
    {
        char inbuffer[BUFFER];
        std::string outbuffer;
        while (true)
        {
            // 5. 读取Client发送的数据 -- TCP->流式socket->文件流IO
            memset(inbuffer, 0, BUFFER);
            ssize_t Read = read(ServerSockfd, inbuffer, BUFFER - 1);
            if (Read > 0)
            {
                inbuffer[Read] = 0;
                // 客户端输入quit时退出本次服务 -- 不区分大小写
                if (strcasecmp(inbuffer, "quit") == 0)
                {
                    std::cout << "Client[" << ClientPort << ":" << ClientIp
                              << "]# exit server!!!" << std::endl;
                    break;
                }
                std::cout << "[" << ClientPort << ":" << ClientIp << "]echo# "
                          << inbuffer << std::endl;
            }
            else if (Read == 0) // 读端一直读,写端关闭,读端read返回0 -- Read == 0 -> 对方退出
            {
                std::cout << "[" << ClientPort << ":" << ClientIp << "]exit! ! !" << std::endl;
                break;
            }
            else
            {
                std::cout << "Server read data error" << strerror(errno) << std::endl;
                break;
            }

            // 提供服务 -- 小写英文 -> 大写英文 -- 大小写转换
            LowToUpp(inbuffer, outbuffer);

            // 6. 发送数据到Client -- TCP->流式socket->文件流IO
            ssize_t Write = write(ServerSockfd, (void *)outbuffer.c_str(), outbuffer.size());
            if (Write < 0)
            {
                std::cout << "Server write data error" << strerror(errno) << std::endl;
                continue;
            }
            outbuffer.clear(); // 清空数据
        }
        close(ServerSockfd); // 服务完成后,关闭"服务socket fd"
    }

    void LowToUpp(const std::string &src, std::string &fast)
    {
        for (auto ch : src)
        {
            if (ch >= 'a' && ch <= 'z')
            {
                fast += ch - 32;
            }
            else
            {
                fast += ch;
            }
        }
    }

private:
    int ListenSockfd_; // 监听socket fd
    uint16_t port_;
    std::string ip_;
};

// (./tcpserver port [ip])
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        std::cout << "Command Line Format: ./tcpserver port [ip]" << std::endl;
        exit(FORM_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    TcpServer Server(port, ip);
    Server.Init();
    Server.Start();
    return 0;
}

🍧2.3、简单TCP通信程序 – 线程池版本

实现大小写转换服务器 – 使用TCP协议

优化

  • 上面2.2的多线程版本,每次客户的请求到来都要去申请线程,这样太慢了(效率低)

  • 我们可以设计一个线程池,好处:可以提前申请好多个线程,客户请求到来时可以立刻去执行

线程池

  • 线程池:启动一个或一个以上的线程,等待任务到来,来任务就派发线程去执行任务

  • 优点:提高效率,提前启动线程

Threadpool.hpp – 线程池源代码 – 非模板版本

#include "Task.hpp"
#include <iostream>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>

// 线程池 -- 启动一堆线程,等待任务到来,来任务就派发线程去执行任务
// 优先:提高效率,提前启动线程

// 设置方案:启动N个线程 -- 执行线程处理函数 -- 函数里面加锁判断队列是否有任务(条件)
//                     -- 没有任务则在条件变量下阻塞等待,有任务则到pop函数拿任务
//                     -- push函数用于加载任务,并且让条件变量就绪,唤醒线程,处理加载的任务

const int TNum = 5; // 默认设置线程数据为5个

class ThreadPool
{
private:
    // 构造函数与析构函数
    ThreadPool(int ThreadNum = TNum) : IsStart(false), ThreadNum_(ThreadNum)
    {
        // 线程数量不能小于或等于0
        if (ThreadNum_ < 0)
        {
            std::cout << "The number of threads cannot be less than 1 " << std::endl;
            exit(1);
        }
        pthread_mutex_init(&Pmutex_, nullptr);
        pthread_cond_init(&Pcond_, nullptr);
    }

    ThreadPool(const ThreadPool &) = delete;
    ThreadPool &operator=(const ThreadPool &) = delete;

public:
    ~ThreadPool()
    {
        pthread_mutex_destroy(&Pmutex_);
        pthread_cond_destroy(&Pcond_);
    }

public:
    // 单例模式
    static ThreadPool *GetThreadPool()
    {
        if (ptp == nullptr)
        {
            pthread_mutex_t Singleton_ = PTHREAD_MUTEX_INITIALIZER; // 单例锁 -- 局部初始化
            pthread_mutex_lock(&Singleton_);
            if (ptp == nullptr)
            {
                ptp = new ThreadPool();
                ptp->StartUpThread(); // 启动线程池
            }
            pthread_mutex_unlock(&Singleton_);
        }
        return ptp;
    }

public:
    // 线程处理任务函数 -- 设置全局处理函数 -- 类成员函数包含隐藏this指针
    static void *TaskProcess(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool *tpp = static_cast<ThreadPool *>(args);

        while (true)
        {
            tpp->lockQueue();            // 加锁
            while (!(tpp->IshaveTask())) // 判断是否有任务,有返回1,没有返回0
            {
                // 没有任务就在条件变量下阻塞等待,等待push加载任务后唤醒
                // 阻塞时,会释放互斥锁,条件变量就绪被唤醒后,会重新获取锁资源
                tpp->waitForTask();
            }
            // 这个任务就被拿到了线程的上下文中
            Task t(tpp->pop());
            tpp->unlockQueue(); // 解锁

            // ....下面是并发的处理任务 -- 约定好任务类必须有"函数对象(operator()())"
            t();
        }

        return nullptr;
    }

    // 启动多线程
    void StartUpThread()
    {
        if (IsStart != false)
        {
            std::cout << "Error: Start thread repeatedly! ! !" << std::endl;
            exit(2);
        }
        for (int i = 0; i < ThreadNum_; ++i)
        {
            pthread_t tid;
            // 这里传this指针,是因为处理函数是在类里面(类成员函数包含隐藏this指针)-- error: 类型不符合
            pthread_create(&tid, nullptr, TaskProcess, this);
        }
        // 启动完成,更新IsStart为true
        IsStart = true;
    }

    // 加载任务
    void push(Task &val)
    {
        // 加锁 -- 加载任务 -- 派发任务给线程 --解锁
        lockQueue();
        threadpool_.push(val);
        WakeUp();
        unlockQueue();
    }

private:
    // 封装部分mutex、cond的接口
    void lockQueue() { pthread_mutex_lock(&Pmutex_); }
    void unlockQueue() { pthread_mutex_unlock(&Pmutex_); }
    void WakeUp() { pthread_cond_signal(&Pcond_); }
    void waitForTask() { pthread_cond_wait(&Pcond_, &Pmutex_); }
    bool IshaveTask() { return !(threadpool_.empty()); }
    // 线程处理任务后,需要在队列中删除任务 -- 并且返回这个任务(处理函数要对任务进行处理)
    Task pop()
    {
        Task val(threadpool_.front());
        threadpool_.pop();
        return val;
    }

private:
    bool IsStart;                 // 防止重复启动线程
    int ThreadNum_;               // 线程数量
    std::queue<Task> threadpool_; // 线程池
    pthread_mutex_t Pmutex_;      // 互斥锁
    pthread_cond_t Pcond_;        // 条件变量
public:
    static ThreadPool *ptp;
};

ThreadPool *ThreadPool::ptp = nullptr;

Task.h – 任务类

为什么要使用function函数封装器呢>?

  • 因为如果服务器的服务API在本类中定义的话,传参给Task类时,服务API有隐藏this指针,我们只能通过bind来调整参数和function来接收

  • function是一个通用的函数封装器,可以封装任何可调用的API(普通函数、成员函数、函数对象等),bind可以调整可调用函数的参数,调整完后生成新的可调用函数,二个是配套使用的

  • 下面的例子的服务API是定义在类外部的,不使用function也行,因为是一个普通函数,没有隐藏的指针,只需要将函数名传过去即可,但我还是用了function接收参数

#include "until.h"
#include <functional>

class Task
{
private:
    // 类型重命名
    using ServerFunc = std::function<void(const std::string, const std::string, int)>;

public:
    Task(const std::string &ClientPort, const std::string &ClientIp, 
                int ServerSockfd, ServerFunc svf)
        : ClientPort_(ClientPort),
          ClientIp_(ClientIp),
          ServerSockfd_(ServerSockfd),
          svf_(svf)
    {
    }

    void operator()() // 函数对象
    {
        // 调用函数对象
        svf_(ClientPort_, ClientIp_, ServerSockfd_);
    }

private:
    std::string ClientPort_;
    std::string ClientIp_;
    int ServerSockfd_;
    ServerFunc svf_; // 函数封装器
};

TcpServer.cc – 服务器源代码 – 其他不变

#include "until.h"
#include "Threadpool.hpp"

// -------------------------------- 服务 --------------------------------------------------------------
void LowToUpp(const std::string &src, std::string &fast) // 英文大小写转换服务
{
    for (auto ch : src)
    {
        if (ch >= 'a' && ch <= 'z')
        {
            fast += ch - 32;
        }
        else
        {
            fast += ch;
        }
    }
}

void Proservice(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd)
{
    char inbuffer[BUFFER];
    std::string outbuffer;
    while (true)
    {
        // 前四步在TcpServer.cc的Start函数中
        // 5. 读取Client发送的数据 -- TCP->流式socket->文件流IO
        memset(inbuffer, 0, BUFFER);
        ssize_t Read = read(ServerSockfd, inbuffer, BUFFER - 1);
        if (Read > 0)
        {
            inbuffer[Read] = 0;
            // 客户端输入quit时退出本次服务 -- 不区分大小写
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                std::cout << "Client[" << ClientPort << ":" << ClientIp
                          << "]# exit server!!!" << std::endl;
                break;
            }
            std::cout << "[" << ClientPort << ":" << ClientIp << "]echo# "
                      << inbuffer << std::endl;
        }
        else if (Read == 0) // 读端一直读,写端关闭,读端read返回0 -- Read == 0 -> 对方退出
        {
            std::cout << "[" << ClientPort << ":" << ClientIp << "]exit! ! !" << std::endl;
            break;
        }
        else
        {
            std::cout << "Server read data error" << strerror(errno) << std::endl;
            break;
        }

        // 提供服务 -- 小写英文 -> 大写英文 -- 大小写转换
        LowToUpp(inbuffer, outbuffer);

        // 6. 发送数据到Client -- TCP->流式socket->文件流IO
        ssize_t Write = write(ServerSockfd, (void *)outbuffer.c_str(), outbuffer.size());
        if (Write < 0)
        {
            std::cout << "Server write data error" << strerror(errno) << std::endl;
            continue;
        }
        outbuffer.clear(); // 清空数据
    }
    close(ServerSockfd); // 服务完成后,关闭"服务socket fd"
}
// ---------------------------------------------------------------------------------------------------

// ------------------------------------- Client Data -------------------------------------------------
class TcpServer; // 前置声明,不开辟空间

class ClientData // 客户端网络数据
{
public:
    ClientData(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd, TcpServer *This)
        : ClientPort_(ClientPort),
          ClientIp_(ClientIp),
          ServerSockfd_(ServerSockfd),
          This_(This)
    {
    }

public:
    std::string ClientPort_;
    std::string ClientIp_;
    int ServerSockfd_;
    TcpServer *This_;
};
// ---------------------------------------------------------------------------------------------------

class TcpServer
{
public:
    TcpServer(uint16_t port, std::string ip = "")
        : ListenSockfd_(-1), port_(port), ip_(ip)
    {
    }
    ~TcpServer()
    {
        if (ListenSockfd_ > 2)
        {
            close(ListenSockfd_);
        }
        delete ptp;
    }

public:
    void Init()
    {
        // 1. 获取sockfd
        // SOCK_STREAM: 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,网络地址族使用TCP协议
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "Server make socket error: " << strerror(errno) << std::endl;
            exit(SOCKET_ERR);
        }
        // 2. 绑定主机基本信息到内核
        struct sockaddr_in Server;
        socklen_t len = sizeof(Server);
        memset(&Server, 0, len);
        Server.sin_family = AF_INET;                                                       // "地址类型/协议家族"
        Server.sin_port = htons(port_);                                                    // 端口号
        Server.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str()); // IP地址
        if (bind(ListenSockfd_, (const struct sockaddr *)&Server, len) < 0)
        {
            std::cout << "Server bind error: " << strerror(errno) << std::endl;
            exit(BIND_ERR);
        }
        // 3. 将套接字设置为监听到来的连接请求状态,监听是否有客户到来(TCP -> 面向连接)
        // 第二个参数:套接字的挂起"连接队列"的最大长度(一般由2到4), 用SOMAXCONN则为系统给出的最大值
        if (listen(ListenSockfd_, SOMAXCONN) < 0)
        {
            std::cout << "Server listen error: " << strerror(errno) << std::endl;
            exit(LISTEN_ERR);
        }
    }

    void Start()
    {
        int ServerSockfd;
        while (true)
        {
            //-------------------------------------------------------------------------------------
            // 4. 服务器启动后,获取新的客户端的连接
            // 该函数返回新的服务套接字,用于服务客户的请求(流式IO服务)
            struct sockaddr_in Client;
            socklen_t len = sizeof(Client);
            ServerSockfd = accept(ListenSockfd_, (struct sockaddr *)&Client, &len);
            if (ServerSockfd < 0)
            {
                std::cout << "Server get connection for error" << strerror(errno) << std::endl;
                continue;
            }
            // 获取Client网络信息 -- port、ip address -- 网络序列 <-> 主机序列
            std::string ClientPort = std::to_string(ntohs(Client.sin_port));
            std::string ClientIp = inet_ntoa(Client.sin_addr);
            //-------------------------------------------------------------------------------------

            //-------------------------------------------------------------------------------------
            // 线程池版本
            Task t(ClientPort, ClientIp, ServerSockfd, Proservice); // Proservice是普通函数,不需要bind调整
            ptp = ThreadPool::GetThreadPool();
            ptp->push(t);
            //-------------------------------------------------------------------------------------
        }
    }

private:
    int ListenSockfd_; // 监听socket fd
    uint16_t port_;
    std::string ip_;
    ThreadPool *ptp;
};

// (./tcpserver port [ip])
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        std::cout << "Command Line Format: ./tcpserver port [ip]" << std::endl;
        exit(FORM_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    TcpServer Server(port, ip);
    Server.Init();
    Server.Start();
    return 0;
}

makefile

all:tcpserver tcpclient

tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread

tcpserver:TcpServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -rf tcpserver tcpclient

🍀3、部署服务器

🍡3.1、会话和进程组

进程组:

  • 进程组:顾名思义,从”组“中可以看出是一个进程的组合,进程组包含一个或多个进程

  • 进程组组长:每个进程组都有一个“组长”,这个组长是进程组中第一个启动的进程,其他进程都是进程组的组员

  • 进程组的生命周期与组长无关,只有当组长和组员全部退出后,这个进程组才会被释放

  • 进程组组长的PGID = 本身的PID,组员PGID = 第一个启动的进程的PID

在这里插入图片描述

会话:

  • 会话:会话是由一个前台进程组(必须)和多个后台进程组组成的

  • 我们登录shell时,OS会我们构建一个会话,会话需要加载bash解析器,bash就是会话话首的进程组,后续我们新启进程时,就是在bash上启动的子进程,bash的PID就是会话的ID(SID)

  • 会话的生命周期是随用户的,用户退出它就退出

  • 在bash中只能启动一个前台进程,但是可以启动多个后台进程,因为前台进程会占用终端,它会将我们阻塞住,直到完成指令的运行

在这里插入图片描述


🍢3.2、守护/精灵进程

【跳转 – 守护/精灵进程 – 百度百科】

  • 守护进程:它是一个后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束

  • 原理:守护进程将自己所在的会话中剥离出来,生成一个新的会话,自己就是会话的话首。它是一个在后台运行并且不受任何终端控制的进程

创建步骤

  • 忽略信号:根据特定的业务和代码逻辑,忽略特定的信号,防止程序异常崩溃

  • 设置新的进程当前工作路径(CWD):使用chdir函数改变进程工作路径

  • 创建会话,会话话首是当前的进程,我们使用setsid函数即可

  • 关闭输入、输出、错误流文件描述符

  • 打开/dev/null(垃圾桶)文件,将输入、输出、错误流文件描述符重定向到/dev/null

注意:setsid不能是使用进程组组长调用,否则会出错!!!


🍧3.3、实现守护进程

Daemon.hpp

#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

// 参数:进程当前工作路径(cwd),默认是原来的路径
void Daemon(const std::string& newCwd = "")
{
    // 1. 忽略SICPIPE信号 -- Server写 -> Client读端关闭 -> Server的write会受到SIGPIPE信号导致终止
    signal(SIGPIPE, SIG_IGN);
    // 2. 设置新的process cwd
    if (newCwd != "")
    {
        chdir(newCwd.c_str());
    }
    // 3. 创建新的会话,话首是服务器进程 -- setsid:设置会话的进程不能是进程组组长
    if (fork() > 0) exit(0);
    setsid();
    // 4. 将IO和错误流重定向至/dev/null文件中(数据黑洞)
    int fd  =open("/dev/null", O_RDWR);
    if (fd > 0)
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 重定向成功后,关闭fd
        if (fd > STDERR_FILENO)
        {
            close(fd);
        }
    }
}

🍨3.4、部署服务器

部署服务器只要将守护进程代码在主函数中执行,就完成部署了!!!

TcpServer – 其他的不变 – 线程池版本的通信程序

#include "until.h"
#include "Threadpool.hpp"
#include "Daemon.hpp"

// -------------------------------- 服务 --------------------------------------------------------------
void LowToUpp(const std::string &src, std::string &fast) // 英文大小写转换服务
{
    for (auto ch : src)
    {
        if (ch >= 'a' && ch <= 'z')
        {
            fast += ch - 32;
        }
        else
        {
            fast += ch;
        }
    }
}

void Proservice(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd)
{
    char inbuffer[BUFFER];
    std::string outbuffer;
    while (true)
    {
        // 前四步在TcpServer.cc的Start函数中
        // 5. 读取Client发送的数据 -- TCP->流式socket->文件流IO
        memset(inbuffer, 0, BUFFER);
        ssize_t Read = read(ServerSockfd, inbuffer, BUFFER - 1);
        if (Read > 0)
        {
            inbuffer[Read] = 0;
            // 客户端输入quit时退出本次服务 -- 不区分大小写
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                std::cout << "Client[" << ClientPort << ":" << ClientIp
                          << "]# exit server!!!" << std::endl;
                break;
            }
            std::cout << "[" << ClientPort << ":" << ClientIp << "]echo# "
                      << inbuffer << std::endl;
        }
        else if (Read == 0) // 读端一直读,写端关闭,读端read返回0 -- Read == 0 -> 对方退出
        {
            std::cout << "[" << ClientPort << ":" << ClientIp << "]exit! ! !" << std::endl;
            break;
        }
        else
        {
            std::cout << "Server read data error" << strerror(errno) << std::endl;
            break;
        }

        // 提供服务 -- 小写英文 -> 大写英文 -- 大小写转换
        LowToUpp(inbuffer, outbuffer);

        // 6. 发送数据到Client -- TCP->流式socket->文件流IO
        ssize_t Write = write(ServerSockfd, (void *)outbuffer.c_str(), outbuffer.size());
        if (Write < 0)
        {
            std::cout << "Server write data error" << strerror(errno) << std::endl;
            continue;
        }
        outbuffer.clear(); // 清空数据
    }
    close(ServerSockfd); // 服务完成后,关闭"服务socket fd"
}
// ---------------------------------------------------------------------------------------------------

// ------------------------------------- Client Data -------------------------------------------------
class TcpServer; // 前置声明,不开辟空间

class ClientData // 客户端网络数据
{
public:
    ClientData(const std::string &ClientPort, const std::string &ClientIp, int ServerSockfd, TcpServer *This)
        : ClientPort_(ClientPort),
          ClientIp_(ClientIp),
          ServerSockfd_(ServerSockfd),
          This_(This)
    {
    }

public:
    std::string ClientPort_;
    std::string ClientIp_;
    int ServerSockfd_;
    TcpServer *This_;
};
// ---------------------------------------------------------------------------------------------------

class TcpServer
{
public:
    TcpServer(uint16_t port, std::string ip = "")
        : ListenSockfd_(-1), port_(port), ip_(ip)
    {
    }
    ~TcpServer()
    {
        if (ListenSockfd_ > 2)
        {
            close(ListenSockfd_);
        }
        delete ptp;
    }

public:
    void Init()
    {
        // 1. 获取sockfd
        // SOCK_STREAM: 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,网络地址族使用TCP协议
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "Server make socket error: " << strerror(errno) << std::endl;
            exit(SOCKET_ERR);
        }
        // 2. 绑定主机基本信息到内核
        struct sockaddr_in Server;
        socklen_t len = sizeof(Server);
        memset(&Server, 0, len);
        Server.sin_family = AF_INET;                                                       // "地址类型/协议家族"
        Server.sin_port = htons(port_);                                                    // 端口号
        Server.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str()); // IP地址
        if (bind(ListenSockfd_, (const struct sockaddr *)&Server, len) < 0)
        {
            std::cout << "Server bind error: " << strerror(errno) << std::endl;
            exit(BIND_ERR);
        }
        // 3. 将套接字设置为监听到来的连接请求状态,监听是否有客户到来(TCP -> 面向连接)
        // 第二个参数:套接字的挂起"连接队列"的最大长度(一般由2到4), 用SOMAXCONN则为系统给出的最大值
        if (listen(ListenSockfd_, SOMAXCONN) < 0)
        {
            std::cout << "Server listen error: " << strerror(errno) << std::endl;
            exit(LISTEN_ERR);
        }
    }

    void Start()
    {
        int ServerSockfd;
        while (true)
        {
            //-------------------------------------------------------------------------------------
            // 4. 服务器启动后,获取新的客户端的连接
            // 该函数返回新的服务套接字,用于服务客户的请求(流式IO服务)
            struct sockaddr_in Client;
            socklen_t len = sizeof(Client);
            ServerSockfd = accept(ListenSockfd_, (struct sockaddr *)&Client, &len);
            if (ServerSockfd < 0)
            {
                std::cout << "Server get connection for error" << strerror(errno) << std::endl;
                continue;
            }
            // 获取Client网络信息 -- port、ip address -- 网络序列 <-> 主机序列
            std::string ClientPort = std::to_string(ntohs(Client.sin_port));
            std::string ClientIp = inet_ntoa(Client.sin_addr);
            //-------------------------------------------------------------------------------------

            //-------------------------------------------------------------------------------------
            // 线程池版本
            Task t(ClientPort, ClientIp, ServerSockfd, Proservice); // Proservice是普通函数,不需要bind调整
            ptp = ThreadPool::GetThreadPool();
            ptp->push(t);
            //-------------------------------------------------------------------------------------
        }
    }

private:
    int ListenSockfd_; // 监听socket fd
    uint16_t port_;
    std::string ip_;
    ThreadPool *ptp;
};

// (./tcpserver port [ip])
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        std::cout << "Command Line Format: ./tcpserver port [ip]" << std::endl;
        exit(FORM_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    Daemon();

    TcpServer Server(port, ip);
    Server.Init();
    Server.Start();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/427615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【redis】验证redis是否正常运行以及报错解决方案

Redis 出现 “(error) NOAUTH Authentication required.” 错误&#xff0c;意味着客户端尝试向 Redis 服务器发送命令&#xff0c;但未提供身份验证或提供的身份验证信息不正确。这通常是由于 Redis 实例开启了密码认证机制导致的。 为了解决该问题&#xff0c;可以尝试以下…

代码随想录算法训练营第六天|242 有效的字母异位词 349 两个数组的交集 202 快乐数 1 两数之和

文章目录哈希表242 有效的字母异位词思路代码总结349 两个数组的交集思路代码总结202 快乐数思路代码总结1 两数之和思路代码总结哈希表 哈希碰撞&#xff1a;拉链法&#xff08;链表&#xff09;线性探测法&#xff08;顺序向后&#xff09; std::unordered_map, std::unorde…

LinuxGUI自动化测试框架搭建(四)-Hyper-V上安装Ubuntu操作系统

(四)-Hyper-V上安装Ubuntu操作系统 1 Ubuntu下载1.1 下载地址1.2 下载说明2 Hyper-V上安装Ubuntu2.1 创建虚拟机2.2 安装Ubuntu系统3 设置虚拟你网络4 关闭虚拟机检查点并移除DVD驱动器5 启动虚拟机6 配置Ubuntu网络7 网络测试1 Ubuntu下载 1.1 下载地址 官网地址:https://…

(十五)排序算法-归并排序

1 基本介绍 1.1 概述 归并排序&#xff08;Merge Sort&#xff09;是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组&#xff0c;再合并数组。 将数组分解最小之后&#xff0c;然后合并两个有序数组&#xff0c;基本思路是比较两个数组的最前面的数&…

华为浮动路由和BFD配置

拓扑图 电脑配置 pc1:192.168.10.1/24 pc2:192.168.20.1/24 AR1路由器配置 配置4个接口的地址 ge0/0/0:192.168.10.254/24 ge0/0/1:192.168.20.254/24 ge0/0/2:12.1.1.1/30 ge4/0/0:13.1.1.1/30 <Huawei>system-view Enter system view, return user view with CtrlZ…

机器学习笔记之正则化(五)Dropout

机器学习笔记之正则化——Dropout引言引子&#xff1a;题目描述(正则化相关)正确答案&#xff1a;ABCD\mathcal A\mathcal B\mathcal C\mathcal DABCD题目解析回顾&#xff1a;集成学习方法&#xff1a;Bagging\text{Bagging}BaggingDropout\text{Dropout}Dropout方法深度神经网…

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理

教程链接&#xff1a;https://www.bilibili.com/video/BV1LQ4y127n4 黑马的资料下载链接&#xff1a;https://pan.baidu.com/s/1zRmwSvSvoDkWh0-MynwERA&pwd1234 目录认识微服务单体架构分布式架构微服务微服务结构微服务技术对比SpringCloud总结 &#x1f380;服务拆分及远…

刷题day54:柱形图中最大矩形

题意描述&#xff1a; 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 暴力方法&#xff1a; class Solution { public:int largestRectangleArea(ve…

软考第四章 局域网与城域网

局域网与城域网 传统局域网&#xff08;LAN&#xff09;是分组广播式网络&#xff0c;这是与分组交换式的广域网的主要区别。广播技术中&#xff0c;所有工作站都连接到共享的传输介质上&#xff0c;共享信道的分配技术是局域网的核心技术&#xff0c;而这一技术又与网络的拓扑…

故障分析 | 从一则错误日志到 MySQL 认证机制与 bug 的深入分析

作者&#xff1a;李锡超 一个爱笑的江苏苏宁银行 数据库工程师&#xff0c;主要负责数据库日常运维、自动化建设、DMP平台运维。擅长MySQL、Python、Oracle&#xff0c;爱好骑行、研究技术。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得…

Redis的MoreKey和BigKey问题

文章目录1、MoreKey案例2、BigKey案例1、MoreKey案例 大批量往Redis里面插入200w测试数据key 在Linux Bash下面执行&#xff0c;插入200w数据 for((i1;i<200*10000;i)); do echo "set k$i v$i">>/root/redisTest.txt;done;使用命令 tail -10 redisTest.t…

Android四大组件之 Activity的启动过程源码解析

前言 Activity是Android中一个很重要的概念&#xff0c;堪称四大组件之首&#xff0c;关于Activity有很多内容&#xff0c;比如生命周期和启动Flags&#xff0c;这二者想要说清楚&#xff0c;恐怕又要写两篇长文&#xff0c;更何况分析它们的源码呢。不过本文的侧重点不是它们…

小黑向携程进攻啦1:跟紧沛奇老师的携程步伐

为什么要学 异步非阻塞、asynciotornado、fastapi、django 3.x asgi、aiohttp都在异步->提升功能 如何讲解&#xff1f; 协程asyncio模块进行异步编程实战案例 1.协程 协程不是计算机提供&#xff0c;程序员人为创造出来的。协程&#xff0c;又称微线程&#xff0c;是一…

共聚焦显微镜——光伏产业制造智能化测量新技术

我国智能光伏应用持续升级&#xff0c;产业各环节产量又创新高。根据光伏行业规范公告企业信息和行业协会预测的数据来看&#xff0c;全国多晶硅、组件产量同比增长均超过60%&#xff0c;晶硅电池产品出口同比增长超过156%。 以共聚焦显微测量技术为原理、应用于材料生产领域的…

STM32的SRAM

文章目录SRAM的概念XM8A51216介绍特点连接线原理框图硬件连接图SRAM的配置使能 FSMC 时钟&#xff0c;并配置 FSMC 相关的 IO 及其时钟使能初始化FSMC&#xff0c;设置FSMC BANK1区域3使能 BANK1 区域 3FSMC读写代码SRAM的概念 SRAM的介绍 STM32F407ZGT6自带了 192K字节的 SRA…

[助人为乐]层次分析法

朋友论文需要用到层次分析法。于是回顾了一下。 相关资料推荐 层次分析法(AHP) 层次分析法&#xff08;AHP&#xff09;详细版本 用人话讲明白AHP层次分析法&#xff08;非常详细原理简单工具实现&#xff09; 层次分析法的流程图 构建层次结构模型(目标-准则-方案层) 深入…

认证服务---OAuth2.0基本介绍,微博登录整合到实际项目中【下篇】

前言 上一篇简单介绍了它的基本使用&#xff0c;这一篇就粗略说明一下如何在项目中实际应用 1、核心代码 1.1 认证微服务 当你进行了授权之后&#xff0c;跳转到一个新的地址。这个地址应该是你访问接口的地址。在这个接口中完成相应的access_token获取&#xff0c;以及调用…

docker-compose容器编排使用详解+示例

文章目录一、docker-compose概述1、产生的背景2、核心概念3、使用的三个步骤4、常用命令二、下载安装1、官方文档2、下载3、卸载三、使用compose1、前置知识&#xff0c;将一个springboot项目打包为镜像2、编写docker-compose.yml文件3、启动docker-compose4、停止一、docker-c…

[Linux]进程控制精讲,简单实现一个shell

目录 前言 进程创建 fork函数初识 写时拷贝 fork常见用法 fork调用失败的原因 进程终止 进程退出场景 进程退出码 查看进程退出码 退出码的含义 进程常见退出方法 exit VS _exit exit函数 _exit函数 二者的区别 return退出 进程等待 进程等待必要性 进程等待…

【Java文件操作】手把手教你拿捏IO 流

哈喽&#xff0c;大家好~我是保护小周ღ&#xff0c;本期为大家带来的是 Java 文件操作&#xff0c;理解文件的概念以及&#xff0c;常用的操作文件的类和方法&#xff0c;FileInputStream 类 和 FileOutputStream , PrintWriter and Scnner, Reader and Wirter 确定不来看看…