【Linux Day16 I/O复用】

news2024/12/25 16:06:47

I/O复用

用途:I/O 复用能同时监听多个文件描述符。

I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一个文件描述符,这使得服务器看起来好像是串行工作 的。

如果要提高并发处理的能力,可以配合使用多线程或多进程等编程方法。

在Linux下,有三种系统调用函数,分别是select,poll,epoll。

1.select

用途:在一段指定时间内,监听用户感兴趣的文件描述符的可读、可写和异常等事件。

1.接口介绍:

1.int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

1.select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就 绪,select 将返回 0。select 失败是返回-1.如果在 select 等待期间,程序接收到信号,则 select 立即返 回-1,并设置 errno 为 EINTR。

2.maxfd 参数:指定的被监听的文件描述符的总数。它通常被设置为 select 监听的所 有文件描述符中的最大值+1 。

3.readfds、writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件 描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件 描述符。select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。

​ 通过下列宏可以访问 fd_set 结构中的位:

  • ​ FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位
  • ​ FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fd
  • ​ FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd

int FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置

4.timeout 参数:用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指 针,采用指针参数是因为 内核将修改它以告诉应用程序 select 等待了多久。timeval 结构的定义如下:

​ struct timeval {

​ long tv_sec; //秒数

​ long tv_usec; // 微秒数

​ };

​ 如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递 NULL,则 select 将一直阻 塞,直到某个文件描述符就绪 。

2. fd_set 是用来存放描述符的对应事件的集合。

在这里插入图片描述

2.例子

1.用select监听键盘是否有数据输入

#include <stdio.h>
#include <sys/select.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
int main()
{
    char buff[256] = {0};
    fd_set fdset;
 
    while (1)
    {
        FD_ZERO(&fdset);
        FD_SET(0, &fdset);
        struct timeval tim = {5,0};  //每次都需要传入一个新的timeval结构体
        int n = select(1,&fdset,NULL,NULL,&tim);//select中会改变tim的值
        if(-1 == n)
        {
            printf("input error\n");
            continue;
        }
        else if(0 == n)
        {
            printf("timeout\n");
            continue;
        }
        else 
        {
            if(FD_ISSET(0,&fdset))
            {
                memset(buff,0,256);
                read(0,buff,255);
                printf("read:%s\n",buff);
            }
        }
    }
}

运行结果:

在这里插入图片描述

2.用select处理多个并发客户端

原理图:

在这里插入图片描述

服务端参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MaxLen 60
void fds_init(int fds[]) //初始化
{
    for (int i = 0; i < MaxLen; ++i)
    {
        fds[i] = -1;  // 无效的描述符
    }
}
void fds_add(int fd, int fds[]) //添加描述符
{
    for (int i = 0; i < MaxLen; ++i)
    {
        if (-1 == fds[i])
        {
            fds[i] = fd;
            break;
        }
    }
}
void fds_del(const int fd, int fds[]) //删除描述符
{
    for (int i = 0; i < MaxLen; ++i)
    {
        if (fd == fds[i])
        {
            fds[i] = -1;
            break;
        }
    }
}
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        exit(1);
    }
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);                   // htons 将主机字节序转换为网络字节
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 回环地址
    int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == res)
    {
        exit(1);
    }
    res = listen(sockfd, 5);
    if (-1 == res)
    {
        exit(1);
    }

    int fds[MaxLen];  // 存放套结字
    fds_init(fds);    //初始化
    fds_add(sockfd, fds); //加入本地服务器的套结字

    fd_set fdset;
    while (1)
    {
        FD_ZERO(&fdset);
        int maxfd = -1;
        for (int i = 0; i < MaxLen; ++i) // 将所有已经建立连的接描述符写入fdset
        {
            if (-1 != fds[i])
            {
                FD_SET(fds[i], &fdset);
                maxfd = maxfd > fds[i] ? maxfd : fds[i];  //记录文件描述符最大值
            }
        }

        struct timeval tim = {5, 0};
        int n = select(maxfd + 1, &fdset, NULL, NULL, &tim);
        if (-1 == n)
        {
            printf("error\n");
            continue;
        }
        else if (0 == n)
        {
            printf("timeout\n");
            continue;
        }
        else
        {
            for (int i = 0; i < MaxLen; ++i)
            {
                if (-1 == fds[i])
                {
                    continue;
                }
                if (FD_ISSET(fds[i], &fdset))
                {
                    if (fds[i] == sockfd)
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(sockfd, (struct sockaddr *)&caddr, &len);
                        if (-1 == c)
                        {
                            continue;
                        }
                        printf("c:%d\n", c);
                        fds_add(c, fds); //建立连接,保存
                    }
                    else
                    {
                        char buff[256] = {0};
                        int n = recv(fds[i], buff, 255, 0);
                        if (0 >= n)
                        {
                            fds_del(fds[i], fds); //断开连接,清除
                            close(fds[i]);
                            printf("one client close");
                        }
                        else
                        {
                            printf("client[%d]:%s\n", fds[i], buff);
                            send(fds[i], "ok", 2, 0);
                        }
                    }
                }
            }
        }
    }
}

