Linux网络编程-socket套接字

一. 端口号

  1. 端口号(port)是传输层协议的内容,是一个2字节16位的整数
  2. 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理
  3. IP地址 + 端口号能够标识网络上的某一台主机的某一个进程
  4. 一个进程可以绑定多个端口号,一个端口号只能绑定一个进程

二. TCP和UDP协议

2.1 TCP协议

(Transmission Control Protocol )传输控制协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

2.2 UDP协议

(User Datagram Protocol 用户数据报协议)

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

三. 网络字节序

3.1 概念

  1. 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
  2. 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
  3. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

3.2 网络字节序和主机字节序转换接口

// h表示host,n表示network,l表示long,s表示short
// 例如htonl表示将32位的长整数从主机字节序转换为网络字节序
// 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
// 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回
#include <arpa/inet.h>
unit32_t htonl(uint32_t hostlong);
unit16_t htons(uint16_t hostshort);
unit32_t ntonl(uint32_t netlong);
unit16_t ntons(uint16_t netshort);

四. socket编程接口

4.1 sockaddr结构

  1. IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址
  2. IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容
// sockaddr
struct sockaddr
{
	unsigned short int sa_family;
	unsigned char sa_data[14];
};

// sockaddr_in
struct sockaddr_in {
	__kernel_sa_family_t sin_family;	/* Address family		*/
	__be16 sin_port;	/* Port number			*/
	struct in_addr sin_addr;	/* Internet address		*/

    /* Pad to size of `struct sockaddr'. */
    unsigned char	__pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
};

// in_addr
struct in_addr {
	__be32	s_addr;
};

在这里插入图片描述

4.2 socket 常见API

4.2.1 简述

socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in。这样的好处是程序的通用性,可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数

4.2.2 socket

// 适用于TCP/UDP,客户端/服务器
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
int socket(int domain, int types, int protocol);

// 3. 功能:创建套接字

// 4. domain: 用于设置网络通信的域,根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义,AF_INET表示IPv4 Internet协议,一般我们传这个参数

// 5. type: 用于设置套接字通信的类型,主要有SOCK_STREAM(流式套接字TCP)、SOCK_DGRAM(数据包套接字UDP)等

// 6. protocol:用于制定某个协议的特定类型,即type类型中的某个类型,一般我们设置为0就好了

// 7. 返回值:成功返回新的套接字文件描述符,失败返回-1

4.2.3 bind

// 适用于TCP/UDP服务器
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);  // [baɪnd]

// 3. 功能:让套接字绑定ip地址和port端口

// 4. sockfd:需要绑定的套接字的文件描述符,也就是上面socket接口的返回值

// 5. addr:我们传的结构体类型为sockaddr_in,里面包含了三个参数sin_family,sin_port, sin_addr,分别代表网络通信的域,我们需要绑定的端口和ip地址,传参时需要用(struct sockaddr*)强制转换一下类型

// 6. addrlen:结构体类型的大小

// 7. 返回值:成功返回0,失败返回-1

4.2.4 recvfrom

// 适用于UDP,服务器/客户端
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);

// 3. 功能:接收其他套接字发来的信息

// 4. sockfd:我们这边的套接字的文件描述符

// 5. buf:接收信息的容器

// 6. len:想接收多少字节的信息

// 7. flags:调用操作方式,一般设置为0即可

// 8. src_addr:这是一个输出型参数,记录给你信息的套接字的信息(包括网络通信的域,和其绑定的端口和ip地址)

// 9. addrlen:也是一个输出型参数,src_addr的大小

// 10. 返回值:成功返回接收到多少个字节的数据,失败返回-1

4.2.5 sendto

// 适用于UDP,服务器/客户端
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
 ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);

// 3. 功能:给其他套接字发送信息

// 4. sockfd:我们这边的套接字的文件描述符

// 5. buf:发送信息的容器

// 6. len:想发送多少字节的信息

// 7. flags:调用操作方式,一般设置为0即可

