前进之路 发表于 2024-8-15 12:18:22

C/C++开发---全篇

1、统筹

学习目标:
        C/C++、python精通。
就业匹配方向:专精一个领域,延长职业生存。
        (1)适配行业;
        (2)量化;
        (3)安全;
        (4)高性能;
        (5)cdn;
        (6)游戏服务;
        (7)根本架构;
        (8)虚拟化;
        (9)网路开发;
        (10)存储;
        (11)主动驾驶;
        (12)推荐算法;
        (13)流媒体服务;
        (14)金融业务;

【1】C/C++学习重点:
        (1)数据结构/算法;(hash、rbtree、b-/b+ tree、list)
        (2)计划模式;(见计划模式专栏,要责备精)
        (3)库;(STL、C++新特性)
        (4)Linux工程;(CMake、git、tcp抓包、netstat)
【2】软件计划根本组件:
        (1)内存池;
        (2)线程池;
        (3)数据库毗连池;
        (4)请求池;
        (5)原子操作;
        (6)ringbuffer;
        (7)无锁队列;
        (8)定时器方案;
        (9)死锁检测;
        (10)内存泄漏;
        (11)日志;
        (12)网络块
        (13)共享内存;
        (14)probuf;
【3】软件计划中心组件:
        (1)mysql;
        (2)redis;
        (3)nginx;
        (4)grpc;
        (5)mq;(消息队列)
【4】框架:适配行业
        (1)cuda;(适配高性能盘算)
        (2)spdk;(适配存储)
        (3)skynet;(适配游戏行业)
        (4)dpdk;(适配网络)
【5】网络方面:
        (1)网络编程;(select、poll、epoll;多进程多线程;壅闭非壅闭;同步异步;业务)
        (2)网络原理;(eth、ip、udp/tcp、http)
        (3)实现一个网络框架;实现一个TCP/IP协议栈;
【6】运维和摆设:
        (1)docker;
        (2)k8s;
【7】性能分析:需要对内核有了解
        (1)磁盘;
        (2)网络;
        (3)内存;
【8】分布式
        (1)分布式数据库 TiDB;
        (2)分布式文件体系 ecph;
        (3)分布式协同 etcd;

注解:
        【1】【2】【3】【4】【5】作为技术根本;【6】【7】作为技术实现的产品;【8】作为技术扩展。
        硬实力:技术过硬。
        软实力:组织能力;协调能力;沟通能力等。
        运气。
        硬实力 + 软实力 + 运气。





2、数据结构与算法

学习目标:
        队列、栈、链表、环形链表、双向链表、二叉树、红黑树、2-3-4、B树、B+树、矩阵等。

2.1、B-/B+ tree









2.2、红黑树







2.3、哈希







3、计划模式

        具体见【计划模式】专栏

4、C++库

        具体见【C++ STL】专栏

5、Linux工程

        具体见【Linux】专栏

6、网络编程

6.1、Linux IO

        I/O(input/output)也就是输入和输出,在冯诺依曼体系结构当中,将数据从输入设备拷贝到内存就叫作输入,将数据从内存拷贝到输出设备就叫作输出。
https://i-blog.csdnimg.cn/direct/1fded7ae1c7d44308231822045cb333f.png
IO 最主要的标题就是效率标题,以读取数据为例:
        当 read/recv 时,假如底层缓冲区中没有数据,read/recv 就会壅闭等候。
        当 read/recv 时,假如底层缓冲区中有数据,read/recv 就会进行拷贝。
        以是,IO 的本质就是:等候(等候 IO 条件就绪) + 数据拷贝(当 IO 条件就绪后将数据拷贝到内存或外设)。只要缓冲区中没有数据,read/recv 就会一直壅闭等候,直到缓冲区中出现数据,然后进行拷贝,以是 read/recv 就会花费大量时间在等这一操作上面,这就是一种低效的 IO 模式。
        任何 IO 的过程,都包含 “等” 和 “拷贝” 这两个步骤,但在现实的应用场景中 “等” 消耗的时间每每比 “拷贝” 消耗的时间多。
五种IO模型:
        (1)壅闭式IO【用1杆鱼竿钓鱼,将鱼钩抛入水中后,就一直盯着浮标一动不动,不理会外界的任何动静,直到有鱼中计,就摆荡鱼竿将鱼钓上来】。
        (2)非壅闭轮询式IO【用1杆鱼竿钓鱼,将鱼钩抛入水中后,就可以去做其它事情了,然后定期观察浮标的动静,假如有鱼中计就将鱼钓上来,否则就继承做其它事情】。
        (3)信号驱动IO【用1杆鱼竿钓鱼,将鱼钩抛入水中后,在鱼竿顶部绑一个铃铛,就可以去做其它事情了,假如铃铛一响就知道有鱼中计了,于是摆荡鱼竿将鱼钓上来,否则就不管鱼竿。】。
        (4)多路复用,多路转接【用100杆鱼竿钓鱼,将 100 个鱼钩抛入水中后,就定期观察这 100 个浮漂的动静,假如某个鱼竿有鱼中计就摆荡对应的鱼竿将鱼钓上来。】。
        (5)异步IO【我是公司CEO,我不钓鱼,但我会告诉老墨,我想吃鱼了,以是老墨拿着桶、鱼竿、电话去钓鱼。鱼钓上后,打电话通知我。】。

6.2、壅闭IO

        壅闭I/O(Blocking I/O)是一种最常见的I/O模型,它是默认的I/O操作方式。在这种模式下,当一个进程对某个文件描述符(好比网络套接字、磁盘文件等)执行读或写操作时,假如该操作不能立即完成(例如,数据还没有到达、缓冲区满等),则进程会被挂起,直到数据准备好或者操作可以执行为止。这种挂起状态会导致进程暂停执行,直到I/O操作完成。
特点:
        (1)简单性:编程模型简单,易于理解和实现。
        (2)资源利用率:在单个I/O密集型应用中,CPU的利用率可能较低,由于进程大部门时间都在等候I/O操作完成。
        (3)并发性:对于需要处理大量并发毗连的服务器应用来说,可能需要为每个毗连分配一个单独的线程或进程,这会导致大量的上下文切换和较高的内存消耗。
/*
    在基于TCP的网络编程中,服务器通常会接受客户端的连接请求,并在每个连接上接收和发送数据。使用阻塞I/O
*/
socket = listen(port);                   // 监听端口
while (true) {
    client_socket = accept(socket);      // 等待并接受客户端连接
    while (true) {
      data = read(client_socket);      // 读取客户端数据,如果数据没有到达,则阻塞
      // 处理数据...
      write(client_socket, response);// 发送响应到客户端
    }
    close(client_socket);
}
6.3、非壅闭轮询式IO



6.4、



6.5、




6.6、




6.7、

