文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

【计算机网络】Socket编程

2023-10-20 14:08

关注

文章目录

理解源IP地址和目的IP地址

IP地址:公网IP,用于唯一标识互联网中的一台主机

源IP,目的IP:对于一个报文来讲,从哪来,到哪去。

源IP指将数据发送过来的IP地址,目的IP指将数据发送给下一个设备的IP地址(mac地址的变化)

意义: 指导一个报文该如何进行路径选择,目的IP是让我们根据目标进行路径选择的依据

理解端口号和进程ID

IP仅仅是解决了两台物理机器之间的相互通信,但是我们还要考虑如何保证双方的用户之间可以看到发送的和接受的数据。所以网络通信传输数据是使用进程来发送和接受的,pid唯一标识一台主机上的一个进程,port端口号也是唯一标识一台机器上的一个进程, 互联网世界本质上就是一个进程通信的世界

IP + PORT = socket(网络套接字)能够标识互联网中的唯一一个进程

端口号(port)是传输层协议的内容

进程PID vs PORT 为什么PID已经可以标识一台机器上的一个进程了,为啥还要创造PORT端口号呢??

这就像身份证号和学号的关系一样,身份证号和学号都可以唯一标识一名学生。若不使用学号只使用身份证号会出现以下问题:

如果身份证号的格式发生变化,那么学校的系统就不能用了

身份证号可能并不能标识一个学生在学校里的信息,比如入学年份、学院、专业、班级等,而只需要一个学号就可以获得这些信息,便于筛选查询数据

所以PORT的创建是为了网络部分和操作系统部分进行解耦

另外一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定

理解源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。就是描述"数据是谁发的,要发给谁"

计算机本身不产生数据,产生数据的是人。人通过特定的客户端产生数据通过网络通信被传送到服务器中。所以所谓的网络通信本质上就是进程间通信 ,比如:抖音的app客户端(进程)<->抖音的服务器(也是一个进程)

认识TCP协议

TCP(Transmission Control Protocol 传输控制协议)特性:

认识UDP协议

UDP(USser Datagram Protocol 用户数据报协议)特性:

TCP 可靠,UDP不可靠。TCP需要使用更多的资源,具体使用哪个必须根据使用场景决定

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

为了使网络程序具有可移植性,使用同样的C代码在大端和小端计算机上编译后都能够正常运行,可以调用以下库函数做网络字节序和主机字节序转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-003WDtyy-1673836199573)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230114135903412.png)]

这些函数名非常好记 h(host 主机) to n(net 网络) l(32位长整型) s(16位短整型)

例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

如果主机是小端字节序,这些函数会将参数做相应的大小端转换然后返回

如果主机是大端字节序,这些函数不做转换,将参数原封不动的返回

socket编程接口

socket网址查看

PING + 网址 (查看IP + PORT)

socket常见API

// 创建 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);// 开始监听socket (TCP, 服务器)int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)int accept(int socket, struct sockaddr* address, socklen_t* address_len);// 建立连接 (TCP, 客户端)int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

可以看到bind(), accept(), connect()函数的参数中都含有一个const struct sockaddr *address,这是因为网络通信的标准方式有多种,例如基于IP的网络通信,AF_INET,原始套接字,域间套接字等的通信方式。为了系统结构的统一化,程序员们设计出一种结构sockaddr作为一个参数标识通信方式,让我们可以使用同一个接口来完成通信

UDP协议实现网络通信