// 8. dest_addr:目的端套接字的信息(包括网络通信的域,和其绑定的端口和ip地址)

// 9. addrlen:dest_addr的大小

// 10. 返回值:成功返回发送了多少个字节的数据,失败返回-1

4.2.6 inet_addr

// 1. 头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 2. 接口声明
in_addr_t inet_addr(const char* cp);

// 3. 功能:点分十进制的IP地址(字符串),转化成网络字节序的IP地址(4字节无符号长整数型数),同时也考虑大小端

// 4. cp:需要转换的IP地址的字符串

// 5. 返回值:如果正确执行将返回一个无符号长整数型数。如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE

4.2.7 inet_ntoa

// 1. 头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 2. 接口声明
char* inet_ntoa(struct in_addr in);

// 3. 功能:跟inet_addr相反,它是将一个网络字节序的IP地址,转化为点分十进制的IP地址。

// 4. in:包含网络字节序的结构体变量,比如说上面struct sockaddr_in里面的sin_addr变量

// 5. 返回值:如果正确执行将返回一个字符串,否则返回NULL

4.2.8 listen

// 适用于TCP,服务器
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
int listen(int sockfd, int backlog);

// 3. 功能:设置sockfd套接字为listen状态,本质是允许用户连接

// 4. backlog: Linux内核协议栈为一个tcp连接管理使用两个队列:
// (1) 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)
// (2) 全连接队列(accpetd队列)(用来保存处于ESTABLISHED状态,但是应用层没有调用accept取走的请求)
// 全连接队列的长度会受到 listen 第二个参数的影响,全连接队列满了的时候, 就无法继续让当前连接的状态进入ESTABLISHED
// 这个队列的长度是backlog + 1

// 5. 返回值:成功返回0,失败返回-1

4.2.9 accept

// 适用于TCP,服务器
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 3. 功能:让sockfd接受一个套接字的连接

// 4. addr和addrlen跟上面的一样,是输出型参数

// 5. 返回值:成功返回一个文件描述符,失败返回-1,这个文件描述符就是他们通信的容器,我们可以使用read和write这些接口去操作

4.2.10 connect

// 适用于TCP,客户端
// 1. 头文件
#include <sys/types.h>
#include <sys/socket.h>

// 2. 接口声明
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 3. 功能:让sockfd套接字与服务器建立连接

// 4. addr和addrlen是服务器的相关信息

// 5. 返回值:成功返回0.失败返回-1

五. 实现UDP通信

server.cpp文件

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
    // 1. 创建套接字
    int sockid = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockid < 0)
    {
        std::cout << "socket" << errno << std::endl;
        return -1;
    }

    struct sockaddr_in server;  // 定义结构体变量,这个变量里面包含我们想让套接字绑定的IP和端口,所以我们还得给这个结构体变量赋值
    server.sin_family = AF_INET;
    server.sin_port = htons((uint16_t)8080);  // 端口,需要转换成网络的格式
    server.sin_addr.s_addr = INADDR_ANY;  // IP,写上这个的意思是只要发给这台主机的这个端口,不用理会IP地址,因为一台主机可以有很多IP,虚拟IP等等

    // 2. 绑定端口和IP
    if (bind(sockid, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        std::cout << "bind" << errno << std::endl;
        return -1;
    }

    // 3. 提供服务
    char buffer[1024];
    while (true)
    {
        struct sockaddr_in client;  // 创建客户端的结构体变量,调用recvfrom时可以获取客户端的IP和端口信息
        socklen_t len = sizeof(client);
        recvfrom(sockid, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&client, &len);  
        buffer[1024] = '\0';
        std::cout << "client say:" << buffer << std::endl;
        std::string str = "OK!";
        sendto(sockid, str.c_str(), str.size(), 0, (struct sockaddr*)&client, len);
    }
    return 0;
}