客户端参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(sockfd != -1);
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == res)
    { 
        exit(1);
    }
    while (1)
    {
        char buff[128] = {0};
        printf("input:\n");
        fgets(buff, 128, stdin); // 会取得\n
        if (strncmp(buff, "end", 3) == 0)
        {
            break;
        }
        send(sockfd, buff, strlen(buff), 0);
        memset(buff, 0, 128);
        recv(sockfd, buff, 127, 0);
        printf("buff=%s\n", buff);
    }
    close(sockfd);
    exit(0);
}

运行结果:

在这里插入图片描述

2.poll

用途:在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪的 。

1.接口介绍:
1.int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll 系统调用成功返回就绪文件描述符的总数,超时返回 0,失败返回-1

nfds 参数:指定被监听事件集合 fds 的大小。

timeout 参数:指定 poll 的超时值,单位是毫秒,timeout 为-1 时,poll 调用将永久 阻塞,直到某个事件发生,timeout 为 0 时,poll 调用将立即返回。

fds 参数:是一个 struct pollfd 结构类型的数组,它指定所有用户感兴趣的文件描述 符上发生的可读、可写和异常等事件。

2.pollfd 结构体

定义如下:

struct pollfd

{

int fd; // 文件描述符

short events; // 注册的关注事件类型

short revents; // 实际发生的事件类型,由内核填充

};

其中,fd 成员指定文件描述符,events 成员告诉 poll 监听 fd 上的哪些事件类型。 它是一系列事件的按位或,revents 成员则由内核修改,通知应用程序 fd 上实际发生了哪些事件。

3.poll支持的事件:

在这里插入图片描述

2.例子

poll和select的原理相似;此处不再赘述。

本地服务器端参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <poll.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>

#define MaxLen 60