6.7.1、select

        select 是体系提供的一个多路转接的接口,可以用来实现多路复用输入 / 输出模型。select 体系调用可以让程序同时监督多个文件描述符上的状态变革。
        select 焦点工作就是等,当监督的文件描述符中有一个或多个事件就绪时,也就是直到被监督的文件描述符有一个或多个发生了状态改变,select 才会成功返回并将对应文件描述符的就绪事件告知调用者。
6.7.1.1、函数原型

函数原型:
#include <sys/time.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数解释:
    nfds       是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。在linux系统中,select的默认最大值为1024。设置这个值的目的是为了不用每次都去轮询这1024个fd,假设我们只需要几个套接字,我们就可以用最大的那个套接字的值加上1作为这个参数的值,当我们在等待是否有套接字准备就绪时,只需要监测maxfd+1个套接字就可以了,这样可以减少轮询时间以及系统的开销。
    readfds    输入输出型参数,readfs是一个容器,里面可以容纳多个文件描述符,把需要监视的描述符放入这个集合中,当有文件描述符可读时,select就会返回一个大于0的值,表示有文件可读.
    writefds   输入输出型参数,和readfs类似,表示有一个可写的文件描述符集合,当有文件可写时,select就会返回一个大于0的值,表示有文件可写。
    exceptfds输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的异常事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已就绪。(这个参数使用一次过后,需要进行重新设定)
    timeout    输入输出型参数,调用时由用户设置select的等待时间,返回时表示timeout的剩余时间。


参数timeout的取值:
    NULL/nullptr   select调用后进行阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
    0                select调用后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,select 检测后都会立即返回。
    特定的时间值       select调用后在指定的时间内进行阻塞等待,若被监视的文件描述符上一直没有事件就绪,则在该时间后select进行超时返回。


返回值说明:
    若函数调用成功,则返回事件就绪的文件描述符个数。
    若timeout时间耗尽,则返回0。
    若函数调用失败,则返回-1,同时错误码被设置。
    只要有一个fd数据就绪或空间就绪,就可以进行返回了。

错误码:
select调用失败时,错误码可能被设置为:
    EBADF   文件描述符为无效的或该文件已关闭。
    EINTR   此调用被信号所中断。
    EINVAL    参数nfds为负值。
    ENOMEM    核心内存不足。

数据结构fd_set:
    其实这个结构就是一个整数数组,更严格的说 fd_set 本质也是一个位图,用位图中对应的位来表示要监视的文件描述符。
    void FD_CLR(int fd, fd_set *set);   // 用来清除描述词组set中相关fd的位
    int FD_ISSET(int fd, fd_set *set);// 用来测试描述词组set中相关fd的位是否为真
    void FD_SET(int fd, fd_set *set);   // 用来设置描述词组set中相关fd的位
    void FD_ZERO(fd_set *set);          // 用来清除描述词组set的全部位

数据结构timeval:
    在Linux的C语言标准库中,timeval结构体的定义通常位于<sys/time.h>头文件中。
    格式如下:
      struct timeval {
            time_t tv_sec;         // 秒
            suseconds_t tv_usec;   // 微秒
      };
    gettimeofday()    该函数用于获取当前时间(包括秒和微秒),并将结果存储在timeval结构体中。
    settimeofday()    该函数用于设置系统时间,虽然它也接受一个timeval结构体作为参数,但通常用于系统时间校准等高级场景。 /*
   fs_set的一些操作
*/
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>

int main(void) {
    fd_set fdset;
    FD_ZERO(&fdset);                            /* 清空集合中所有的元素 */
    FD_SET(STDOUT_FILENO, &fdset);            /* 将stdout的文件描述符添加到集合中 */

    if (FD_ISSET(STDOUT_FILENO, &fdset) != 0)   /* 测试stdout是否包含在集合中 */
      printf("stdout has been set\n");
    else
      printf("stdout has not been set\n");

    FD_CLR(STDOUT_FILENO, &fdset);            /* 从集合中移除stdout的文件描述符 */

    if (FD_ISSET(STDOUT_FILENO, &fdset) != 0)
      printf("stdout has been set\n");
    else
      printf("stdout has not been set\n");

    return 0;
} 6.7.1.2、工作流程

假如要实现一个简单的select服务器,读取客户端发来的数据并进行打印,工作流程如下:
        (1)先初始化服务器,完成套接字的创建、绑定和监听。
        (2)定义一个_fd_array数组用于保存监听套接字和已经与客户端建立毗连的套接字,初始化时就可将监听套接字添加到_fd_array数组中。
        (3)然后服务器开始循环调用select函数,检测读事件是否就绪,若就绪则执行对应操作。
        (4)每次调用select函数之前,都需要定义一个读文件描述符集readfds,并将_fd_array中的文件描述符依次设置进readfds中,表示让select监督这些文件描述符的读事件是否就绪。
        (5)当select检测到数据就绪时会将读事件就绪的文件描述符设置进readfds中,此时就可以或许得知哪些文件描述符的读事件就绪,并对这些文件描述符进行对应操作。
        (6)若读事件就绪的是监听套接字,则调用accept函数从底层全毗连队列获取已建立的毗连,并将该毗连对应的套接字添加到_fd_array数组中。
        (7)若读事件就绪的是与客户端建立毗连的套接字,则调用read函数读取客户端发来的数据并进行打印输出。
        (8)服务器与客户端建立毗连的套接字读事件就绪,也可能是客户端将毗连关闭了,此时服务器应该调用close关闭该套接字,并将该套接字从_fd_array数组中扫除,不需要再监督该文件描述符的读事件了。
6.7.1.3、示例

服务器:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_BACKLOG (5)
#define BUF_SIZE (2048)
#define ONCE_READ_SIZE (1500)

#define EPOLL_SIZE (100);
#define MAX_EVENTS (10)

void usage(void) {
    printf("*********************************\n");
    printf("./server 本端ip 本端端口\n");
    printf("*********************************\n");
}

// 当前已有的链接
#define MAXCLINE 5   // 连接队列中的个数
int fd;    // 连接的fd

int conn_amount;   // 此次while循环,有发送过来数据的套接字个数