UDP创建socket文件描述符
man socket # 查看文档
#include           #include int socket(int domain, int type, int protocol);DESCRIPTION    // socket函数创建一个通信端点并且返回一个描述符       socket() creates an endpoint for communication and returns a descriptor.       The  domain  argument specifies a communication domain; this selects the protocol family which will be used for communication.  These families are defined in <sys/socket.h>.    // 目前已知的具体格式包含       The currently understood formats include:Name                Purpose                          Man page    //有点多就不全部复制过来了,最常用的就是这个       AF_INET             IPv4 Internet protocols          ip(7)    The socket has the indicated type, which specifies the communication semantics.  Currently defined types are://TCP协议使用 提供有序,可靠,双向,基于连接的字节流,可以支持带外数据传输机制       SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.    //UDP协议使用 支持数据报(固定最大长度的无连接、不可靠消息)       SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).RETURN VALUE         //成功返回文件描述符,若出现错误返回-1,并正确设置errno       On success, a file descriptor for the new socket is returned.  On error, -1 is returned, and errno is set appropriately.
第一个参数:domain参数指定一个通信域,domain参数选择用于通信的协议族,这些协议族被定义在<sys/socket.h>头文件中AF_INET             IPv4 Internet protocols          ip(7)第二个参数:type 套接字有指定的类型,它指定通信语义,当前定义的类型有(这里也是列举了两个最常用的)TCP协议使用 提供有序,可靠,双向,基于连接的字节流,可以支持带外数据传输机制       SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.UDP协议使用 支持数据报(固定最大长度的无连接、不可靠消息)SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).第三个参数:protocol 指定协议,通常只有一个协议支持特定套接字类型,所以在这种情况下协议可以指定为零返回值:成功返回一个文件描述符,失败返回-1

示例代码

// 1、创建套接字,打开网络文件int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){    std::cerr << "socket create errno:" << errno << std::endl;    return 1; }
sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.

在这里插入图片描述

注意:

在socket API中sockaddr和sockaddr_un都可以使用sockaddr_in类型表示,当我们传入sockaddr_in类型的结构体对象指针,接口会拿出前16位地址类型判断使struct sockaddr还是structaddr_un。在使用的时候需要强制转化成sockaddr_in.

这里可能会有小伙伴感觉奇怪,为啥不使用void*传入参数而是从新创建一个结构体sockaddr_in呢?这是因为当时的C语言还并不支持 void *语法,为了方便两种接口的统一就这样设计的

UDP绑定端口号

想要使用绑定端口号函数首先要先初始化填充sockaddr结构体对象,需要填充三个参数

#include int bind(int socket, const struct sockaddr *address,         socklen_t address_len);
第一个参数:socket      一个文件描述符,指定绑定的网络文件第二个参数:address     一个结构体指针,用于指定协议类型第三个参数:address_len ssize_t类型的重命名,用于指定address结构体对象的大小返回值: 成功返回0,失败返回-1

查看address_in 类型的定义,编辑器用的是vscode,右击类型转到定义就可以查看了

struct sockaddr_in  {    __SOCKADDR_COMMON (sin_);    in_port_t sin_port;    struct in_addr sin_addr;        unsigned char sin_zero[sizeof (struct sockaddr) -   __SOCKADDR_COMMON_SIZE -   sizeof (in_port_t) -   sizeof (struct in_addr)];  };struct in_addr  {    in_addr_t s_addr;  };
// 2 、服务器绑定端口和ip(特殊处理)struct sockaddr_in local;local.sin_family = AF_INET;        //sockaddr 初始化1//此处的端口号,是我们计算机上的变量,是主机序列local.sin_port = htons(port);    //sockaddr 初始化2//a.需要将人识别的点分十进制,字符串风格的IP地址,转换成4字节整数iP//b.也要考虑大小端 //云服务器不允许用户直接bind公网IP,另外,实际正常编写的时候也不会指明IP//local.sin_addr.s_addr = inet_addr("43.2.2.2");//INADDR_ANY: 如果你bind的是确定的IP(主机),意味着只有发到该IP主机上面的数据才会//交给你的网络进程,但是一般的服务器右多个网卡,配置多个IP,我们需要的不是某个IP上的数据//我们需要的是,所有发送到该主机,发送到该端口的数据local.sin_addr.s_addr = INADDR_ANY;//sockaddr 初始化3if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){std::cerr << "bind errno" << errno << std::endl;return 2; }
UDP接收发送网络数据