void fds_init(struct pollfd fds[])
{
    for (int i = 0; i < MaxLen; ++i)
    {
        fds[i].fd = -1;
        fds[i].events = 0;
        fds[i].revents = 0;
    }
}
void fds_add(struct pollfd fds[], int fd)
{
    for (int i = 0; i < MaxLen; ++i)
    {
        if (-1 == fds[i].fd)
        {
            fds[i].fd = fd;
            fds[i].events = POLLIN; // 读事件
            fds[i].revents = 0;
            break;
        }
    }
}
void fds_del(struct pollfd fds[], int fd)
{
    for (int i = 0; i < MaxLen; ++i)
    {
        if (fd == fds[i].fd)
        {
            fds[i].fd = -1;
            fds[i].events = 0;
            fds[i].revents = 0;
            break;
        }
    }
}
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        exit(1);
    }
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);                   // htons 将主机字节序转换为网络字节
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 回环地址
    int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == res)
    {
        exit(1);
    }
    res = listen(sockfd, 5);
    if (-1 == res)
    {
        exit(1);
    }

    struct pollfd fds[MaxLen]; //存放套接字描述符
    fds_init(fds);
    fds_add(fds, sockfd);

    while (true)
    {
        int n = poll(fds, MaxLen, 6000); // 阻塞
        if (-1 == n)
        {
            continue;
        }
        else if (0 == n)
        {
            printf("time out\n");
            continue;
        }
        else
        {
            for (int i = 0; i < MaxLen; ++i)
            {
                if (-1 == fds[i].fd)
                {
                    continue;
                }
                if (fds->revents & POLLIN)
                {
                    if (sockfd == fds[i].fd)
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(sockfd, (struct sockaddr *)&caddr, &len);
                        if (-1 == c)
                        {
                            continue;
                        }
                        printf("c:%d\n", c);
                        fds_add(c,fds);
                    }
                    else
                    {
                        char buff[256] = {0};
                        int n = recv(fds[i].fd, buff, 255, 0);
                        if (0 >= n)
                        {
                            fds_del(fds[i].fd, fds); //断开连接,清除
                            close(fds[i].fd);
                            printf("one client close"); 
                        }
                        else
                        {
                            printf("client[%d]:%s\n", fds[i], buff);
                            send(fds[i].fd, "ok", 2, 0);
                        }
                    }
                }
            }
        }
    }
}

epoll

  1. epoll 是 Linux 特有的 I/O 复用函数。
  2. epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中。从而无需像 select 和 poll 那样每次调用都要重复传入文件描述符或事件集。但 epoll 需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。

1.接口介绍:

1.epoll_create() 用于创建内核事件表

原型:int epoll_create(int size);

epoll_create()成功返回内核事件表的文件描述符,失败返回-1

size 参数:现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。

时间表实际上是一颗红黑树

2.epoll_ctl()用于操作内核事件表

原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll_ctl()成功返回 0,失败返回-1

epfd 参数:指定要操作的内核事件表的文件描述符

fd 参数:指定要操作的文件描述符

op 参数:指定操作类型:

  • EPOLL_CTL_ADD 往内核事件表中中添加一个文件描述符(即参数fd),指定监视的事件类型(参数event)
  • EPOLL_CTL_MOD 修改内核事件表中已经存在的描述符(即参数fd)对应的监视事件类型(参数event)
  • EPOLL_CTL_DEL 将某内核事件表中已经存在的描述符(即参数fd)删除,参数event传NULL

event 需要epoll监视的fd对应的事件类型,它是 epoll_event 结构指针类型,epoll_event 的定义如下:

struct epoll_event

{

_uint32_t events; // epoll 事件

epoll_data_t data; // 用户数据

};

其中,events 成员描述事件类型,epoll 支持的事件类型与 poll 基本相同,表示 epoll 事件的宏是在 poll 对应的宏前加上‘E’,比如 epoll 的数据可读事件是 EPOLLIN。但是 epoll 有两个额外的事件类型–EPOLLET 和 EPOLLONESHOT。 data 成员用于存储用户数据,是一个联合体,其定义如下:

typedef union epoll_data

{

void *ptr;

int fd;

uint32_t u32;

uint64_t u64;

}epoll_data_t;

其中 fd 成员使用的最多,它指定事件所从属的目标文件描述符。

3.epoll_wait()用于在一段超时时间内等待一组文件描述符上的事件

原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll_wait()成功返回需要处理的事件数目,失败返回-1,超时返回 0

epfd 参数:指定要操作的内核事件表的文件描述符

events 参数:接口的返回参数;是一个用户数组,epoll把发生的事件的集合从内核复制到 events数组中。events数组是一个用户分配好大小的数组,数组长度大于等于maxevents。(events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存)

maxevents 参数:指定用户数组的大小,即指定最多监听多少个事件,它必须大于 0