int main(int argc, char *argv[])
{
    struct sockaddr_in local;      // 服务器端地址
    struct sockaddr_in peer;         // 客户端地址
    socklen_t addrlen = sizeof(peer);
    int sock_fd = 0;               // 服务器端套接字
    int new_fd = 0;                  // 客户端套接字
    int ret = 0;
    char send_buf = {0};   // 发送缓冲区
    char recv_buf = {0};   // 接收缓冲区

    if (argc != 3) {
      usage();
      return -1;
    }

    char *ip = argv;
    unsigned short port = atoi(argv);
    printf("ip:port->%s:%u\n", argv, port);

    // 创建服务器端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    printf("socket: %d\n", sock_fd);
    if (sock_fd == -1) {
      perror("socket error");
      return -1;
    }

    // 初始化服务端地址结构体 包含ip地址、端口号
    memset(&local, 0, sizeof(struct sockaddr_in));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(ip);
    local.sin_port = htons(port);

    // 绑定端口绑定套接字、ip地址、端口号之间的关系
    ret = bind(sock_fd, (struct sockaddr *)&local, sizeof(struct sockaddr));
    if (ret == -1) {
      close(sock_fd);
      perror("bind error");
      return -1;
    }

    // 监听服务器套接字
    ret = listen(sock_fd, LISTEN_BACKLOG);
    if (ret == -1) {
      close(sock_fd);
      perror("listen error");
      return -1;
    }

    //文件描述符集合的定义
    fd_set fdsr;
    int maxsock = sock_fd;
    struct timeval tv;
    while (1) {
      // 清空 文件描述符集合
      FD_ZERO(&fdsr);
      // 将 服务器套接字 添加到 文件描述符集合 中
                FD_SET(sock_fd, &fdsr);

      //超时的设定,这里也可以不需要设置时间,将这个参数设置为NULL, 表明此时select为阻塞模式
                tv.tv_sec = 30;
                tv.tv_usec =0;

                //将当前已有的连接全部加到这个这个集合中,可以监测客户端是否有数据到来
                for(int i = 0; i < MAXCLINE; i++)
                {
                        if(fd!=0)
                        {
                                FD_SET(fd, &fdsr);
                        }
                }

      // 等待事件发生
      ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
                if(ret < 0)
                {
            // 没有找到有效的连接 失败
                        perror("select error!\n");
                        break;
                } else if(ret == 0)
                {
            // 指定的时间到,
                        printf("timeout \n");
                        continue;
                }

      // 接受数据 遍历【必要流程】
          for(int i =0; i < conn_amount; i++)
                {
            // 该套接字在集合中
                        if(FD_ISSET(fd, &fdsr))
                        {
                                ret = recv(fd, recv_buf, sizeof(recv_buf), 0);
                                if(ret <= 0) // 客户端连接关闭,清除文件描述符集中的相应的位
                                {
                                  printf("client[%d] close\n", i);
                                  close(fd);
                                  FD_CLR(fd, &fdsr);
                                  fd = 0;
                                  conn_amount--;
                                } else {
                  // 否则有相应的数据发送过来 ,进行相应的处理
                                        if(ret < BUF_SIZE)
                                                memset(&recv_buf, '\0', 1);
                                        printf("client[%d] send:%s\n", i, recv_buf);
                                }
                        }
                }
      
      // 循环遍历所有的连接,检测是否有数据到来【必要流程】
      if(FD_ISSET(sock_fd, &fdsr))
                {
                        new_fd = accept(sock_fd, (struct sockaddr *)&peer, &addrlen);
                        if(new_fd <= 0)
                        {
                                perror("accept error\n");
                                continue;
                        }

                        //添加新的fd到数组中 判断有效的连接数是否小于最大的连接数,如果小于的话,就把新的连接套接字加入集合
                        if(conn_amount < MAXCLINE)
                        {
                                for(int i = 0; i < MAXCLINE; i++)
                                {
                                        if(fd==0)
                                        {
                                                fd = new_fd;
                                                break;
                                        }
                                }
                                conn_amount++;
                                printf("new connection client[%d]%s:%d\n", conn_amount, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
                                if(new_fd > maxsock)
                                {
                                        maxsock = new_fd;
                                }
                        } else {
                                printf("max connections arrive ,exit\n");
                                send(new_fd,"bye",4,0);
                                close(new_fd);
                                continue;
                        }
      }
    }

    return 0;
}
#
# ./socket-select-server 127.0.0.1 4000
ip:port->127.0.0.1:4000
socket: 3
new connection client127.0.0.1:47328
client send:1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
client send:2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
client send:3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
client send:4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
client send:5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
client send:6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
client send:7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777
client send:8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
client send:9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
client send:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
client send:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
client send:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#
# 客户端:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_BACKLOG (5)
#define BUF_SIZE (100)

#define REQUEST_STR "tcp pack"

void usage(void) {
    printf("*********************************\n");
    printf("./client 对端ip 对端端口\n");
    printf("*********************************\n");
}

int main(int argc, char *argv[])
{
    struct sockaddr_in client;   // 客户端地址
    struct sockaddr_in server;   // 服务器地址
    int sock_fd = 0;
    int ret = 0;
    socklen_t addrlen = 0;
    char send_buf = {0};
    char recv_buf = {0};

    if (argc != 3) {
      usage();
      return -1;
    }

    char *ip = argv;
    unsigned short port = atoi(argv);
    printf("ip:port->%s:%u\n", argv, port);

    // 创建客户端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
      perror("socket error");
      return -1;
    }
    printf("sock_fd: %d\n", sock_fd);

    memset(&server, 0, sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip);
    server.sin_port = htons(port);

    ret = connect(sock_fd, (struct sockaddr *)&server, sizeof(struct sockaddr));
    if (ret == -1) {
      close(sock_fd);
      perror("connect error");
      return -1;
    }

    char seq = 0x31;
    while(1) {
      memset(send_buf, seq, BUF_SIZE);
      send(sock_fd, send_buf, BUF_SIZE, 0);
      printf("send %s\n", send_buf);
      sleep(2);
      seq++;
    }
   
    close(sock_fd);

    return 0;
}
#
# ./socket-epoll-client127.0.0.1 4000
ip:port->127.0.0.1:4000
sock_fd: 3
send 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
send 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
send 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
send 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
send 5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
send 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
send 7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777
send 8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
send 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
send ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
send ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
send <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
send ====================================================================================================
#
#
6.7.2、poll

        poll也是一种linux中多路转接的方案。它所对应的多路转接方案主要是解决select存在的两个标题:select的文件描述符有上限的标题;select每次都要重新设置关心的fd标题。
6.7.2.1、函数原型

函数原型:
    #include <poll.h>
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:
    struct pollfd *fds   指向pollfd结构体数组的指针,每个pollfd结构体代表一个要监视的文件描述符。该参数是待监控事件的集合。
    nfds_t nfds            fds数组中pollfd结构体的数量。这个值必须是正数。
    int timeout            等待事件发生前的超时时间(毫秒)。如果timeout为-1,则poll将无限期地等待直到至少有一个文件描述符就绪。如果timeout为0,则poll将立即返回,不阻塞。

返回值说明:
    成功时,poll返回准备就绪的文件描述符的数量(即fds数组中revents字段非零的pollfd结构体的数量)。
    如果在调用时发生错误,则返回-1,并设置errno以指示错误的原因。


结构体struct pollfd说明:
struct pollfd {
    int fd             要监视的文件描述符。
    short events       请求监视的事件类型,可以是POLLIN(可读)、POLLOUT(可写)、POLLERR(错误)、POLLHUP(挂起)、POLLNVAL(无效请求)等,可以使用位或(|)操作符组合多个事件。
short revents:在调用返回时,包含实际发生的事件的位掩码。这个字段在调用poll之前应该被忽略。
    short revents      poll函数返回时告知用户该文件描述符上的哪些事件已经就绪。
};