client.cpp文件

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cout << argv[0] << ": server_ip and server_port" << std::endl;
        return -1;
    }
    
    // 1. 创建套接字
    int sockid = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockid < 0)
    {
        std::cout << "socket" << errno << std::endl;
    }

    struct sockaddr_in server;  // 定义服务器的结构体变量,并且加入服务器的IP和端口信息
    server.sin_family = AF_INET;
    server.sin_port = htons(std::stoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);

    // 注意:client不需要自己绑定(bind)IP和port,因为如果指明端口号有可能会被占用。client发送消息的时候,OS会自动给你bang定。
    // 2. 适用服务
    while (true)
    {
        std::string msg;
        std::cout << "请输入:";
        std::cin >> msg;
        sendto(sockid, msg.c_str(), msg.size(), 0, (struct sockaddr*)&server, sizeof(server));

        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        recvfrom(sockid, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &len);
        std::cout << "server say:" << buffer << std::endl;
    }
    return 0;
}

六. 实现TCP通信

6.1 单进程版

server.cpp文件代码

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

void Usage(const char* proc)
{
    std::cout << "Usage" << proc << "port" << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    
    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket" << errno << std::endl;
        return 2;
    }

    // 2. 绑定端口和IP
    uint16_t sin_port = std::stoi(argv[1]);
    sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(sin_port);
    server.sin_addr.s_addr = INADDR_ANY;
    if (bind(sockfd, (struct sockaddr*)&server, sizeof(server)) != 0)
    {
        std::cerr << "bind" << errno << std::endl;
        return 3;
    }
    
    // 3. 设置监听listen
    if (listen(sockfd, 5) != 0)
    {
        std::cerr << "listen" << errno << std::endl;
        return 4;
    }

    // 4. 接受连接,给客户端提供服务
    sockaddr_in client;
    socklen_t addrlen;
    while (true)
    {
        int new_fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
        if (new_fd > 0)
        {
           while (true)
           {
               char buffer[1024];
               memset(buffer, '\0', sizeof(buffer));
               ssize_t read_count = read(new_fd, buffer, sizeof(buffer) - 1);  // TCP连接成功后,可以像正常文件那样操作,调用接口也是一样的
               if (read_count > 0)
               {
                    buffer[read_count] = '\0';
                    std::cout << "client" << new_fd << ":" << buffer << std::endl;
                    std::string msg;
                    msg += std::to_string(new_fd);
                    msg += " okok!!!";
                    write(new_fd, msg.c_str(), msg.size());
               }
               else if (read_count == 0)
               {
                   std::cout << "client quit...." << std::endl;
                   break;
               }
               else 
               {
                   std::cerr << "read" << errno << std::endl;
                   break;
               }
           }
        }
    }
    return 0;
}

client.cpp文件代码(注意:其他版本的client.cpp文件代码是一样的,后面就不展示了)

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

void Usage(const char* proc)
{
    std::cout << "Usage:" << proc << "serverIP serverPort" << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket" << errno << std::endl;
        return 2;
    }

    // 2. 请求连接,适用服务
    sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons((uint16_t)std::stoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    if (connect(sockfd, (struct sockaddr*)&server, sizeof(server)) == -1)
    {
        std::cerr << "connect" << errno << std::endl;
        return 3;
    }

    // 3. 使用服务
    char buffer[1024];
    memset(buffer, '\0', sizeof(buffer));
    std::string msg;
    while (true)
    {
        std::cout << "请输入:";
        std::cin >> msg;
        int w_len = write(sockfd, msg.c_str(), msg.size());
        if (w_len == -1)
        {
            std::cerr << "write" << errno << std::endl;
            return 4;
        }
        int r_len = read(sockfd, buffer, sizeof(buffer) - 1);
        if (r_len > 0)
        {
            buffer[r_len] = '\n';
            std::cout << buffer << std::endl;
        }
        else if (r_len == -1)
        {
            std::cerr << "read" << errno << std::endl;
            return 5;
        }
    }
    return 0;
}

6.2 多进程版