UDP接受和发送网络数据一般使用这组接口。UDP使用的是数据报格式的数据传输,所以不可以使用字节流式接口

#include #include // UDP发送网络数据ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);// UDP接受网络数据ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom()第一个参数: sockfd 网络文件描述符第二个参数: buf 传输的数据第三个参数: len 传输数据的大小是多少个字节第四个参数: flags 设置为0第五个参数: *dest_addr 输出型参数,是谁给我这个进程发送数据的第六个参数: addrlen 输出型参数用于标记dest_addr的大小返回值 成功返回0 失败返回-1sendto()第一个参数: sockfd 网络文件描述符第二个参数: buf 传输的数据第三个参数: len 传输数据的大小是多少个字节第四个参数: flags 设置为0第五个参数: *dest_addr 我需要将这个数据发送给谁第六个参数: addrlen 数用于标记dest_addr的大小返回值 成功返回0 失败返回-1

示例代码

// 3、提供服务bool quit = false;#define NUM 1024char buffer[NUM];while (!quit){    struct sockaddr_in peer;    socklen_t len = sizeof(peer);    ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len); //网络中只有数据报和字节流,不需要传送'\0'    if (cnt > 0){        buffer[cnt] = 0;        std::cout << "client# " << buffer << std::endl;        std::string echo_hello = "hello client";        sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&peer, len);    }}return 0;}

简单的UDP网络程序

服务器代码

#include #include #include #include #include #include #include void Usage(std::string proc){    std::cout << "Usage: " << proc << "port" << std::endl; }int main(int argc, char* argv[]){    if (argc != 2){        Usage(argv[0]);        return -1;    }    uint16_t port = atoi(argv[1]);    // 1、创建套接字,打开网络文件    int sock = socket(AF_INET, SOCK_DGRAM, 0);    if (sock < 0){        std::cerr << "socket create errno:" << errno << std::endl;        return 1;     }    // 2 、服务器绑定端口和ip(特殊处理)    struct sockaddr_in local;    local.sin_family = AF_INET;    //此处的端口号,是我们计算机上的变量,是主机序列    local.sin_port = htons(port);     //a.需要将人识别的点分十进制,字符串风格的IP地址,转换成4字节整数iP    //b.也要考虑大小端     //云服务器不允许用户直接bind公网IP,另外,实际正常编写的时候也不会指明IP    //local.sin_addr.s_addr = inet_addr("43.2.2.2");    //INADDR_ANY: 如果你bind的是确定的IP(主机),意味着只有发到该IP主机上面的数据才会    //交给你的网络进程,但是一般的服务器右多个网卡,配置多个IP,我们需要的不是某个IP上的数据    //我们需要的是,所有发送到该主机,发送到该端口的数据    local.sin_addr.s_addr = INADDR_ANY;    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){        std::cerr << "bind errno" << errno << std::endl;        return 2;     }     // 3、提供服务    bool quit = false;    #define NUM 1024    char buffer[NUM];    while (!quit){        struct sockaddr_in peer;        socklen_t len = sizeof(peer);                ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);        if (cnt > 0){            buffer[cnt] = 0;            std::cout << "client# " << buffer << std::endl;            std::string echo_hello = "hello client";            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&peer, len);        }        }    return 0;}

客户端代码