events和revents事件类型有如下种类:
#define POLLIN         0x0001 //有数据需要读取
#define POLLPRI      0x0002 //
#define POLLOUT      0x0004 //可以写入数据
#define POLLERR      0x0008 //调用出错,仅在revents中出现
#define POLLHUP      0x0010 //调用中断,仅在revents中出现
#define POLLNVAL       0x0020 //无效的请求,仅在revents中出现 6.7.2.2、示例

服务器:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_BACKLOG (5)
#define BUF_SIZE (2048)
#define ONCE_READ_SIZE (1500)

#define EPOLL_SIZE (100);
#define MAX_EVENTS (10)

void usage(void) {
    printf("*********************************\n");
    printf("./server 本端ip 本端端口\n");
    printf("*********************************\n");
}

// 当前已有的链接
#define MAXCLINE 5   // 连接队列中的个数
int fd;    // 连接的fd

int conn_amount;   // 此次while循环,有发送过来数据的套接字个数

int main(int argc, char *argv[])
{
    struct sockaddr_in local;      // 服务器端地址
    struct sockaddr_in peer;         // 客户端地址
    socklen_t addrlen = sizeof(peer);
    int sock_fd = 0;               // 服务器端套接字
    int new_fd = 0;                  // 客户端套接字
    int ret = 0;
    char send_buf = {0};   // 发送缓冲区
    char recv_buf = {0};   // 接收缓冲区

    if (argc != 3) {
      usage();
      return -1;
    }

    char *ip = argv;
    unsigned short port = atoi(argv);
    printf("ip:port->%s:%u\n", argv, port);

    // 创建服务器端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    printf("socket: %d\n", sock_fd);
    if (sock_fd == -1) {
      perror("socket error");
      return -1;
    }

    // 初始化服务端地址结构体 包含ip地址、端口号
    memset(&local, 0, sizeof(struct sockaddr_in));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(ip);
    local.sin_port = htons(port);

    // 绑定端口绑定套接字、ip地址、端口号之间的关系
    ret = bind(sock_fd, (struct sockaddr *)&local, sizeof(struct sockaddr));
    if (ret == -1) {
      close(sock_fd);
      perror("bind error");
      return -1;
    }

    // 监听服务器套接字
    ret = listen(sock_fd, LISTEN_BACKLOG);
    if (ret == -1) {
      close(sock_fd);
      perror("listen error");
      return -1;
    }

    //文件描述符集合的定义
    /*
      poll只是对select函数传入的参数的一个封装,底层实现还是select,好处就是需要传入的参数少了
    */
    // pollfd数组, 一个元素为一个 描述符以及相对应的事件。元素的个数为可处理socket的最大个数,突破了select的1024的限制
    pollfd pfds = {0};
    pfds.fd = sock_fd;
    pfds.events = POLLIN;// 可读时间

    int maxsock = sock_fd;

    //将当前已有的连接全部加到这个这个集合中,可以监测客户端是否有数据到来
    for(int i = 0; i < MAXCLINE; i++)
    {
      if(fd != 0)
      {
            pfds].fd = fd;
            pfds].events = POLLIN;
            maxsock = maxsock > fd ? maxsock : fd;
      }
    }

    struct timeval tv;
    while (1) {
      // 等待事件发生
      ret = poll(pfds, maxsock + 1, -1);
      
      // 循环遍历所有的连接,检测是否有数据到来【必要流程】
      for (int i = 1; i <= maxsock; i++)
      {
            if (pfds.revents & POLLIN)
            {
                int clientfd = accept(pfds.fd, (sockaddr*)&peer, &addrlen);
                printf("accept client[%d %d]\n", i, clientfd);
                if (clientfd < 0)
                {
                  continue;
                }
                pfds.fd = clientfd;
                pfds.events = POLLIN;
                maxsock = maxsock > clientfd ? maxsock : clientfd;
            }
      }

      // 接受数据 遍历【必要流程】
      for (int i = 1; i <= maxsock; i++)
      {
            if (pfds.revents & POLLIN)
            {
                ret = recv(pfds.fd, recv_buf, sizeof(recv_buf), 0);
                if(ret <= 0) // 客户端连接关闭,清除文件描述符集中的相应的位
                {
                  printf("client[%d] close\n", i);
                  close(fd);
                  pfds.fd = 0;
                  pfds.events = 0;
                  continue;
                                } else {
                  // 否则有相应的数据发送过来 ,进行相应的处理
                  if(ret < BUF_SIZE)
                        memset(&recv_buf, '\0', 1);
                  printf("client[%d] send:%s\n", i, recv_buf);
                }
                        }
                }
    }

    return 0;
}
#
# ./socket-poll-server 127.0.0.1 3000
ip:port->127.0.0.1:3000
socket: 3
accept client
client close
accept client
client send:1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
accept client
client send:2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
accept client
client send:3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
accept client
client send:4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
accept client
client send:5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
accept client
client send:6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
accept client
client send:7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777
accept client
client send:8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
accept client
client send:9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
accept client
client send:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
accept client
client send:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
accept client
client send:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
accept client
client send:====================================================================================================
accept client
client send:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
accept client
client send:????????????????????????????????????????????????????????????????????????????????????????????????????
accept client
client send:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
^Xaccept client
client send:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
accept client
client send:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
^Caccept client
client send:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
accept client
client send:DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
accept client
client close
#
# 客户端:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_BACKLOG (5)
#define BUF_SIZE (100)

#define REQUEST_STR "tcp pack"

void usage(void) {
    printf("*********************************\n");
    printf("./client 对端ip 对端端口\n");
    printf("*********************************\n");
}

int main(int argc, char *argv[])
{
    struct sockaddr_in client;   // 客户端地址
    struct sockaddr_in server;   // 服务器地址
    int sock_fd = 0;
    int ret = 0;
    socklen_t addrlen = 0;
    char send_buf = {0};
    char recv_buf = {0};

    if (argc != 3) {
      usage();
      return -1;
    }

    char *ip = argv;
    unsigned short port = atoi(argv);
    printf("ip:port->%s:%u\n", argv, port);

    // 创建客户端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
      perror("socket error");
      return -1;
    }
    printf("sock_fd: %d\n", sock_fd);

    memset(&server, 0, sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip);
    server.sin_port = htons(port);

    ret = connect(sock_fd, (struct sockaddr *)&server, sizeof(struct sockaddr));
    if (ret == -1) {
      close(sock_fd);
      perror("connect error");
      return -1;
    }

    char seq = 0x31;
    while(1) {
      memset(send_buf, seq, BUF_SIZE);
      send(sock_fd, send_buf, BUF_SIZE, 0);
      printf("send %s\n", send_buf);
      sleep(2);
      seq++;
    }
   
    close(sock_fd);

    return 0;
}
​​​​​​​
#
# ./socket-epoll-client 127.0.0.1 3000
ip:port->127.0.0.1:3000
sock_fd: 3
send 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
send 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
send 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
send 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
send 5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
send 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
send 7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777
send 8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
send 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
send ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
send ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
send <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
send ====================================================================================================
send >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
send ????????????????????????????????????????????????????????????????????????????????????????????????????
send @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
send AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
send BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
send CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
send DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
#
#
6.7.3、epoll【*****】

        epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的加强版本,它能显著提高程序在大量并发毗连中只有少量生动的情况下的体系CPU利用率。