server.cpp文件代码

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void Usage(const char* proc)
{
    std::cout << "Usage" << proc << "port" << std::endl;
}

// 提供IO服务
void ServerIO(int sockfd)
{
    if (sockfd > 0) { while (true)
       {
           char buffer[1024];
           memset(buffer, '\0', sizeof(buffer));
           ssize_t read_count = read(sockfd, buffer, sizeof(buffer) - 1);
           if (read_count > 0)
           {
                buffer[read_count] = '\0';
                std::cout << "client" << sockfd << ":" << buffer << std::endl;
                std::string msg;
                msg += std::to_string(sockfd);
                msg += " okok!!!";
                write(sockfd, msg.c_str(), msg.size());
           }
           else if (read_count == 0)
           {
               std::cout << "client quit...." << std::endl;
               break;
           }
           else 
           {
               std::cerr << "read" << errno << std::endl;
               break;
           }
       }
    }
}
int main(int argc, char* argv[])
{
    //signal(SIGCHLD, SIG_IGN);  // 忽略子进程的信号,让子进程退出的时候自动清理,避免产生僵尸进程
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    
    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket" << errno << std::endl;
        return 2;
    }

    // 2. 绑定端口和IP
    uint16_t sin_port = std::stoi(argv[1]);
    sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(sin_port);
    server.sin_addr.s_addr = INADDR_ANY;
    if (bind(sockfd, (struct sockaddr*)&server, sizeof(server)) != 0)
    {
        std::cerr << "bind" << errno << std::endl;
        return 3;
    }
    
    // 3. 设置监听listen
    if (listen(sockfd, 5) != 0)
    {
        std::cerr << "listen" << errno << std::endl;
        return 4;
    }

    // 4. 接受连接,给客户端提供服务
    sockaddr_in client;
    socklen_t addrlen;
    while (true)
    {
        int new_fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
        
        if (new_fd > 0)
        {
            uint16_t client_port = ntohs(client.sin_port);
            const char* client_IP = inet_ntoa(client.sin_addr);
            std::cout << "Get a link --> IP:" << client_IP << "    port:" << client_port << std::endl;
            
            // 创建子进程,让子进程为连接成功的客户端提供服务,父进程继续等下一个连接的客户端
            pid_t pid = fork();
            if (pid == 0)
            {
                // child
                close(sockfd);
                if (fork() > 0) exit(1);  // 子进程在创建子进程,即孙子进程,然后子进程自己退出了,让孙子进程执行后面的代码。此时孙子进程变成孤儿进程,由操作系统回收
                ServerIO(new_fd);
                close(new_fd);
                break;
            }
            else 
            {
                waitpid(pid, NULL, 0);  // 等待刚刚自己退出的子进程
                // 父进程
                close(new_fd);
            }
        }
    }
    return 0;
}

client.cpp文件代码(跟单进程版一样的)

6.3 多线程版

server.cpp文件代码

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>

void Usage(const char* proc)
{
    std::cout << "Usage" << proc << "port" << std::endl;
}

void ServerIO(int sockfd)
{
    if (sockfd > 0) { while (true)
       {
           char buffer[1024];
           memset(buffer, '\0', sizeof(buffer));
           ssize_t read_count = read(sockfd, buffer, sizeof(buffer) - 1);
           if (read_count > 0)
           {
                buffer[read_count] = '\0';
                std::cout << "client" << sockfd << ":" << buffer << std::endl;
                std::string msg;
                msg += std::to_string(sockfd);
                msg += " okok!!!";
                write(sockfd, msg.c_str(), msg.size());
           }
           else if (read_count == 0)
           {
               std::cout << "client quit...." << std::endl;
               break;
           }
           else 
           {
               std::cerr << "read" << errno << std::endl;
               break;
           }
       }
    }
}