timeout 参数:指定超时时间,单位为毫秒,如果 timeout 为 0,则 epoll_wait 会立即 返回,如果 timeout 为-1,则 epoll_wait 会一直阻塞,直到有事件就绪; 0表示不阻塞。

2.例子

本地服务器端参考代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>

#define MaxLen 20
void epoll_add(int epfd, int fd, int op)
{
    struct epoll_event ep;
    ep.events = op;
    ep.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ep) == -1)
    {
        printf("Add fd error\n");
    }
}
void epoll_del(int epfd, int fd)
{
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1)
    {
        printf("Del fd error\n");
    }
}
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        exit(1);
    }
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);                   // htons 将主机字节序转换为网络字节
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 回环地址
    int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == res)
    {
        exit(1);
    }
    res = listen(sockfd, 5);
    if (-1 == res)
    {
        exit(1);
    }
    int epfd = epoll_create(MaxLen); // 内核事件表的文件描述符
    if (-1 == epfd)
    {
        exit(1);
    }
    struct epoll_event evs[MaxLen]; // 存放就绪的文件描述符
    epoll_add(epfd,sockfd,EPOLL_CTL_ADD);
    while (true)
    {
        int n = epoll_wait(epfd, evs, MaxLen, 5000);
        if (-1 == n)
        {
            continue;
        }
        else if (0 == n)
        {
            printf("time out\n");
            continue;
        }
        else
        {
            for (int i = 0; i < n; ++i) // 区别于select和poll
            {
                int fd = evs[i].data.fd;
                if (evs[i].events & EPOLLIN) //读事件发生
                {
                    if (sockfd == fd)
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(fd, (struct sockaddr *)&caddr, &len);
                        if (0 >= c)
                        {
                            continue;
                        }
                        printf("accept client:%d",c);
                        epoll_add(epfd, c, EPOLL_CTL_ADD);
                    }
                    else
                    {
                        char buff[256] = {0};
                        int c = recv(fd, buff, 255, 0);
                        if (0 >= c)
                        {
                            printf("one client close\n");
                            epoll_del(epfd, fd);
                            close(fd);
                            continue;
                        }
                        printf("client(%d):%s\n", fd, buff);
                        send(fd, "OK", 2, 0);
                    }
                }
            }
        }
    }
}

3.LT 和 ET 模式

1.LT模式

以读事件为例,当缓冲区有数据准备好的时候,此时会触发读事件,**如果我们一直不去读取缓冲区里的数据,epoll模型就会一直通知我们有事件就绪。**LT模式也是epoll模型的默认模式。

2.ET模式

对于读事件 EPOLLIN,当该描述符对应的接受缓冲区的数据准备好的时候,也会触发读事件,但是只会触发一次,如果我们这次没有调用read/recv 读取 或者 没有一次读完,后面就不会通知有读事件就绪了。简单来说,只有当该描述符对应的接受缓冲区里的数据量发生变化的时候,才会通知我们一次,不会像LT模式那样一直通知。

示例图:

在这里插入图片描述
在这里插入图片描述

3.ET模式下的非阻塞编程:
阻塞状态下epoll存在问题:

只有接收缓冲区的数据变化时才会通知,通知的次数少了自然也会引发一些问题,比如触发读事件后必须把数据收取干净,因为你不一定有下一次机会再收取数据了,即使不采用一次读取干净的方式,也要把这个激活状态记下来,后续接着处理,否则如果数据残留到下一次消息来到时就会造成延迟现象。

解决方法:
1.为避免接收缓冲区中的数据一次取不完,我们采用循环来将缓冲区读取干净,当recv发现缓冲区里没有数据了,此时会默认进入阻塞状态,等待数据就绪,这就严重影响到后面的文件描述符读取/写入内容了。所以ET模式下必须要设为非阻塞。
2.当非阻塞状态下时,当缓冲区没有数据时,会返回error,并且将全局变量( errno ) 置为 EAGAIN 或 EWOULDBLOCK;
置为非阻塞的两种方法:
1.fnctl函数设定
    int flag = fcntl(client_fd, F_GETFL);
    flag |= O_NONBLOCK;
    int ret = fcntl(client_fd, F_SETFL, flag);
    if (ret == -1)
    {
        printf("Set Wait error\n");
    }