优势:
        (1)无穷定的文件描述符数目:epoll所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,在1GB内存的机器上约莫是10万左右,具体数目可以通过cat /proc/sys/fs/file-max检察。
        (2)高效的IO事件处理:epoll通过内核与用户空间共享一个事件表,当文件描述符的状态发生变革时,内核会将这个事件通知给用户空间,用户空间再根据事件范例进行相应的处理。这种方式避免了select/poll的轮询机制,提高了效率。
        (3)支持两种触发模式:epoll除了提供select/poll那种IO事件的程度触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
6.7.3.1、函数原型

函数原型:
(1) 创建一个epoll实例,并返回一个文件描述符,用于后续的epoll操作。这个文件描述符用于引用epoll实例
    #include <sys/epoll.h>
    int epoll_create(int size);

参数说明:
    size   表示内核需要监控的最大数量。然而,这个参数在现代Linux内核中已经被忽略,只需要传入一个大于0的值即可。

返回值说明:
    成功时返回一个新的文件描述符。
    失败时返回-1,并设置errno以指示错误。


(2) 用于向epoll实例中注册、修改或删除一个文件描述符(如socket)及其事件。
    #include <sys/epoll.h>
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数说明:
    epfd   由epoll_create返回的文件描述符。
    op       指定要执行的操作,常见的操作有EPOLL_CTL_ADD(添加)、EPOLL_CTL_DEL(删除)和EPOLL_CTL_MOD(修改)。
    fd       要注册、修改或删除的文件描述符。
    event    指向epoll_event结构体的指针,用于指定事件类型和用户数据。

返回值说明:
    成功时返回0。
    失败时返回-1,并设置errno以指示错误。

结构体struct epoll_event说明:
    #include <sys/epoll.h>
    struct epoll_event{
      uint32_t events;    // epoll事件,参考事件列表
      epoll_data_t data;
    } ;
    typedef union epoll_data {
      void *ptr;
      int fd;         // 套接字文件描述符
      uint32_t u32;
      uint64_t u64;
    } epoll_data_t;

epoll事件说明:
    #include <sys/epoll.h>
    enum EPOLL_EVENTS
    {
      EPOLLIN = 0x001, //读事件
      EPOLLPRI = 0x002,
      EPOLLOUT = 0x004, //写事件
      EPOLLRDNORM = 0x040,
      EPOLLRDBAND = 0x080,
      EPOLLWRNORM = 0x100,
      EPOLLWRBAND = 0x200,
      EPOLLMSG = 0x400,
      EPOLLERR = 0x008, //出错事件
      EPOLLHUP = 0x010, //出错事件
      EPOLLRDHUP = 0x2000,
      EPOLLEXCLUSIVE = 1u << 28,
      EPOLLWAKEUP = 1u << 29,
      EPOLLONESHOT = 1u << 30,
      EPOLLET = 1u << 31 //边缘触发
    };


(3) 等待注册在epoll实例上的文件描述符上的事件发生。如果事件发生,则将其复制到用户提供的数组中。
    #include <sys/epoll.h>
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数说明:
    epfd          由epoll_create返回的文件描述符。
    events      指向epoll_event结构体的数组的指针,用于接收发生的事件。【传入内容是空,传出不为空】
    maxevents   指定events数组的大小,即epoll_wait一次能处理的最大事件数。
    timeout       等待事件发生前的超时时间(毫秒)。小于0:一直等待;等于0:立即返回;大于0:等待超时时间返回,单位毫秒。

返回值说明:
    成功时返回发生事件的文件描述符的数量。
    如果没有事件发生且没有超时,则返回0。
    失败时返回-1,并设置errno以指示错误。 6.7.3.2、工作原理

https://i-blog.csdnimg.cn/direct/701b89dd87124b22bf7534b1ff7ade7c.png
(1)程度触发(LT)
工作原理:
        在程度触发模式下,当某个文件描述符上有新的数据可读或可写时,epoll_wait会立即返回并通知应用程序。即使应用程序没有处理完所有的数据,下一次epoll_wait调用仍旧会返回该文件描述符上的事件。
        对于读操作:假如文件描述符上的接收缓冲区中有任何数据可读(不为空),epoll_wait会返回该文件描述符可读的事件。即使应用程序没有读取所有数据,下一次epoll_wait调用仍旧会返回相同的可读事件。
        对于写操作:假如文件描述符上的发送缓冲区有足够的空间可以写入数据,epoll_wait会返回该文件描述符可写的事件。
实用场景:
        程度触发模式实用于范例的轮询方式,应用程序可以重复调用epoll_wait来处理文件描述符上的I/O事件,直到所有事件都被处理完毕。这种模式对于实时性要求不是非常高的应用,如普通的网络服务器或需要周期性处理数据的情况较为实用。
(2)边沿触发(ET)
工作原理:
        在边沿触发模式下,epoll_wait只在文件描述符状态发生变革时才返回,并且只通知应用程序一次。也就是说,只有当文件描述符从无事件变为有事件时,epoll_wait才会返回。
        对于读操作:仅当文件描述符上的接收缓冲区由空变为非空时,epoll_wait才会返回该文件描述符可读的事件。
        对于写操作:仅当文件描述符上的发送缓冲区由满变为非满时,epoll_wait才会返回该文件描述符可写的事件。
        边沿触发模式要求应用程序在接收到事件后立即处理所有可用的数据,由于下一次epoll_wait调用不会返回相同的事件。
实用场景:
        边沿触发模式特别适合处理大量事件和高并发的场景。由于它只在状态变革时通知应用程序,可以减少不须要的上下文切换,提高效率。对于事件响应速度要求较高的应用,如高性能网络服务器,需要快速处理大量毗连或数据的情况,边沿触发模式更为适合。
6.7.3.3、示例

服务器:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_BACKLOG (5)
#define BUF_SIZE (2048)
#define ONCE_READ_SIZE (1500)

#define EPOLL_SIZE (100);
#define MAX_EVENTS (10)

void usage(void) {
    printf("*********************************\n");
    printf("./server 本端ip 本端端口\n");
    printf("*********************************\n");
}

void setnonblocking(int fd) {
    int flag = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}