void* Run(void* arg)
{
    pthread_detach(pthread_self());  // 线程分离,告诉系统当线程退出时会自动释放资源,不用join等待
    int fd = *(int*)arg;
    delete (int*)arg;
    ServerIO(fd);
    close(fd);
}
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    
    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket" << errno << std::endl;
        return 2;
    }

    // 2. 绑定端口和IP
    uint16_t sin_port = std::stoi(argv[1]);
    sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(sin_port);
    server.sin_addr.s_addr = INADDR_ANY;
    if (bind(sockfd, (struct sockaddr*)&server, sizeof(server)) != 0)
    {
        std::cerr << "bind" << errno << std::endl;
        return 3;
    }
    
    // 3. 设置监听listen
    if (listen(sockfd, 5) != 0)
    {
        std::cerr << "listen" << errno << std::endl;
        return 4;
    }

    // 4. 接受连接,给客户端提供服务
    sockaddr_in client;
    socklen_t addrlen;
    while (true)
    {
        int new_fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
        
        if (new_fd > 0)
        {
            uint16_t client_port = ntohs(client.sin_port);
            const char* client_IP = inet_ntoa(client.sin_addr);
            std::cout << "Get a link --> IP:" << client_IP << "    port:" << client_port << std::endl;
            pthread_t tid;
            int* fd = new int(new_fd);
            pthread_create(&tid, NULL, Run, (void*)fd);
        }
    }
    return 0;
}

client.cpp文件代码(跟单进程版一样的)

6.4 线程池版

task.hpp文件代码

#pragma once 
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
namespace li 
{
    class task
    {
    public:
        task()
            :_fd(-1)
        {}
        task(const int& _fd)
            :_fd(_fd)
        {}
        
        // 为客户端提供服务
        void Run()
        {
            if (_fd > 0) 
            { 
                while (true)
                {
                   char buffer[1024];
                   memset(buffer, '\0', sizeof(buffer));
                   ssize_t read_count = read(_fd, buffer, sizeof(buffer) - 1);
                   if (read_count > 0)
                   {
                        buffer[read_count] = '\0';
                        std::cout << "client" << _fd << ":" << buffer << std::endl;
                        std::string msg;
                        msg += std::to_string(_fd);
                        msg += " okok!!!";
                        write(_fd, msg.c_str(), msg.size());
                   }
                   else if (read_count == 0)
                   {
                       std::cout << "client quit...." << std::endl;
                       break;
                   }
                   else 
                   {
                       std::cerr << "read" << errno << std::endl;
                       break;
                   }
               }
            }
            close(_fd);
        }

    private:
        int _fd;
    };
}

singleton_thread_pool.hpp文件代码

#pragma once 
#include <iostream>
#include <queue>
#include <pthread.h>

namespace li 
{
    template<class T>
    class thread_pool
    {
    public:
        ~thread_pool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }

        static void* Run(void* arg)
        {
            pthread_detach(pthread_self());
            thread_pool<T>* tp = (thread_pool<T>*)arg;
            while (true)
            {
                tp->Lock();
                while (tp->IsEmpty())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->UnLock();
                t.Run();
            }   
        }
        void ThreadPoolInit()
        {
            pthread_t id;
            for (int i = 0; i < _num; i++)
            {
                pthread_create(&id, nullptr, Run, (void*)this);
            }
        }

        void PushTask(const T& t)
        {
            Lock();
            _q.push(t);
            UnLock();
            Signal();
        }
        
