TCP与IO多路复用实现板块(select、poll、epoll)

使用Select方式创建TCP服务器

一些基本概念

select实现IO多路复用的特点

  1. 一个进程最多只能监听1024个文件描述符(千级别)

  2. select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗cpu资源)

  3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(注意:拷贝是非常耗时的)

注意

select 一旦返回将没有产生事件的文件描述符从表中清空。下次检测需要重新添加。表中的下标是文件描述符的值,添加的时候是根据对应的位置添加的,检测个数是添加进表中最大的文件描述符+1

重点函数

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
​
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/*
    功能:用于检测是哪个或者哪些文件描述符产生的事件
    参数:
        nfds:用于检测最大文件描述符个数
        readfds:读事件集合
        writefds:写事件集合
        exceptfds:异常事件类型集合
        timeout:超时检测
        (如果不做超时检测:传值NULL)
    返回值:
        <0出错
        >0有事件产生
        ==0表示超时时间已经到达
    struct timeval{
        long tv_sec;//seconds
        long tv_usec;//microseconds
    };
*/
​
void FD_CLR(int fd,fd_set *set);
int FD_ISSET(int fd,fd_set *set);
void FD_SETZ(int fd,fd_set *set);
void FD_ZERO(fd_set *set);

select.h

//select.h
#ifndef __SELECT_H
#define __SELECT_H
​
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
​
#endif

select.c

//select.c
#include "001select_demo.h"
​
int main(int argc, char const *argv[])
{
    int sockfd, acceptfd;
    if (argc != 2)
    {
        printf("please input %s <port>\n", argv[0]);
        return -1;
    }
​
    //创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
​
    //填充结构体
    struct sockaddr_in serveraddr, clientaddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[1]));
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
​
    socklen_t len = sizeof(clientaddr);
​
    //绑定
    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind err");
        return 0;
    }
​
    //监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }
​
    //select实现监听sockfd,实现多个客户端链接服务器
    //创建表
    fd_set readfds, tempfds;
    //清空表
    FD_ZERO(&readfds);
    //添加关心的文件描述符:0 sockfd
    FD_SET(0, &readfds);
    FD_SET(sockfd, &readfds);
​
    int maxfd = sockfd;
​
    int ret;
    char buf[128];
    int recvbyte;
    while (1)
    {
        tempfds = readfds;
        //调用select循环检测是否有事件发生
        ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
​
        if (ret < 0)
        {
            perror("select err");
            return -1;
        }
​
        for (int i = 0; i < maxfd + 1; i++)
        {
            if (FD_ISSET(i, &tempfds))
            {
                if (i == 0)
                {
                    memset(buf, 0, sizeof(buf));
                    fgets(buf, sizeof(buf), stdin);
                    printf("key:%s\n", buf);
                    //所有客户端发送通知
                    for (int j = sockfd + 1; j < maxfd + 1; j++)
                    {
                        if (FD_ISSET(j, &readfds))
                        {
                            send(j, buf, sizeof(buf), 0);
                        }
                    }
                }
                else if (i == sockfd)
                {
                    if ((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len)) < 0)
                    {
                        perror("accept err");
                        return -1;
                    }
                    printf("client:ip=%s,port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
                    //用于通信的文件描述符添加到原表中
                    FD_SET(acceptfd, &readfds);
                    if (maxfd < acceptfd)
                    {
                        maxfd = acceptfd;
                    }
                }
                else
                {
                    memset(buf, 0, sizeof(buf));
                    recvbyte = recv(i, buf, sizeof(buf), 0);
                    if (recvbyte < 0)
                    {
                        perror("recv err");
                        return -1;
                    }
                    else if (recvbyte == 0)
                    {
                        printf("client exit.\n");
                        FD_CLR(i, &readfds);
                        close(i);
                    }
                    else
                    {
                        printf("%d:%s\n", i, buf);
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

使用Poll方式创建TCP服务器

一些基本概念

poll实现IO多路复用的特点

  1. 优化文件描述符个数限制(根据poll函数第一个函数参数个数来定,如果监听的事件为1个,则结构体数组元素个数为1,如果想监听100个,那么这个结构体数据的元素个数就位100,由程序员自行设定

  2. poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低

  3. poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

重点函数

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
    功能:和 select 实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列
    参数:*fds:关心的文件描述符数组 struct pollfd fds[N];
        nfds:文件描述符个数
        timeout:超时检测(毫秒级 -1阻塞)
        
        struct pollfd{
            int fd;//检测的文件描述符
            short events;//检测事件
            short revents;//调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员的值就可以确定是否产生事件
        }
        事件:
            POLLIN:读事件//监听消息一般都是读消息,终端的输入也是读消息,accept请求客户端也是读消息
            POLLOUT:写事件
            POLLERR:异常事件
        返回值:
            <0出错
            >0表示有事件产生
            ==0表示超时时间已到
        
*/
​

poll.h

#ifndef __POLL_H
#define __POLL_H
​
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/time.h>
#include <unistd.h>
​
#endif

poll.c

#include "002poll_demo.h"
​
int main(int argc, const char *argv[])
{
    int sockfd, acceptfd;
    if (argc != 2)
    {
        printf("please input %s <port>\n", argv[0]);
        return -1;
    }
​
    //创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    //填充结构体
    struct sockaddr_in serveraddr, clientaddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[1]));
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
​
    socklen_t len = sizeof(clientaddr);
​
    //绑定
    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
​
    //监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }
​
    //poll实现监听sockfd,实现多个客户端链接服务器
    //创建表
    struct pollfd fds[20] = {0};
    //将关心的文件描述符添加到表中
    fds[0].fd = 0;
    fds[0].events = POLLIN; //读事件
​
    fds[1].fd = sockfd;
    fds[1].events = POLLIN;
​
    int num = 2;
​
    int ret;
    char buf[128];
    int recvbyte;
    while (1)
    {
        //调用poll循环检测是否有事件产生
        ret = poll(fds, num, -1); //阻塞等待事件产生返回
        if (ret < 0)
        {
            perror("poll err");
            return -1;
        }
​
        for (int i = 0; i < num; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == 0)
                {
                    memset(buf, 0, sizeof(buf));
                    fgets(buf, sizeof(buf), stdin);
                    printf("key:%s\n", buf);
                    for (int j = 2; j < num; j++)
                    {
                        send(fds[j].fd, buf, sizeof(buf), 0);
                    }
                }
                else if (fds[i].fd == sockfd)
                {
                    if ((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len)) < 0)
                    {
                        perror("accept err");
                        return -1;
                    }
                    printf("client:ip=%s,port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
​
                    fds[num].fd = acceptfd;
                    fds[num].events = POLLIN;
                    num++;
                }
                else
                {
                    memset(buf, 0, sizeof(buf));
                    recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0);
                    if (recvbyte < 0)
                    {
                        perror("recv err");
                        return -1;
                    }
                    else if (recvbyte == 0)
                    {
                        printf("client exit.\n");
                        close(fds[i].fd);
​
                        for (int j = i + 1; j < num; j++)
                        {
                            fds[j - 1] = fds[j];
                        }
                        num--;
                        i--;
                    }
                    else
                    {
                        printf("%d:%s\n", fds[i].fd, buf);
                    }
                }
            }
        }
    }
​
    return 0;
}
​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值