int main(int argc, char *argv[])
{
    struct sockaddr_in local;      // 服务器端地址
    struct sockaddr_in peer;         // 客户端地址
    socklen_t addrlen = sizeof(peer);
    int sock_fd = 0;               // 服务器端套接字
    int new_fd = 0;                  // 客户端套接字
    int ret = 0;
    char send_buf = {0};   // 发送缓冲区
    char recv_buf = {0};   // 接收缓冲区

    if (argc != 3) {
      usage();
      return -1;
    }

    char *ip = argv;
    unsigned short port = atoi(argv);
    printf("ip:port->%s:%u\n", argv, port);

    // 创建服务器端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    printf("socket: %d\n", sock_fd);
    if (sock_fd == -1) {
      perror("socket error");
      return -1;
    }

    // 初始化服务端地址结构体 包含ip地址、端口号
    memset(&local, 0, sizeof(struct sockaddr_in));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(ip);
    local.sin_port = htons(port);

    // 绑定端口绑定套接字、ip地址、端口号之间的关系
    ret = bind(sock_fd, (struct sockaddr *)&local, sizeof(struct sockaddr));
    if (ret == -1) {
      close(sock_fd);
      perror("bind error");
      return -1;
    }

    // 监听服务器套接字
    ret = listen(sock_fd, LISTEN_BACKLOG);
    if (ret == -1) {
      close(sock_fd);
      perror("listen error");
      return -1;
    }

    // 创建epoll实例
    int epoll_size = EPOLL_SIZE;
    int efd = epoll_create(epoll_size);
    if (efd == -1) {
      perror("epoll create error");
      return -1;
    }

    // 定义 epoll文件描述符
    struct epoll_event ev;
    // 将服务器套接字添加到epoll监控列表中
    ev.data.fd = sock_fd;
    ev.events = EPOLLIN;
    if (epoll_ctl(efd, EPOLL_CTL_ADD, sock_fd, &ev) == -1) {
      perror("epoll ctl ADD error");
      return -1;
    }
   
    // 定义 epoll事件结构体数组
    struct epoll_event events;
    int timeout = 1000;
    while (1) {
      // 等待epoll事件
      int nfds = epoll_wait(efd, events, MAX_EVENTS, timeout);
      if (nfds == -1) {
            perror("epoll wait error");
            return -1;
      } else if (nfds == 0) {
            printf("epoll wait timeout\n");
            continue;
      } else {

      }

      // 遍历所有触发的epoll事件
      for (int i = 0; i < nfds; i++) {
            int fd = events.data.fd;
            printf("events[%d] events:%08x %d %d\n", i, events.events, fd, sock_fd);
            if (fd == sock_fd) {
                // 处理服务器套接字上的事件,即新的客户端连接
                new_fd = accept(sock_fd, (struct sockaddr *)&peer, &addrlen);
                if (new_fd == -1) {
                  perror("accept error");
                  continue;
                }
                setnonblocking(new_fd);
                ev.data.fd = new_fd;
                ev.events = EPOLLIN|EPOLLET;
                if (epoll_ctl(efd, EPOLL_CTL_ADD, new_fd, &ev) == -1) {
                  perror("epoll ctl ADD new fd error");
                  close(new_fd);
                  continue;
                }
            } else {
                // 处理客户端套接字上的事件,即客户端发送的数据
                if (events.events & EPOLLIN) {
                  printf("fd:%d is readable\n", fd);
                  memset(recv_buf, 0, BUF_SIZE);
                  unsigned int len = 0;
                  while(1) {
                        ret = recv(fd, recv_buf + len, ONCE_READ_SIZE, 0);
                        if (ret ==0) {
                            printf("remove fd:%d\n", fd);
                            epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL);
                            close(fd);
                            break;
                        } else if ((ret == -1) && ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))) {
                            printf("fd:%d recv errno:%d done\n", fd, errno);
                            break;
                        } else if ((ret == -1) && !((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))) {
                            printf("remove fd:%d errno:%d\n", fd, errno);
                            epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL);
                            close(fd);
                            break;
                        } else {
                            printf("once read ret:%d\n", ret);
                            len += ret;
                        }
                  }
                  printf("recv fd:%d, len:%d, %s\n", fd, len, recv_buf);
                } if (events.events & EPOLLOUT) {
                  printf("fd:%d is sendable\n", fd);
                } else if ((events.events & EPOLLERR) ||
                        ((events.events & EPOLLHUP))) {
                  printf("fd:%d error\n", fd);
                }
            }
      }
    }

    return 0;
}
#
# ./socket-epoll-server 127.0.0.1 5000
ip:port->127.0.0.1:5000
epoll wait timeout
epoll wait timeout
epoll wait timeout
events events:00000001 3 3
events events:00000001 5 3
fd:5 is readable
once read ret:100
fd:5 recv errno:11 done
recv fd:5, len:100, 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
epoll wait timeout
events events:00000001 5 3
fd:5 is readable
once read ret:100
fd:5 recv errno:11 done
recv fd:5, len:100, 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
epoll wait timeout
events events:00000001 5 3
fd:5 is readable
once read ret:100
fd:5 recv errno:11 done
recv fd:5, len:100, 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
epoll wait timeout
events events:00000001 5 3
fd:5 is readable
once read ret:100
fd:5 recv errno:11 done
recv fd:5, len:100, 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
epoll wait timeout
events events:00000001 5 3
fd:5 is readable
once read ret:100
fd:5 recv errno:11 done
recv fd:5, len:100, 5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
epoll wait timeout
events events:00000001 5 3
fd:5 is readable
once read ret:100
fd:5 recv errno:11 done
recv fd:5, len:100, 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
epoll wait timeout
#
# 客户端:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LISTEN_BACKLOG (5)
#define BUF_SIZE (100)

#define REQUEST_STR "tcp pack"

void usage(void) {
    printf("*********************************\n");
    printf("./client 对端ip 对端端口\n");
    printf("*********************************\n");
}

int main(int argc, char *argv[])
{
    struct sockaddr_in client;   // 客户端地址
    struct sockaddr_in server;   // 服务器地址
    int sock_fd = 0;
    int ret = 0;
    socklen_t addrlen = 0;
    char send_buf = {0};
    char recv_buf = {0};

    if (argc != 3) {
      usage();
      return -1;
    }

    char *ip = argv;
    unsigned short port = atoi(argv);
    printf("ip:port->%s:%u\n", argv, port);

    // 创建客户端套接字
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
      perror("socket error");
      return -1;
    }
    printf("sock_fd: %d\n", sock_fd);

    memset(&server, 0, sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip);
    server.sin_port = htons(port);

    ret = connect(sock_fd, (struct sockaddr *)&server, sizeof(struct sockaddr));
    if (ret == -1) {
      close(sock_fd);
      perror("connect error");
      return -1;
    }

    char seq = 0x31;
    while(1) {
      memset(send_buf, seq, BUF_SIZE);
      send(sock_fd, send_buf, BUF_SIZE, 0);
      printf("send %s\n", send_buf);
      sleep(2);
      seq++;
    }
   
    close(sock_fd);

    return 0;
}
#
# ./socket-epoll-client127.0.0.1 5000
ip:port->127.0.0.1:5000
sock_fd: 3
send 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
send 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
send 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
send 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444
send 5555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
send 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
send 7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777
#
#
6.8、套接字

        套接字(Socket)是盘算机网络通信中的一个基本概念,它是一种抽象的数据结构,用于在网络应用程序之间提供通信接口。套接字可以看作是一个端点,用于发送和接收数据,使得运行在不同机器上的应用程序可以或许互换信息,从而实现网络功能。
        Linux套接字(Socket)是盘算机网络中用于实现进程间通信的一种机制,它提供了一种标准化的方式,使得应用程序可以或许通过网络毗连进行相互之间的通信。
        网络通信的本质:本质上就是一种进程间通信。