        static thread_pool<T>* GetInstance(const int& num = 10) 
        {
            static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 静态的mutex不需要销毁

            if (_ptr == nullptr)  // 双重判断,防止创建之后还会上锁,影响效率
            {
                pthread_mutex_lock(&mutex);
                if (_ptr == nullptr)
                {
                    _ptr = new thread_pool<T>(num);
                }
                pthread_mutex_unlock(&mutex);
            }
            return _ptr;
        }
    private:
        thread_pool(const thread_pool<T>& tp) = delete;
        thread_pool<T>& operator=(thread_pool<T>& tp) = delete;
        thread_pool(const int& num)
            :_num(num)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_cond, nullptr);
            ThreadPoolInit();
        }
        void Lock()
        {
            pthread_mutex_lock(&_mutex);
        }

        void UnLock()
        {
            pthread_mutex_unlock(&_mutex);
        }

        bool IsEmpty()
        {
            return _q.size() == 0;
        }

        void Wait()
        {
            pthread_cond_wait(&_cond, &_mutex);
        }

        void Signal()
        {
            pthread_cond_signal(&_cond);
        }

        void PopTask(T* t)
        {
            *t = _q.front();
            _q.pop();
        }
    private:
        std::queue<T> _q;
        int _num;
        pthread_mutex_t _mutex;
        pthread_cond_t _cond;
        static thread_pool<T>* _ptr;
    };
    template<class T>
    thread_pool<T>* thread_pool<T>::_ptr = nullptr;  //静态成员需要在类外初始化
}

server.cpp文件代码

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include "singleton_thread_pool.hpp"
#include "task.hpp"

using namespace li;
void Usage(const char* proc)
{
    std::cout << "Usage" << proc << "port" << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    
    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket" << errno << std::endl;
        return 2;
    }

    // 2. 绑定端口和IP
    uint16_t sin_port = std::stoi(argv[1]);
    sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(sin_port);
    server.sin_addr.s_addr = INADDR_ANY;
    if (bind(sockfd, (struct sockaddr*)&server, sizeof(server)) != 0)
    {
        std::cerr << "bind" << errno << std::endl;
        return 3;
    }
    
    // 3. 设置监听listen
    if (listen(sockfd, 5) != 0)
    {
        std::cerr << "listen" << errno << std::endl;
        return 4;
    }

    // 4. 接受连接,给客户端提供服务
    sockaddr_in client;
    socklen_t addrlen;
    while (true)
    {
        int new_fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
        
        if (new_fd > 0)
        {
            uint16_t client_port = ntohs(client.sin_port);
            const char* client_IP = inet_ntoa(client.sin_addr);
            std::cout << "Get a link --> IP:" << client_IP << "    port:" << client_port << std::endl;
            task t(new_fd);  // 构建新任务
            thread_pool<task>::GetInstance(5)->PushTask(t);  // 将新任务放到队列里面
        }
    }
    return 0;
}

6.5 三次挥手建立连接

6.5.1 服务器初始化

  1. 调用socket, 创建文件描述符
  2. 调用bind, 将当前的文件描述符和ip/port绑定在一起,如果这个端口已经被其他进程占用了, 就会bind失败
  3. 调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备
  4. 调用accecpt, 并阻塞, 等待客户端连接过来

6.5.2 客户端建立连接的过程

  1. 调用socket,创建文件描述符
  2. 调用connect, 向服务器发起连接请求
  3. 第一次握手: connect会发出SYN段并阻塞等待服务器应答
  4. 第二次握手: 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"
  5. 第三次握手: 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段
    注意: 三次握手现在只是提一下,具体的细节后面的文章会更新

6.6 通信的过程

1.建立连接后,TCP协议提供全双工的通信服务。全双工,即在同一条连接中,同一时刻,通信双方可以同时写数据。半双工,同一条连接在同一时刻只能由一方来写数据

2.通信的过程中服务器和客户端都可以调用文件操作的read和write接口,read读的时候也是像管道那样是阻塞等待。

6.7 四次挥手断开连接

  1. 第一次挥手: 如果客户端没有更多的请求了,就调用close()关闭连接,客户端会向服务器发送FIN段
  2. 第二次挥手: 此时服务器收到FIN后,会回应一个ACK,同时read会返回0
  3. 第三次挥手: read返回之后,服务器就知道客户端关闭了连接,也调用close关闭连接,这个时候服务器会向客户端发送一个FIN
  4. 第四次挥手: 客户端收到FIN,再返回一个ACK给服务器
    注意: 四次挥手现在只是提一下,具体的细节后面的文章会更新
  • 8
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论 16

打赏作者

柿子__

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值