#include #include #include #include #include #include #include void Usage(std::string proc){    std::cout << "Usage:\n\t" << proc << " server_ip server_port" << std::endl;}int main(int argc, char* argv[]){    if (argc != 3) {        Usage(argv[0]);        return 3;    }    //1、创建套接字,打开网络文件    int sock = socket(AF_INET, SOCK_DGRAM, 0);    if (sock < 0){        std::cerr << "socket error : " << errno << std::endl;        return 1;     }    //2、客户端也必须显示的bind的吗?    //a、首先,客户端必须也要有ip和port    //b、但是,客户端不需要显示bind!一旦显示bind,就必须明确,client要和哪一个port关联    //client指明端口号,在client一定存在吗??会不会已经被其他人绑定了呢??一旦port被占用则无法使用服务    //server要求port必须明确,并且不变,但client只要有就可以!一般是OS自动给我们bind()    //当client正常发送数据的时候,OS会自动给你bind,采用随机端口的方式,自动帮你匹配合适端口    //2.使用服务    while (true){        std::string message;        std::cout << "输入#";        std::cin >> message;        //a、你的数据从哪里来        //b、你要发给谁        sockaddr_in server;        server.sin_family = AF_INET;        server.sin_port = htons(atoi(argv[2]));        server.sin_addr.s_addr = inet_addr(argv[1]);        sendto(sock, message.c_str(), message.length(), 0, (struct sockaddr*)&server, sizeof(server));        struct sockaddr_in tmp;        socklen_t len = sizeof(tmp);        char buffer[1024];        int cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &len);        if (cnt > 0){            buffer[cnt] = 0;            std::cout << "server echo#" << buffer << std::endl;        }           }    return 0;}

TCP协议实现网络通信

TCP创建socket文件描述符

客户端服务端都需要

//1.创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket create errno" << errno << std::endl;return 2;}
TCP绑定端口号

服务端需要显示绑定,客户端OS自动绑定

//2.binduint16_t port = atoi(argv[2]);struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons(port);local.sin_family = AF_INET;if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){    std::cerr << "bind errno" << errno << std::endl;    return 3;}
TCP建立连接

服务端需要

// 开始监听socket (TCP, 服务器)int listen(int socket, int backlog); //第二个参数传5即可

阅读文档

DESCRIPTION //描述//listen函数所指的sockfd套接字标记为被动套接字,就是这个套接字将使用accept(2)标准接收过来的连接请求listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2).       The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.       //backlog参数定义sockfd的挂起连接队列可能增长的最大长度。如果连接请求在队列已满时到达客户端可能会收到带有ECONNREFUSED指示的错误,或者,如果底层协议支持重传,则可以忽略该请求,以便稍后重新尝试连接成功。       The  backlog  argument defines the maximum length to which the queue of pending connections for sockfd may grow.  If a connection request arrives when the queue is full, theclient may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a  later  reattemptat connection succeeds.

示例代码

//3.因为tcp是面向连接的,所以在通信之前必须要建立连接//连接一定是有人主动建立(客户端),一定有人被动接收连接(服务器)const int back_log = 5;if (listen(sock, back_log) < 0){std::cerr << "listen error" << std::endl;return 4;}
TCP接收请求

服务端需要

上文创建的套接字为监听套接字,用于与端口号绑定。accept()函数用于接收监听套接字获得到的请求,并与其建立连接,会返回一个新的文件描述符用于数据传输

// 接收请求 (TCP, 服务器)int accept(int socket, struct sockaddr* address, socklen_t* address_len);

阅读文档

accept()系统调用用于基于连接的套接字类型(SOCK_STREAM、SOCK_SEQPACKET)。它提取侦听套接字挂起连接队列中的第一个连接请求sockfd,创建一个新的连接套接字,并返回一个引用该套接字的新文件描述符。新创建的套接字未处于侦听状态。原始套接字sockfd不受此调用的影响。The  accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET).  It extracts the first connection request on the queue of pending connec‐tions for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket.  The newly created socket is not  in  thelistening state.  The original socket sockfd is unaffected by this call.

示例代码

//accept     for ( ; ; ){        struct sockaddr_in peer;        socklen_t len = sizeof(peer);        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len); //接收请求        if (new_sock < 0){//如果接收失败则跳过            continue;           }        //提供服务        while (true){        }    }
TCP发起连接

客户端需要

struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_addr.s_addr = inet_addr(svr_ip.c_str());server.sin_family = AF_INET;server.sin_port = htons(svr_port);if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){    std::cerr << "connect errno" << errno << std::endl;    return 3;}
TCP接收发送网络数据