6.8.1、端口号

        端口号(port)是传输层协议的内容。端口号是一个2字节16位的整数。端口号用来标识一个进程,告诉操作体系,当前的这个数据要交给哪一个进程来处理。
        IP所在 + 端口号 可以或许标识网络上的某一台主机的某一个进程。
        一个端口号只能被一个进程占用。
        由于端口号是隶属于某台主机的,以是端口号可以在两台不同的主机当中重复,但是在同一台主机上进行网络通信的进程的端口号不能重复。别的,一个进程可以绑定多个端口号,但是一个端口号不能被多个进程同时绑定。
6.8.2、TCP

        TCP协议叫做传输控制协议(Transmission Control Protocol),TCP协议是一种面向毗连的、可靠的、基于字节流的传输层通信协议。
        TCP协议是面向毗连的,假如两台主机之间想要进行数据传输,那么必须要先建立毗连,当毗连建立成功后才气进行数据传输。其次,TCP协议是保证可靠的协议,数据在传输过程中假如出现了丢包、乱序等情况,TCP协议都有对应的解决方法。
特点:
        传输层协议
        有毗连
        可靠传输
        面向字节流
6.8.3、UDP

        UDP协议叫做用户数据报协议(User Datagram Protocol),UDP协议是一种无需建立毗连的、不可靠的、面向数据报的传输层通信协议。
        利用UDP协议进行通信时无需建立毗连,假如两台主机之间想要进行数据传输,那么直接将数据发送给对端主机就行了,但这也就意味着UDP协议是不可靠的,数据在传输过程中假如出现了丢包、乱序等情况,UDP协议自己是不知道的。
特点:
        传输层协议
        无毗连
        不可靠传输
        面向数据报
6.8.4、网络字节序

        内存中的多字节数据相对于内存所在有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移所在也有大端小端之分,网络数据流同样有大端小端之分。
        (1)大端存储:低字节存放在高所在,高字节存放在低所在。
        (2)小端存储:低字节存放在低所在,高字节存放在高所在。
网络字节序与主机字节序之间的转换:
#include <arpa/inet.h>

主机字节序转换为网络字节序【大端】----转换4字节的IP地址
uint32_t htonl(uint32_t hostlong);

主机字节序转换为网络字节序----转换2字节的端口号
uint16_t htons(uint16_t hostshort);

网络字节序【大端】转换为主机字节序 -----转换4字节的IP地址
uint32_t ntohl(uint32_t netlong);

网络字节序【大端】转换为主机字节序 -----转换2字节的端口号
uint16_t ntohs(uint16_t netshort);
6.8.5、socket

(1)常见接口
Linux socket函数说明:
(1) 创建socket文件描述符(TCP/UDP, 客户端+服务器)
int socket(int domain, int type, int protocol);

参数说明:
        domain   IP地址类型; 常用:AF_INET, AF_INET6
        type       套接字类型
                           SOCK_STREAM:它提供基于字节流的有序、可靠、双向连接可以支持带外数据传输机制。【流式套接字------用于TCP通信】
                           SOCK_DGRAM:支持数据报(固定最大值的无连接、不可靠消息长度)。【报文套接字----UDP通信】
                           SOCK_SEQPACKET
        protocol   默认0

返回值说明:
    成功      返回文件描述符socketid
    失败      -1


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

参数说明:
    socketid    套接字
    address   服务器ip套接字结构体地址
    addrlen   结构体大小
         
返回值说明:
    成功         0
    失败         -1

结构体struct sockaddr说明:
    struct sockaddr {
      sa_family_t sa_family;   // 地址族,如AF_INET表示IPv4,AF_INET6表示IPv6
      char      sa_data;   // 用于存储具体地址信息的可变长度数组,但直接操作它并不推荐
    };
    在实际编程中,通常不会直接使用struct sockaddr,而是使用它的特定类型,如struct sockaddr_in(用于IPv4地址)和 struct sockaddr_in6(用于IPv6地址)。

    struct sockaddr_in {
      sa_family_t    sin_family;    // 地址族,对于IPv4地址,它总是AF_INET
      uint16_t       sin_port;      // 端口号,网络字节顺序
      struct in_addr sin_addr;      // IPv4地址
      // 通常还有以下填充字节,但这里省略了
      // char      sin_zero;
    };   
    // 其中,in_addr的定义是
    struct in_addr {
      uint32_t s_addr; // IPv4地址,网络字节顺序
    };

    struct sockaddr_in6 {
      sa_family_t   sin6_family;   // 地址族,对于IPv6地址,它总是AF_INET6
      uint16_t      sin6_port;   // 端口号,网络字节顺序
      uint32_t      sin6_flowinfo; // IPv6 流标签和流量类别
      struct in6_addr sin6_addr;   // IPv6 地址
      uint32_t      sin6_scope_id; // 范围ID(用于链路本地地址等)
    };
    // 其中,in6_addr的定义是
    struct in6_addr {
      uint8_t s6_addr; // 128位IPv6地址,网络字节顺序
    };


(3) 开始监听socket(TCP, 服务器)
int listen(int socketid, int backlog);

参数说明:
    socketid   套接字
    backlog      已完成连接队列和未完成连接队列数之和的最大值128。一般这个参数为5。

返回值说明:
    成功         0
    失败         -1


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

参数说明:
    socketid   套接字
    address      传入类型参数,获取客户端的IP和端口信息
    address_len结构体大小的地址

返回值说明:
    成功          新的已连接套接字的文件描述符socketid_new
    失败          -1


(5) 建立连接(TCP, 客户端)
int connect(int socketid, const struct sockaddr *addr, socklen_t addrlen);

参数说明:
    socketid   套接字【文件描述符】
    addr         服务器套接字结构体地址(ip地址、端口号)
    addrlen      结构体的长度

返回值说明:
    成功         0
    失败         -1




Linux中的read和write函数说明:
(1) 读取消息
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数说明:
    fd      文件描述符(在socket编程中,它是socket描述符)。
    buf       指向缓冲区的指针,该缓冲区用于存储从文件或socket读取的数据。
    count   请求读取的字节数。

返回值说明:
    成功时,返回读取的字节数(可能小于请求的字节数,特别是在非阻塞模式下或当达到文件末尾时)。
    如果到达文件末尾,则返回0。
    失败时,返回-1,并设置errno以指示错误(如EAGAIN、EINTR、EBADF等)。