2.recv的参数设定
int c = recv(fd,buff,1,MSG_DONTWAIT);
if(0 >= c)
{
  if(errno != EAGAIN && errno != EWOULDBLOCK ) 
  {
    epoll_del(epfd,fd);
    close(fd);
    printf("one client close\n");
  }
}
4.例子

本地服务器端参考代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <fcntl.h> //新增
#include <errno.h> //新增

#define MaxLen 20
void set_NoWait(int fd)
{
    int oldfl = fcntl(fd,F_GETFL);  //获取描述符的状态
    int newfl = oldfl | O_NONBLOCK;
    int n = fcntl(fd,F_SETFL,newfl); //新增非阻塞状态
    if(-1 == n)
    {
        printf("Set NoWait error\n");
    }
}
void epoll_add(int epfd, int fd, int op)
{
    struct epoll_event ep;
    ep.events = op | EPOLLET; //开启ET模式
    ep.data.fd = fd;
    set_NoWait(fd); //置为非阻塞
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ep) == -1)
    {
        printf("Add fd error\n");
    }
}
void epoll_del(int epfd, int fd)
{
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1)
    {
        printf("Del fd error\n");
    }
}
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        exit(1);
    }
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);                   // htons 将主机字节序转换为网络字节
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 回环地址
    int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == res)
    {
        exit(1);
    }
    res = listen(sockfd, 5);
    if (-1 == res)
    {
        exit(1);
    }
    int epfd = epoll_create(MaxLen); // 内核事件表的文件描述符
    if (-1 == epfd)
    {
        exit(1);
    }
    struct epoll_event evs[MaxLen]; // 存放就绪的文件描述符
    epoll_add(epfd,sockfd,EPOLL_CTL_ADD);
    while (true)
    {
        int n = epoll_wait(epfd, evs, MaxLen, 5000);
        if (-1 == n)
        {
            continue;
        }
        else if (0 == n)
        {
            printf("time out\n");
            continue;
        }
        else
        {
            for (int i = 0; i < n; ++i) // 区别于select和poll
            {
                int fd = evs[i].data.fd;
                if (evs[i].events & EPOLLIN) //读事件发生
                {
                    if (sockfd == fd)
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(fd, (struct sockaddr *)&caddr, &len);
                        if (0 >= c)
                        {
                            continue;
                        }
                        printf("accept client:%d",c);
                        epoll_add(epfd, c, EPOLL_CTL_ADD);
                    }
                    else
                    {
                        char buff[256] = {0};
                        while(true) //采用循环清空接受缓冲区
                        {
                            int c = recv(fd,buff,1,0);
                            if(0 >= c)
                            {
                                if(errno != EAGAIN && errno != EWOULDBLOCK ) 
                                {
                                    epoll_del(epfd,fd);
                                    close(fd);
                                    printf("one client close\n");
                                }
                                break;
                            }
                            printf("client(%d):%s\n",fd,buff);
                            send(fd,"OK",2,0);
                        }
                    }
                }
            }
        }
    }
}

客户端参考代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(sockfd != -1);
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == res)
    { 
        exit(1);
    }
    while (1)
    {
        char buff[128] = {0};
        printf("input:\n");
        fgets(buff, 128, stdin); // 会取得\n
        buff[strlen(buff)-1] = 0; //删除\n
        if (strncmp(buff, "end", 3) == 0)
        {
            break;
        }
        send(sockfd, buff, strlen(buff), 0);
        memset(buff, 0, 128);
        recv(sockfd, buff, 127, 0);
        printf("buff=%s\n", buff);
    }
    close(sockfd);
    exit(0);
}

运行结果:

在这里插入图片描述

select/poll/epoll的区别

select

1.select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。
2.select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程。
3.select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。

poll

本质上和select没有区别,主要就是去掉了 select 只能监听 1024 个文件描述符的限制。

epoll

1.内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
2.内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
3.内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

总结:

I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

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

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

相关文章

鸿蒙实战开发-使用关系型数据库实现对账单的增、删、改、查

介绍 本Codelab以记账为例&#xff0c;使用关系型数据库的相关接口实现了对账单的增、删、改、查操作。实现效果如图所示&#xff1a; 相关概念 关系型数据库&#xff1a;基于关系模型来管理数据的数据库&#xff0c;提供了增、删、改、查等接口&#xff0c;也可运行输入的SQ…

简述C语言文件操作

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分79)&#xff0c;分享…

java-基于springboot+vue实现的旅游信息管理系统功能介绍

开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 项目关键技术 1、JSP技术 JSP(Java…

深入探索JDK动态代理:从入门到原理的全面解析

文章目录 基本概念入门案例实现JDK动态代理的步骤入门实操拓展--动态生成代理类的几种方法方式一&#xff1a;通过getProxyClass方法获取代理实例方式二&#xff1a;通过newProxyInstance方法获取代理实例&#xff08;常用&#xff01;&#xff09;方式三&#xff1a;通过Lambd…

二十三 超级数据查看器 讲解稿 设置

二十三 超级数据查看器 讲解稿 设置 ​点击此处 以新页面 打开B站 播放当前教学视频 点击访问app下载页面 百度手机助手 下载地址 大家好&#xff0c;这节课我们讲一下&#xff0c;超级数据查看器的设置功能。 首先&#xff0c;我们打开超级数据查看器&#xff0c; 我…

AcWing 796. 子矩阵的和

这个题的重点是仿照一维的数组&#xff0c;所以a[N][N]也是从1索引开始的。画个图举个例子就非常清晰了 之所以不好理解是因为没画格子&#xff0c;一个格子代表一个点&#xff0c;就很好理解了。 java代码&#xff1a; import java.io.*; public class Main{static int N 1…

Java二阶知识点总结(七)SVN和Git

SVN 1、SVN和Git的区别 SVN是集中式的&#xff0c;也就是会有一个服务器保存所有代码&#xff0c;拉取代码的时候只能从这个服务器上拉取&#xff1b;Git是分布式的&#xff0c;也就是说每个人都保存有所有代码&#xff0c;如果要获取代码&#xff0c;可以从其他人手上获取SV…

使用 STL 容器发生异常的常见原因分析与总结

目录 1、概述 2、使用STL列表中的元素越界 3、遍历STL列表删除元素时对迭代器自加处理有问题引发越界 4、更隐蔽的遍历STL列表删除元素时引发越界的场景 5、多线程同时操作STL列表时没有加锁导致冲突 6、对包含STL列表对象的结构体进行memset操作导致STL列表对象内存出异…

操作符的属性:优先级、结合性

操作符的属性&#xff1a;优先级、结合性 优先级结合性 C语言的操作符有2个重要的属性&#xff1a;优先级、结合性&#xff0c;这两个属性决定了表达式求值的计算顺序。 优先级 优先级指的是&#xff0c;如果⼀个表达式包含多个运算符&#xff0c;哪个运算符应该优先执行。各…

RabbitMQ3.x之一_WindowServer2019中安装RabbitMQ详细教程

RabbitMQ3.x之一_WindowServer2019中安装RabbitMQ详细教程 文章目录 RabbitMQ3.x之一_WindowServer2019中安装RabbitMQ详细教程1. 安装环境说明1. WindowServer20192. ErLang与RabbitMQ对应版本 2 安装Erlang1. 安装Erlang2. ErLnag环境变量配置3. 查看是否安装成功 3. 安装Rab…

数据结构面试常见问题之串的模式匹配(KMP算法)系列-大师改进

&#x1f600;前言 KMP算法是一种改进的字符串匹配算法&#xff0c;由D.E.Knuth&#xff0c;J.H.Morris和V.R.Pratt提出&#xff0c;因此人们称它为克努特—莫里斯—普拉特操作&#xff08;简称KMP算法&#xff09; KMP算法的优势: 提高了匹配效率&#xff0c;时间复杂度为O(m…

【C++】用哈希桶模拟实现unordered_set和unordered_map

目录 一、哈希介绍1.1 哈希概念1.2 哈希冲突解决1.2.1 闭散列1.2.2 开散列 二、哈希桶2.1 实现哈希桶2.1.1 构造节点和声明成员变量2.1.2 构造与析构2.1.3 仿函数2.1.4 查找2.1.5 插入2.1.6 删除 2.2 kv模型哈希桶源代码 三、改造哈希桶3.1 beginend3.2 迭代器3.2.1 前置 3.3 改…

Linux_ubuntu中进行断点调试

文章目录 一、安装gdb调试器&#xff1a;二、使用gcc编译程序&#xff1a;三、使用gdb对程序进行调试&#xff1a;1.设置断点&#xff1a;使用break命令或简写为b来设置断点2.调试运行——run&#xff1a;3.继续执行——continue/c&#xff1a;4.单步执行&#xff1a;5.监视变量…

6.windows ubuntu 子系统 测序数据质量控制。

上一个分享&#xff0c;我们对测序数据进行了质量评估&#xff0c;接下来我们需要对数据进行数据质量控制。 数据预处理&#xff08;Data Preprocessing&#xff09;&#xff1a;包括去除接头序列&#xff08;adapter trimming&#xff09;、去除低质量序列&#xff08;qualit…

【Spring Boot 源码学习】共享 MetadataReaderFactory 上下文初始化器

《Spring Boot 源码学习系列》 共享 MetadataReaderFactory 上下文初始化器 一、引言二、往期内容三、主要内容3.1 源码初识3.2 CachingMetadataReaderFactoryPostProcessor3.2.1 register 方法3.2.1 configureConfigurationClassPostProcessor 方法 3.3 ConfigurationClassPos…

SpringMVC结合设计模式:解决MyBatisPlus传递嵌套JSON数据的难题

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

Learn OpenGL 24 点光源阴影

点光源阴影 上个教程我们学到了如何使用阴影映射技术创建动态阴影。效果不错&#xff0c;但它只适合定向光&#xff0c;因为阴影只是在单一定向光源下生成的。所以它也叫定向阴影映射&#xff0c;深度&#xff08;阴影&#xff09;贴图生成自定向光的视角。 本节我们的焦点是…

Java进阶—GC回收(垃圾回收)

1. 什么是垃圾回收 垃圾回收(Garbage Collection&#xff0c;GC)是Java虚拟机(JVM)的一项重要功能&#xff0c;用于自动管理程序中不再使用的内存。在Java中&#xff0c;程序员不需要手动释放内存&#xff0c;因为GC会自动检测并回收不再使用的对象&#xff0c;从而减少内存泄…

Java基础【上】韩顺平(反射、类加载、final接口、抽象类、内部类)

涵盖知识点&#xff1a;反射、类加载、单例模式、final、抽象类、接口、内部类&#xff08;局部内部类、匿名内部类、成员内部类、静态内部类&#xff09; P711 反射机制原理 创建如下目录结构&#xff0c;在模块下创建src文件夹&#xff0c;文件夹要设置为Sources文件夹&…

Git使用:实现文件在不同设备之间进行同步

一、注册Gitee&#xff0c;创建远程仓库 注册网址&#xff1a;登录 - Gitee.com 打开Gitee&#xff0c;注册完进行登录&#xff0c;点击右上角【】创建一个仓库 新建仓库&#xff1a; 点击创建&#xff0c;仓库创建完毕。 二、下载Git安装包&#xff0c;并创建本地仓库 下载网…