因为TCP协议使用字节流的流式传输格式,所以也可以使用read(), write(),recv(), send()等方法流式写入读取的方法进行接收和发送数据

ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){    //将获取的内容当作字符串    buffer[s] = 0;    std::cout << "client# " << buffer << std::endl;    std::string echo_string = ">>>server<<<";    echo_string += buffer;    echo_string += "...";    write(sock, echo_string.c_str(), echo_string.size());}

简单TCP网络程序

经过上面的学习,我们可以将示例代码进行拼接,很轻松就可以写出一个服务器和客户端

服务器代码

#include #include #include #include #include #include #include #include #include #include #include void Usage(std::string s){    std::cout << "Usage :" << "\n\t" << s << " port " << std::endl;}int main(int argc, char* argv[]){    if (argc != 2){        Usage(argv[0]);        return 1;    }        //1.创建套接字    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);    if (listen_sock < 0){        std::cerr << "socket create errno" << errno << std::endl;        return 2;    }    std::cout << "listen success" << std::endl;    //2.bind    uint16_t port = atoi(argv[1]);    struct sockaddr_in local;    memset(&local, 0, sizeof(local));    local.sin_addr.s_addr = INADDR_ANY;    local.sin_port = htons(port);    local.sin_family = AF_INET;    if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){        std::cerr << "bind errno" << errno << std::endl;        return 3;    }    std::cout << "bind success" << std::endl;    //3.因为tcp是面向连接的,所以在通信之前必须要建立连接    //连接一定是有人主动建立(客户端),一定有人被动接收连接(服务器)    const int back_log = 5;    if (listen(listen_sock, back_log) < 0){        std::cerr << "listen error" << std::endl;        return 4;    }    //accept     for ( ; ; ){        struct sockaddr_in peer;        socklen_t len = sizeof(peer);        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);        if (new_sock < 0){            continue;           }        std::cout << "get a link ..." << std::endl;             //提供服务        while (true){            char buffer[1024];            memset(buffer, 0, sizeof(buffer));            ssize_t s = read(sock, buffer, sizeof(buffer) - 1);            if (s > 0){                //将获取的内容当作字符串                buffer[s] = 0;                std::cout << "client# " << buffer << std::endl;                std::string echo_string = ">>>server<<<";                echo_string += buffer;                echo_string += "...";                write(sock, echo_string.c_str(), echo_string.size());            }            else if (s == 0){                std::cout << "client quit ..." << std::endl;                break;            }            else {                std::cerr << "read errno" << errno << std::endl;                break;            }        }    }}

客户端代码

#include #include #include #include #include #include #include #include #include #include #include void Usage(std::string s){    std::cout << "Usage :" << "\n\t" << s << " server_ip server_port " << std::endl;}int main(int argc, char* argv[]){    if (argc != 3){        Usage(argv[0]);        return 1;    }    std::string svr_ip = argv[1];    uint16_t svr_port = atoi(argv[2]);    int sock = socket(AF_INET, SOCK_STREAM, 0);    if (sock < 0){        std::cerr << "socket errno" << errno << std::endl;        return 2;    }     struct sockaddr_in server;    memset(&server, 0, sizeof(server));    server.sin_addr.s_addr = inet_addr(svr_ip.c_str());    server.sin_family = AF_INET;    server.sin_port = htons(svr_port);    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){        std::cerr << "connect errno" << errno << std::endl;        return 3;    }    //进行正常的业务请求    while (true){        std::cout << "Please Enter# ";        char buffer[1024];        fgets(buffer, sizeof(buffer) - 1, stdin);        write(sock, buffer, strlen(buffer));        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);        if (s > 0){            buffer[s] = 0;            std::cout << "server echo# " << buffer << std::endl;        }    }}

但是以上代码有一个非常明显的问题,那就是一个服务器在接收一个客户端的连接后为这个客户端提供服务,而主进程就死循环执行服务代码了,无法继续接收其他客户端的连接,导致我们的服务器在同一时刻只能服务一个客户端,接下来我们会使用操作系统的知识对服务器进行优化

多进程优化TCP服务器

我们可以让服务器主进程接收客户端的连接,然后创建子进程来为客户端提供服务。

方法一 在信号章节我们学过signal()函数,其可以自定义信号的处理方式

#include signal(SIGCHLD, SIG_IGN); // 在Linux中父进程忽略子进程的SIGCHILD信号,子进程会自动退出释放资源

在子进程退出时会给父进程发送SIGCHILD信号,告诉父进程我推出了,如果我们让父进程忽略SIGCHILD信号,那么子进程就会自动退出并且释放资源,父进程就无需等待

方法二 让父进程进行waitpid(),子进程马上退出,让孙子进程执行服务,孙子进程会被操作系统领养

//child if (fork() > 0) exit(0);close(listen_sock);ServiceIO(new_sock); //提供服务close(new_sock);exit(0);

所以我们可以将提供的服务放在一个函数中供子进程调用

signal(SIGCHLD, SIG_IGN); // 在Linux中父进程忽略子进程的SIGCHILD信号,子进程会自动退出释放资源    //accept     for ( ; ; ){        struct sockaddr_in peer;        socklen_t len = sizeof(peer);        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);        if (new_sock < 0){            continue;           }        std::cout << "get a link ..." << std::endl;         pid_t id = fork();        if (id  < 0){            continue;        }        else if (id == 0){            //child             if (fork() > 0) exit(0);            close(listen_sock);            ServiceIO(new_sock); //提供服务            close(new_sock);            exit(0);        }        else {            //father            close(new_sock);        }    }

子进程会继承父进程的struct files_struct因为子进程只提供服务所以listen_sock是没有用的,可以将其关掉,对于父进程只用于监听接收请求,所以提供服务的new_sock传递给子进程后就没有用了,可以关闭,防止文件描述符泄露

多线程优化TCP服务器

创建线程

pthread_t tid;int *pram = new int(new_sock);pthread_create(&tid, nullptr, HandlerRequest, pram);

线程执行函数

void *HandlerRequest(void* args){    pthread_detach(pthread_self());    int sock = *(int*)args;    delete (int*)args;    ServiceIO(sock);   //提供服务    close(sock);}

注意:该代码存在以下问题

a、创建线程、进程无上限

b、当客户连接来了,我们才给客户创建进程/线程

线程池优化TCP服务器

使用find()指令查找我们之前写过的线程池

[clx@VM-20-6-centos Lesson_Linux]$ lltotal 28drwxrwxr-x 21 clx clx 4096 Aug  1 00:49 21_7_lessondrwxrwxr-x 25 clx clx 4096 Aug 27 09:40 21_8_lessondrwxrwxr-x 21 clx clx 4096 Oct 30 20:28 22_10_lessondrwxrwxr-x 23 clx clx 4096 Nov 28 19:13 22_11_lessondrwxrwxr-x  6 clx clx 4096 Dec 30 21:09 22_12_lessondrwxrwxr-x 18 clx clx 4096 Sep 30 18:01 22_9_lessondrwxrwxr-x  5 clx clx 4096 Jan 16 09:01 23_1_lesson[clx@VM-20-6-centos Lesson_Linux]$ find . -name ThreadPool.hpp //查找线程池./22_11_lesson/lesson_11_24/FirstProject/ThreadPool.hpp./22_11_lesson/lesson_11_25/FirstProject/ThreadPool.hpp

创建服务套接字后将使用套接字创建一个任务,然后Push到线程池里就可以了

Task t(new_sock);ThreadPool::GetInstance()->InitThreadPool();ThreadPool::GetInstance()->PushTask(new_sock);

Task代码

#pragma once #include #include #include #include class Task{public:    Task() : sock(-1){};    Task(int _sock) : sock(_sock){}    void ProcessOn(){        // while (true){            char buffer[1024];            memset(buffer, 0, sizeof(buffer));            ssize_t s = read(sock, buffer, sizeof(buffer) - 1);            if (s > 0){                //将获取的内容当作字符串                buffer[s] = 0;                std::cout << "client# " << buffer << std::endl;                std::string echo_string = ">>>server<<<";                echo_string += buffer;                echo_string += "...";                write(sock, echo_string.c_str(), echo_string.size());            }            else if (s == 0){                std::cout << "client quit ..." << std::endl;                //break;            }            else {                std::cerr << "read errno" << errno << std::endl;                //break;            }        //}        close(sock);    }        void operator()(){        ProcessOn();    }private:    int sock;};

线程池代码

#pragma once#include #include #include #include "Task.hpp"#define THREAD_NUM 6class ThreadPool{private:  ThreadPool(int num = THREAD_NUM) : _pthread_num(num), _stop(false)  {    pthread_mutex_init(&_lock, nullptr);    pthread_cond_init(&_cond, nullptr);  }public:  ~ThreadPool()  {    pthread_mutex_destroy(&_lock);    pthread_cond_destroy(&_cond);  }  static void *ThreadRoutine(void *args);  static ThreadPool *GetInstance();  bool InitThreadPool();  void PushTask(const Task &task);  void PopTask(Task &task);  void ThreadWait() { pthread_cond_wait(&_cond, &_lock); }  void ThreadWakeUp() { pthread_cond_signal(&_cond); }  void ThreadLock() { pthread_mutex_lock(&_lock); };  void ThreadUnlock() { pthread_mutex_unlock(&_lock); };  bool IsStop() { return _stop; }  bool TaskQueueIsEmpty() { return _task_queue.empty(); }private:  std::queue<Task> _task_queue;  size_t _pthread_num;  bool _stop;  pthread_mutex_t _lock;  pthread_cond_t _cond;  static ThreadPool *_single_instance;};ThreadPool *ThreadPool::_single_instance = nullptr;ThreadPool *ThreadPool::GetInstance(){  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  if (_single_instance == nullptr)  {    pthread_mutex_lock(&mutex);    _single_instance = new ThreadPool();    _single_instance->InitThreadPool();    pthread_mutex_unlock(&mutex);  }  return _single_instance;}bool ThreadPool::InitThreadPool(){  pthread_t tid;  for (size_t i = 0; i < _pthread_num; i++)  {    int ret = pthread_create(&tid, nullptr, ThreadRoutine, (void *)this);    if (ret != 0)    {      std::cout << "pthread_create errno" << std::endl;      return false;    }  }  std::cout << "InitThreadPool success" << std::endl;  return true;}void *ThreadPool::ThreadRoutine(void *args){  ThreadPool *tp = (ThreadPool *)args;  while (true)  {    Task task(0);    tp->ThreadLock();    while (tp->TaskQueueIsEmpty())    {      tp->ThreadWait();    }    tp->PopTask(task);    tp->ThreadUnlock();    task.ProcessOn();  }}void ThreadPool::PushTask(const Task &task){  ThreadLock();  _task_queue.push(task);  ThreadWakeUp();  ThreadUnlock();}void ThreadPool::PopTask(Task &task){  task = _task_queue.front();  _task_queue.pop();}

socket编程总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hXnDEdo7-1673836199576)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230116101425614.png)]

TCP协议三次握手四次挥手

服务器初始化:

败;

建立连接的过程:

这个建立连接的过程, 通常称为 三次握手;

数据传输的过程

断开连接的过程:
这个断开连接的过程, 通常称为四次挥手

在学习 socket API 时需要注意应用程序和TCP协议层是如何交互的:

应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段

应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些字段,再比如read()返回0就表明收到了FIN段

学习路线规划

现在我们从零开始,通过系统调用接口(socket, bind, listen …)来编写应用层,在完善应用层的基础上之后我们会学习操作系统的传输层和网络层,还有网卡驱动中的数字链路层的原理以及设计
接请求;

来源地址:https://blog.csdn.net/m0_69442905/article/details/128701277

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-服务器
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