(2) 写入消息
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数说明:
    fd      文件描述符(在socket编程中,它是socket描述符)。
    buf       指向包含要写入数据的缓冲区的指针。
    count   要写入的字节数。

返回值说明:
    成功时,返回写入的字节数(可能小于请求的字节数,尤其是在非阻塞模式下)。
    失败时,返回-1,并设置errno以指示错误(如EAGAIN、EINTR、EBADF、EPIPE等)。

(3) 发送消息
send()函数用于在已连接的套接字上发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数说明:
    sockfd      要发送数据的套接字描述符。
    buf         指向包含要发送数据的缓冲区的指针。
    len         要发送数据的字节数。
    flags         控制选项,通常设置为0。

返回值说明:
    成功         返回实际发送的字节数。
    链接关闭      0
    失败         -1

(4) 接受消息
recv()函数用于从已连接的套接字接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数说明:
    sockfd         要接收数据的套接字描述符。
    buf            指向用于存储接收数据的缓冲区的指针。
    len            缓冲区的大小(以字节为单位。
    flags          控制选项,通常设置为0。

返回值说明:
    成功         返回实际接收到的字节数。
    链接关闭      0
    失败         -1
6.8.6、linux socket进程间通信

服务器:
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
      std::cerr << "Socket creation failed" << std::endl;
      return -1;
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(5000);

    // 绑定套接字到指定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
      std::cerr << "Bind failed" << std::endl;
      return -1;
    }

    // 开始监听
    if (listen(server_fd, 5) < 0) {
      std::cerr << "Listen failed" << std::endl;
      return -1;
    }

    // 接受连接
    while (true) {
      if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) >= 0) {
            //std::cerr << "Accept failed" << std::endl;
            break;
      }
    }

    char buffer = {0};
    int valread = read(new_socket, buffer, 1024);
    std::cout << "Received from client: " << buffer << std::endl;

    // 发送响应
    const char *response = "Hello from server";
    send(new_socket, response, strlen(response), 0);

    close(new_socket);
    close(server_fd);

    return 0;
} 客户端:
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      std::cerr << "Socket creation error" << std::endl;
      return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000);

    // 将服务器地址从字符串转换为网络地址
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
      std::cerr << "Invalid address/ Address not supported" << std::endl;
      return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
      std::cerr << "Connection Failed" << std::endl;
      return -1;
    }

    // 发送数据
    const char *message = "Hello from client";
    send(sock, message, strlen(message), 0);

    char buffer = {0};
    int valread = read(sock, buffer, 1024);
    std::cout << "Received from server: " << buffer << std::endl;

    close(sock);

    return 0;
} 运行结果:
# g++ socket-server.cc -o socket-server
# ./socket-server
Received from client: Hello from client
#
#


# g++ socket-client.cc -o socket-client
# ./socket-client
Received from server: Hello from server
#
# 6.8.7、linux socket不同主机间通信

服务器端:
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
      std::cerr << "Socket creation failed" << std::endl;
      return -1;
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(5000);

    // 绑定套接字到指定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
      std::cerr << "Bind failed" << std::endl;
      return -1;
    }

    // 开始监听
    if (listen(server_fd, 5) < 0) {
      std::cerr << "Listen failed" << std::endl;
      return -1;
    }

    // 接受连接
    while (true) {
      if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) >= 0) {
            //std::cerr << "Accept failed" << std::endl;
            break;
      }
    }

    char buffer = {0};
    int valread = read(new_socket, buffer, 1024);
    std::cout << "Received from client: " << buffer << std::endl;

    // 发送响应
    const char *response = "Hello from server";
    send(new_socket, response, strlen(response), 0);

    close(new_socket);
    close(server_fd);

    return 0;
} 客户端:
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      std::cerr << "Socket creation error" << std::endl;
      return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000);

    // 将服务器地址从字符串转换为网络地址
    if (inet_pton(AF_INET, "服务器的IP地址", &serv_addr.sin_addr) <= 0) {
      std::cerr << "Invalid address/ Address not supported" << std::endl;
      return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
      std::cerr << "Connection Failed" << std::endl;
      return -1;
    }

    // 发送数据
    const char *message = "Hello from client";
    send(sock, message, strlen(message), 0);

    char buffer = {0};
    int valread = read(sock, buffer, 1024);
    std::cout << "Received from server: " << buffer << std::endl;

    close(sock);

    return 0;
}
6.9、socket和epoll联合利用





标题:

1、标题:进程PID、端口号的区别:
        进程ID(PID)是用来标识体系内所有进程的唯一性的,它是属于体系级的概念。
        端口号(port)是用来标识需要对外进行网络数据请求的进程的唯一性的,它是属于网络的概念。
        一台机器上可能会有大量的进程,但并不是所有的进程都要进行网络通信,可能有很大一部门的进程是不需要进行网络通信的本地进程,此时PID虽然也可以标识这些网络进程的唯一性,但在该场景下就不太符合了。
        Port和进程ID反映了一个进程利用的不同场景,在同一主机下利用进程ID标识一个进程,在网络通信中利用Port标识一个进程。

2、在 Linux 中,Socket 服务器和客户端上的套接字有以下一些不同之处:
角色和功能:
        (1)​​​​​​​服务器套接字主要用于监听来自客户端的毗连请求,并在担当毗连后与客户端进行通信。
        (2)客户端套接字用于主动发起毗连请求,以与服务器建立通信。
绑定和监听:
​​​​​​​        (1)服务器套接字通常需要绑定到一个特定的所在和端口,以便客户端可以或许找到它。然后,服务器会调用 listen 函数开始监听毗连请求。
        (2)客户端套接字一般不需要绑定,而是在发起毗连时指定服务器的所在和端口。
毗连建立:
​​​​​​​        (1)服务器通过 accept 函数担当客户端的毗连请求,从而创建一个新的与客户端通信的套接字。
        (2)客户端利用 connect 函数向服务器发起毗连请求。
所在利用:
        (1)服务器套接字通常利用一个众所周知的、固定的所在和端口,以便客户端可以或许预期并找到它。
        (2)客户端套接字在毗连时利用服务器的所在和端口。
并发处理:
        (1)​​​​​​​服务器可能需要同时处理多个客户端的毗连,可能会采用多线程、多进程或异步 I/O 等方式来实现并发处理。
        (2)客户端通常只与一个服务器进行通信,不需要处理多个并发毗连。
生命周期:
        (1)​​​​​​​服务器套接字在服务器运行期间通常一直存在,连续监听毗连。
        (1)客户端套接字在与服务器完成通信或出现错误后可能会关闭。
总的来说,服务器套接字和客户端套接字在利用方式、功能和在通信过程中的角色上存在差异,以适应它们在网络通信中不同的职责。




参考:

炫酷的伊莉娜-CSDN博客
epoll原理分析,图文并茂讲解epoll,彻底弄懂epoll机制-CSDN博客
linux select函数解析以及示例_linux select实例-CSDN博客



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C/C++开发---全篇