socket编程以及select、epoll、poll示例详解

2023-05-16

socket编程

socket这个词可以表示很多概念,在TCP/IP协议中“IP地址 + TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP + 端口号”就称为socket。在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么两个socket组成的socket pair就唯一标识一个连接。

  • 预备知识
    网络字节序:内存中多字节数据相对于内存地址有大端小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,所以发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接收到的字节按内存从低到高的顺序保存,因此网络数据流的地址应该规定:先发出的数据是低地址,后发出的数据是高地址。TCP/IP协议规定网络数据流应该采用大端字节序,即低地址高字节。所以发送主机和接收主机是小段字节序的在发送和接收之前需要做字节序的转换。

    为了使网络程序具有可移植性可以调用以下函数进行网络字节数的转换。

 #include <arpa/inet.h>
 uint32_t htonl(uint32_t hostlong);
 uint16_t htons(uint16_t hostshort);
 uint32_t ntohl(uint32_t netlong);
 uint16_t ntohs(uint16_t netshort);

socket地址数据类型及相关函数
sockaddr数据结构
这里写图片描述

IPv6和UNIXDomain Socket的地 址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的 内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指 针,但是sock API的实现早于ANSI C标准化,那时还没有空指针类型这些函数的参数都⽤用struct sockaddr 类型表示,在传递参数之前要强制类型转换一下。
本次只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表⽰示32位的IP 地址。但是我们通常⽤用点分十进制的字符串表示IP 地址,以下函数可以在字符串表⽰示 和in_addr表⽰示之间转换。也就是说可以将字符串转换成in_addr类型,也可以将本地字节转换成网络字节。相反由同样有从网络转换到本地的函数具体的用法我们下面的代码中来看。

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
       int inet_aton(const char *cp, struct in_addr *inp);
       in_addr_t inet_addr(const char *cp);

       in_addr_t inet_network(const char *cp);

       char *inet_ntoa(struct in_addr in);

       struct in_addr inet_makeaddr(int net, int host);

       in_addr_t inet_lnaof(struct in_addr in)
  • tcpsocket 实现
    实现模型:
    1.服务器端 socket -> bind -> listen -> accept(阻塞,三次握手)-> send。
    2.客户端 socket -> connect(阻塞,三次握手)-> rcv。
    函数介绍:
 int socket(int family, int type, int protocol)

family :指定协议的类型本次选择AF_INET(IPv4协议)。
type:网络数据类型,TCP是面向字节流的—SOCK_STREAM.
protocol:前两个参数一般确定了协议类型通常传0.
返回值:成功返回套接字符。
失败返回-1设置相关错误码。

int bind(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

sockfd : socket函数成功时候返回的套接字描述符。
servaddr :服务器的IP和端口。
addrlen : 长度(sizeof(servaddr))。
返回值:成功返回0
失败返回-1,并设置相关错误码。

int listen(int sockfd, int backlog)

sockfd: socket函数成功时候返回的套接字描述符。
backlog : 内核中套接字排队的最大个数。
返回值:成功返回0
失败返回-1,并设置相关错误码。

 int accept(int sockfd, const struct sockaddr *servaddr, socklen_t *addrlen)

sockfd : socket函数成功时候返回的套接字描述符。
servaddr : 输出型参数,客户端的ip和端口。
addrlen : 长度(sizeof(servaddr))。
返回值:成功:从监听套接字返回已连接套接字
失败:失败返回-1,并设置相关错误码。

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

sockfd:函数返回的套接字描述符
servaddr :服务器的IP和端口
addrlen : 长度(sizeof(servaddr))。
返回值:成功返回0
失败返回-1,并设置相关错误码

  • 实现代码
server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
static usage(const char* proc)
{
 printf("Usage:%s[local-ip][local-port]\n",proc);
}
static int start_up(const char *local_ip,int local_port)
{
    //1.create sock
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        close(sock);
        exit(1);
    }
    //2,createbind
    struct sockaddr_in local;
    bzero(&local,sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(local_port);
    local.sin_addr.s_addr = inet_addr(local_ip);
    if( bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0)
    {
        perror("bind");
        close(sock);
        exit(2);
    }
    //3.listen
    if(listen(sock,10) < 0)
    {
     perror("listen");
    close(sock);
     exit(3);
    }
    return sock;

}
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
    }
    int sock = start_up(argv[1],atoi(argv[2]));
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    while(1)
    {
        int new_sock = accept(sock,(struct sockaddr*)&client, &len);
        if(new_sock < 0)
        {
            perror("accept");
            close(sock);
            return 1;
        }
        printf("client_ip:%s client_port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
        char buf[1024];
        while(1)
        {
            ssize_t s = read(new_sock,buf,sizeof(buf)-1);
            if(s > 0)
            {
                buf[s] = 0;
                printf("client say# %s",buf);
            }
            else if(s == 0)
            {
                printf("client quit!\n");
                break;
            }
            write(new_sock,buf, strlen(buf));
        }
    }

    close(sock);
    return 0;
}
client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
static void usage(const char* proc)
{
    printf("Usage:%s [server-ip] [server-port]",proc);
}
int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
    }
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        return 1;
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    if(connect(sock,(struct sockaddr *)&server,sizeof(server)) < 0)
    {
        perror("connect");
        return 2;
    }
    while(1)
    {
        printf("please Entry#");
        fflush(stdout);
        char buf[1024];
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s > 0)//read success
        {
            buf[s] = 0;
        }
        write(sock,buf,strlen(buf));
        ssize_t _s = read(sock,buf,sizeof(buf)-1);
        if(_s > 0)
        {
            buf[_s - 1] = 0;
            printf("server echo# %s\n",buf);
        }
    }
    close(sock);
    return 0;
}

上述代码只可以处理单个用户,为了可以处理多个用户请求我们可以编写多进程或者多线程的TCP套接字。完整代码如下。

多进程TCPsocket
https://coding.net/u/Hyacinth_Dy/p/MyCode/git/blob/master/%E5%A4%9A%E8%BF%9B%E7%A8%8BTCP%E5%A5%97%E6%8E%A5%E5%AD%97

多线程TCPsocket
https://coding.net/u/Hyacinth_Dy/p/MyCode/git/blob/master/%E5%A4%9A%E7%BA%BF%E7%A8%8BTcp%E5%A5%97%E6%8E%A5%E5%AD%97

对于上述代码就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即在性能上并不是合理的选择,因此我们需要提高代码的性能。下面介绍三种常用的高性能套接字编程方法。

I/O多路复用之select函数

  • select函数预备知识

    1. struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作。
      (1) FD_CLR(inr fd,fd_set* set):用来清除描述词组set中相关fd 的位
      (2)FD_ISSET(int fd,fd_set *set):用来测试描述词组set中相关fd 的位是否为真
      FD_SET(int fd,fd_set*set):用来设置描述词组set中相关fd的位
      2.struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
      FD_ZERO(fd_set *set);用来清除描述词组set的全部位
  • select函数介绍

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

maxfdp : 需要监视的最大文件描述符加1。

readfds、writefds、errorfds:分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。

timeout:等待时间,这个时间内,需要监视的描述符没有事件
发⽣生则函数返回,返回值为0。设为NULL 表示阻塞式等待,一直等到有事件就绪,函数才会返回,0表示非阻塞式等待,没有事件就立即返回,大于0表示等待的时间。
返回值:大于0表示就绪时间的个数,等于0表示timeout等待时间到了,小于0表示调用失败。

  • select函数原理

    select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这⾥里等待,直到被监视的文件句柄有一个或多个发⽣生了状态改变。关于文件句柄,其实就是⼀一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

    1.我们通常需要额外定义一个数组来保存需要监视的文件描述符,并将其他没有保存描述符的位置初始化为一个特定值,一般为-1,这样方便我们遍历数组,判断对应的文件描述符是否发生了相应的事件。

    2.采用上述的宏操作FD_SET(int fd,fd_set*set)遍历数组将关心的文件描述符设置到对应的事件集合里。并且每次调用之前都需要遍历数组,设置文件描述符。

    3.调用select函数等待所关心的文件描述符。有文件描述符上的事件就绪后select函数返回,没有事件就绪的文件描述符在文件描述符集合中对应的位置会被置为0,这就是上述第二步的原因。

    4.select 返回值大于0表示就绪的文件描述符的个数,0表示等待时间到了,小于0表示调用失败,因此我们可以遍历数组采用FD_ISSET(int fd,fd_set *set)判断哪个文件描述符上的事件就绪,然后执行相应的操作。

  • 采用select的tcp socket实现代码。

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
static void Usage(const char* proc)
{
    printf("%s [local_ip] [local_port]\n",proc);
}
int array[4096];
static int start_up(const char* _ip,int _port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = inet_addr(_ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
    {
        perror("bind");
        exit(2);
    }
    if(listen(sock,10) < 0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return -1;
    }
    int listensock = start_up(argv[1],atoi(argv[2]));
    int maxfd = 0;
    fd_set rfds;
    fd_set wfds;
    array[0] = listensock;
    int i = 1;
    int array_size = sizeof(array)/sizeof(array[0]);
    for(; i < array_size;i++)
    {
        array[i] = -1;
    }
    while(1)
    {
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        for(i = 0;i < array_size;++i)
        {
            if(array[i] > 0)
            {
                FD_SET(array[i],&rfds);
                FD_SET(array[i],&wfds);
                if(array[i] > maxfd)
                {
                    maxfd = array[i];
                }
            }
        }
        switch(select(maxfd + 1,&rfds,&wfds,NULL,NULL))
        {
            case 0:
                {
                    printf("timeout\n");
                    break;
                }
            case -1:
                {
                    perror("select");
                    break;
                }
             default:
                {
                    int j = 0;
                    for(; j < array_size; ++j)
                    {
                        if(j == 0 && FD_ISSET(array[j],&rfds))
                        {
                            //listensock happened read events
                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);
                            int new_sock = accept(listensock,(struct sockaddr*)&client,&len);
                            if(new_sock < 0)//accept failed
                            {
                                perror("accept");
                                continue;
                            }
                            else//accept success
                            {
                                printf("get a new client%s\n",inet_ntoa(client.sin_addr));
                                fflush(stdout);
                                int k = 1;
                                for(; k < array_size;++k)
                                {
                                    if(array[k] < 0)
                                    {
                                        array[k] = new_sock;
                                        if(new_sock > maxfd)
                                            maxfd = new_sock;
                                        break;
                                    }
                                }
                                if(k == array_size)
                                {
                                    close(new_sock);
                                }
                            }
                        }//j == 0
                        else if(j != 0 && FD_ISSET(array[j], &rfds))
                        {
                            //new_sock happend read events
                            char buf[1024];
                            ssize_t s = read(array[j],buf,sizeof(buf) - 1);
                            if(s > 0)//read success
                            {
                                buf[s] = 0;
                                printf("clientsay#%s\n",buf);
                                if(FD_ISSET(array[j],&wfds))
                                {
                                    char *msg = "HTTP/1.0 200 OK <\r\n\r\n<html><h1>yingying beautiful</h1></html>\r\n";
                                    write(array[j],msg,strlen(msg));

                                }
                            }
                            else if(0 == s)
                            {
                                printf("client quit!\n");
                                close(array[j]);
                                array[j] = -1;
                            }
                            else
                            {
                                perror("read");
                                close(array[j]);
                                array[j] = -1;
                            }
                        }//else j != 0  
                    }
                    break;
                }
        }
    }
    return 0;
}

client端同上面tcpsocket端相同。

  • select的优缺点
    优点:
    (1)select的可移植性好,在某些unix下不支持poll.
    (2)select对超时值提供了很好的精度,精确到微秒,而poll式毫秒。
    缺点:
    (1)单个进程可监视的fd数量被限制,默认是1024。
    (2)需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
    (3)对fd进行扫描时是线性扫描,fd剧增后,IO效率降低,每次调用都对fd进行线性扫描遍历,随着fd的增加会造成遍历速度慢的问题。
    (4)select函数超时参数在返回时也是未定义的,考虑到可移植性,每次超时之后进入下一个select之前都要重新设置超时参数。

I/O多路复用之poll函数

  • poll函数预备知识
    不同于select函数poll采用一个pollfd指针向内核传递需要关心的描述符及其相关事件。
    这里写图片描述
    fd : 需要关心的文件描述符
    events : 需要关心的事件,合法事件如下
POLLIN         有数据可读。

POLLRDNORM       有普通数据可读。

POLLRDBAND      有优先数据可读。

POLLPRI        有紧迫数据可读。

POLLOUT         写数据不会导致阻塞。

POLLWRNORM       写普通数据不会导致阻塞。

POLLWRBAND       写优先数据不会导致阻塞。

POLLMSGSIGPOLL     消息可用。

revents : 关心的事件就绪时 revents会被设置成上述对应的事件,除此之外还可能设置为如下内容。

POLLER    指定的文件描述符发生错误。

POLLHUP   指定的文件描述符挂起事件。

POLLNVAL  指定的文件描述符非法。
  • poll函数介绍
#include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

参数介绍:
fds : 对应上述介绍的结构体指针
nfds : 标记数组中结构体元素的总个数。
timeout : 超时时间 ,等于0表示非阻塞式等待,小于0表示阻塞式等待,大于0表示等待的时间。
返回值:
成功时返回fds数组中事件就绪的文件描述符的个数
返回0表示超时时间到了。
返回-1表示调用失败,对应的错误码会被设置。

EBADF         一个或多个结构体中指定的文件描述符无效。

EFAULTfds   指针指向的地址超出进程的地址空间。

EINTR      请求的事件之前产生一个信号,调用可以重新发起。

EINVALnfds  参数超出PLIMIT_NOFILE值。

ENOMEM       可用内存不足,无法完成请求。
  • poll函数实现原理
    (1)将需要关心的文件描述符放进fds数组中
    (2)调用poll函数
    (3)函数成功返回后根据返回值遍历fds数组,将关心的事件与结构体中的revents相与判断事件是否就绪。
    (4)事件就绪执行相关操作。

  • poll实现tcpsocket代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
static void usage(const char *proc)
{
    printf("%s [local_ip] [local_port]\n",proc);
}
int start_up(const char*_ip,int _port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }
    int opt = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = inet_addr(_ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
    {
        perror("bind");
        return 3;
    }
    if(listen(sock,10) < 0)
    {
        perror("listen");
        return 4;
    }
    return sock;
}
int main(int argc, char*argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        return 1;
    }
    int sock = start_up(argv[1],atoi(argv[2]));
    struct pollfd peerfd[1024];
    peerfd[0].fd = sock;
    peerfd[0].events = POLLIN;
    int nfds = 1;
    int ret;
    int maxsize = sizeof(peerfd)/sizeof(peerfd[0]);
    int i = 1;
    int timeout = -1;
    for(; i < maxsize; ++i)
    {
        peerfd[i].fd = -1;
    }
    while(1)
    {
        switch(ret = poll(peerfd,nfds,timeout))
        {
            case 0:
                printf("timeout...\n");
                break;
            case -1:
                perror("poll");
                break;
            default:
                {
                        if(peerfd[0].revents & POLLIN)
                        {
                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);
                            int new_sock = accept(sock,\
                                    (struct sockaddr*)&client,&len);
                            printf("accept finish %d\n",new_sock);
                            if(new_sock < 0)
                            {
                                perror("accept");
                                continue;
                            }
                            printf("get a new client\n");
                                int j = 1;
                                for(; j < maxsize; ++j)
                                {
                                    if(peerfd[j].fd < 0)
                                    {
                                        peerfd[j].fd = new_sock;
                                        break;
                                    }
                                }
                                if(j == maxsize)
                                {
                                    printf("to many clients...\n");
                                    close(new_sock);
                                }
                                peerfd[j].events = POLLIN;
                                if(j + 1 > nfds)
                                    nfds = j + 1;
                        }
                        for(i = 1;i < nfds;++i)
                        {
                            if(peerfd[i].revents & POLLIN)
                        {
                            printf("read ready\n");
                            char buf[1024];
                            ssize_t s = read(peerfd[i].fd,buf, \
                                    sizeof(buf) - 1);
                            if(s > 0)
                            {
                                buf[s] = 0;
                                printf("client say#%s",buf);
                                fflush(stdout);
                                peerfd[i].events = POLLOUT;
                            }
                        else if(s <= 0)
                            {
                                close(peerfd[i].fd);
                                peerfd[i].fd = -1;
                            }
                            else
                            {

                            }
                        }//i != 0
                        else if(peerfd[i].revents & POLLOUT)
                        {
                            char *msg = "HTTP/1.0 200 OK \
                                         <\r\n\r\n<html><h1> \
                                         yingying beautiful \
                                         </h1></html>\r\n";
                            write(peerfd[i].fd,msg,strlen(msg));
                            close(peerfd[i].fd);
                            peerfd[i].fd = -1;
                        }
                        else
                        {
                        }
                    }//for
                }//default
                break;
        }
    }
    return 0;
}

客户端同上。

  • poll函数的优缺点
    优点:
    (1)不要求计算最大文件描述符+1的大小。
    (2)应付大数量的文件描述符时比select要快。
    (3)没有最大连接数的限制是基于链表存储的。
    缺点:
    (1)大量的fd数组被整体复制于内核态和用户态之间,而不管这样的复制是不是有意义。
    (2)同select相同的是调用结束后需要轮询来获取就绪描述符。

I/O多路复用之epoll函数

  • epoll函数预备知识
    epoll函数是多路复用IO接口select和poll函数的增强版本。显著减少程序在大量并发连接中只有少量活跃的情况下CPU利用率,他不会复用文件描述符集合来传递结果,而迫使开发者每次等待事件之前都必须重新设置要等待的文件描述符集合,另外就是获取事件时无需遍历整个文件描述符集合,只需要遍历被内核异步唤醒加入ready队列的描述符集合就行了 。

  • epoll函数相关系统调用

 int epoll_create(int size);

生成一个epoll函数专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。

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

控制文件描述符上的事件,包括注册,删除,修改等操作。
epfd : epoll的专用描述符。
op : 相关操作,通常用以下宏来表示
event : 通知内核需要监听的事件,

EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除⼀一个fd;

fd : 需要监听的事件。结构体格式如下:
这里写图片描述
events的合法参数如下

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这⾥里应该表⽰示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于⽔水平触发(Level
Triggered)来说的。
EPOLLONESHOT:只监听⼀一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加⼊入到EPOLL队列里。
 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epfd : epoll特有的文件描述符
events :从内核中的就绪队列中拷贝出就绪的文件描述符。不可以是空指针,内核只负责将数据拷贝到这里,不会为我们开辟空间。
maxevent : 高速内核events有多大,一般不能超过epoll_create传递的size,
timeout : 函数超时时间,0表示非阻塞式等待,-1表示阻塞式等待,函数返回0表示已经超时。
- epoll函数底层实现过程
首先epoll_create创建一个epoll文件描述符,底层同时创建一个红黑树,和一个就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,epoll_wait马上返回。ET模式不会检查,只会调用一次
- epoll实现tcpsocket代码

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<sys/epoll.h>
static Usage(const char* proc)
{
    printf("%s [local_ip] [local_port]\n",proc);
}
int start_up(const char*_ip,int _port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        exit(2);
    }
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = inet_addr(_ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
    {
        perror("bind");
        exit(3);
    }
    if(listen(sock,10)< 0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}
int main(int argc, char*argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }
    int sock = start_up(argv[1],atoi(argv[2]));
    int epollfd = epoll_create(256);
    if(epollfd < 0)
    {
        perror("epoll_create");
        return 5;
    }
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sock;
    if(epoll_ctl(epollfd,EPOLL_CTL_ADD,sock,&ev) < 0)
    {
        perror("epoll_ctl");
        return 6;
    }
    int evnums = 0;//epoll_wait return val
    struct epoll_event evs[64];
    int timeout = -1;
    while(1)
    {
        switch(evnums = epoll_wait(epollfd,evs,64,timeout))
        {
            case 0:
     printf("timeout...\n");
     break;
            case -1:
     perror("epoll_wait");
     break;
default:
     {
         int i = 0;
         for(; i < evnums; ++i)
         {
             struct sockaddr_in client;
             socklen_t len = sizeof(client);
             if(evs[i].data.fd == sock \
                     && evs[i].events & EPOLLIN)
             {
                 int new_sock = accept(sock, \
                         (struct sockaddr*)&client,&len);
                 if(new_sock < 0)
                 {
                     perror("accept");
                     continue;
                 }//if accept failed
                 else 
                 {
                     printf("Get a new client[%s]\n", \
                             inet_ntoa(client.sin_addr));
                     ev.data.fd = new_sock;
                     ev.events = EPOLLIN;
                     epoll_ctl(epollfd,EPOLL_CTL_ADD,\
                             new_sock,&ev);
                 }//accept success

             }//if fd == sock
             else if(evs[i].data.fd != sock && \
                     evs[i].events & EPOLLIN)
             {
                 char buf[1024];
                 ssize_t s = read(evs[i].data.fd,buf,sizeof(buf) - 1);
                 if(s > 0)
                 {
                     buf[s] = 0;
                     printf("client say#%s",buf);
                     ev.data.fd = evs[i].data.fd;
                     ev.events = EPOLLOUT;
                     epoll_ctl(epollfd,EPOLL_CTL_MOD, \
                             evs[i].data.fd,&ev);
                 }//s > 0
                 else
                 {
                     close(evs[i].data.fd);
                     epoll_ctl(epollfd,EPOLL_CTL_DEL, \
                             evs[i].data.fd,NULL);
                 }
             }//fd != sock
             else if(evs[i].data.fd != sock \
                     && evs[i].events & EPOLLOUT)
             {
                 char *msg =  "HTTP/1.0 200 OK <\r\n\r\n<html><h1>yingying beautiful </h1></html>\r\n";
                 write(evs[i].data.fd,msg,strlen(msg));
                 close(evs[i].data.fd);
                 epoll_ctl(epollfd,EPOLL_CTL_DEL, \
                             evs[i].data.fd,NULL);
             }//EPOLLOUT
             else
             {
             }
         }//for
     }//default
     break;
        }//switch
    }//while
    return 0;
}
  • epoll函数的优缺点
    优点:
    epoll的优点:
    (1)支持一个进程打开大数目的socket描述符(FD)
    select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
    (2)IO效率不随FD数目增加而线性下降
    传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是”活跃”的, 但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行 操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个”伪”AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的—比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
    (3)使用mmap加速内核与用户空间的消息传递。
    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就 很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。
    (4)内核微调
    这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。 比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小 — 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网 卡驱动架构。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

socket编程以及select、epoll、poll示例详解 的相关文章

  • linux安装llvm

    先装cmake xff0c 可以用sudo apt get install cmake或者去官网下载源码编译安装 下载llvm git clone https github com llvm llvm project git 3 Build
  • Makefile中的MAKECMDGOALS

    make 在执行时会设置一个特殊变量 xff1a 34 MAKECMDGOALS 34 xff0c 该变量记录了命令行参数指定的终极目标列表 xff0c 没有通过参数指定终极目标时此变量为空 该变量仅限于用在特殊场合 比如判断 xff0c
  • Docker切换存放目录踩坑

    因为 var 目录下的磁盘空间不足 xff0c 故把docker的存放目录切换到 data 下面 xff0c 具体步骤 xff1a 1 编辑docker配置文件 etc docker daemon json xff0c 修改data roo
  • 关于Modelsim SE-64 2020.4取消优化后不显示波形问题

    Modelsim取消优化后报错 Error suppressible vsim 12110 All optimizations are disabled because the novopt option is in effect This
  • 关于串口调试助手XCOM点击发送后卡住问题

    未成功安装CH340驱动 USB串口驱动 安装前先重启电脑 xff0c 再点击安装 串口选择错误 打开设备管理器 xff0c 查看USB连接的端口 xff08 COM xff09 号 xff0c 选择正确的端口 xff08 COM xff0
  • Makefile中Linux转Windows执行知识点

    makefile 是一个自动化编译工具 xff0c 可以简化编译过程 xff0c 自动化处理依赖关系和编译顺序 xff0c 提高了代码的可维护性 makefile 通常由一些规则和命令组成 xff0c 规则由目标 依赖和命令构成 xff0c
  • darknet2ncnn编译中 libopencv 库文件找不到

    问题描述 没有直接从 github 上下载 darknet2ncnn 包 xff0c 用的是他人提供的包 xff0c 包已经编译好 解压已经有 convert verify 文件 执行该文件 xff0c 问题描述如下 xff1a root
  • linux usb设备如何和u盘对应

    已知 usb 的 pid vid 如何对加载的u盘进行管理 思路 xff0c 找到 U盘的厂商信息中的pid和 vid 对应关系 xff0c 然后控制 U盘的加载 但是 U盘信息中没有pid 和 vid root 64 li PC sys
  • CV面试题(持续更新!!!)

    CV面试题 1 反卷积 反卷积又叫做转置卷积 xff0c 在计算机中计算的时候 xff0c 转置卷积先将卷积核转为稀疏矩阵C的形式 xff0c 然后计算的时候正向传播的时候左乘这个稀疏矩阵C的转置 xff0c 反向传播的时候左乘这个稀疏矩阵
  • 程序运行时数据保存位置

    程序运行时 xff0c 内存中有六个地方可以保存数据 1 寄存器 这是最快的保存区域 xff0c 寄存器位于处理器内部 然而寄存器的数量很有限 xff0c 所以寄存器是根据需要由编译器的分配的 我们对此没有直接的控制权限 也不可能在我们的程
  • ESP-Drone无人机控制板设计的第一个任务---绘制ESP32-S2-WROVER模块及周边电路

    第1步 xff0c 查看官方ESP Drone无人机ESP32 S2 WROVER模块的参考设计原理图 第二步 xff0c 用KiCAD绘制ESP32 S2 WROVER模块及周边电路 1 如图2 1所示 xff0c 从KiCAD的原理图符
  • ROS学习——读取摄像头数据image

    在ROS工作空间的src文件夹下创建read camera功能包 xff0c 并在包内创建include launch src cfg四个文件夹 在cfg文件夹中创建param yaml文件 xff0c 并写入以下内容 xff1a imag
  • ROS学习——控制小车转向

    给定一个旋转的角度 xff0c 让小车进行顺时针或逆时针旋转 span class token macro property span class token directive keyword include span span clas
  • PID参数设定

    在电机的控制领域 xff0c 不同的电机有不同的驱动方式 xff0c 其中应用最广泛的就是PID proportion integration differentiation 控制 P I和D分别指比例控制 xff0c 积分控制和微分控制
  • 系统编程__2__父子进程的创建和回收

    系统编程 这里写的是对于小白来说更多的了解系统编程的文章 xff0c 有写的不对的地方还恳请各位大佬指出错误 xff0c 小编一定会多多采纳 手动多谢 那么 xff0c 上一次我们稍微了解了一下关于系统编程的一些主要内容 没有看到的童鞋还请
  • php解决跨域访问

    php跨域问题解决判断 参考文章 xff1a php跨域 xff1a https blog csdn net ouxiaoxian article details 89332027 预检请求是什么 xff1a https www jians
  • 动态库与静态库的区别是什么

    区别 xff1a 1 静态库的扩展名一般为 a 或 lib xff1b 动态库的扩展名一般为 so 或 dll 2 静态库在编译时会直接整合到目标程序中 xff0c 编译成功的可执行文件可独立运行 xff1b 动态库在编译时不会放到连接的目
  • Ubuntu 使用 du 查看某个文件夹大小

    在 Ubuntu 系统中 xff0c 你可以使用 du 命令来查看文件夹的大小 例如 xff0c 如果你想查看文件夹 var log 的大小 xff0c 你可以使用如下的命令 xff1a du sh var log 其中 xff0c s 选
  • 无人机六旋翼数学建模[matlab-simulink]

    写在前面 xff0c 这篇文章是借鉴Drexel University 的Senior Design project的matlab simulink四旋翼模型 xff0c 在此基础上针对六旋翼进行的基本改进 xff0c 这里只对 43 型模
  • stm32连接DHT11温湿度传感器

    目录 1 DHT11简介 1 1 连接电路 1 2 串行接口 单线双向 2 cubeMX设置 3 代码开发 3 1 实现定时函数 3 2 打开串口调试 3 4 测试代码实现 4 运行效果 1 DHT11简介 1 1 连接电路 信息如下 xf

随机推荐

  • STM32CubeMX 真的不要太好用

    STM32CubeMX 真的不要太好用 由于工作内容的变动 xff0c 我已经很久没有正经的玩过单片机了 xff0c 近期又要用它做个小玩意了 xff0c 还是选 stm32 吧 xff0c 外设库开发不要太方便 xff0c 哈哈哈 先去
  • ESP-Drone控制板设计的第二个任务-绘制USB-TTL串口下载电路和ESP32-S2芯片内置USB接口电路

    1 摘要 ESP32系列处理器一般会需要采用串口来下载代码 xff0c 因此在其设计中都会保留一个USB TTL串口电路 xff0c 查看乐鑫官网的参考设计 xff0c 基本上是采用CP2102这颗USB转TTL串口芯片 xff0c 但在本
  • Airflow ETL任务调度工具 介绍

    Airflow 是 Apache 基金会的一套用于创建 管理和监控工作流程的开源平台 xff0c 是一套非常优秀的任务调度工具 截至2022年7月 xff0c 在GitHub上已经拥有近27k的star 本文主要介绍一下Airflow 2
  • Data_web(八)mysql增量同步到mongodb

    1 mongdb连接 连接方式如下 xff08 重要 xff01 xff01 xff01 xff01 xff0c 账号密码必须建立在db下面 xff0c 如果默认再admin下面 xff0c 导致无法切换库 xff0c 连接报错 xff09
  • 2021电子设计竞赛飞控视觉之openmv寻找方格中心

    写在前面 这是我在电赛飞控备赛期间写的一个小函数 xff0c 功能是寻找目标点所在方格的中心 这样四旋翼在方格地图上移动一定距离之后就可以使用openmv将四旋翼辅助定位至目前所在方格的中心 今年G题刚出来的时候本来以为能用上这个函数进行辅
  • centos6.5vim基本配置

    简单的vim配置 xff1a 在目录 etc 下面 xff0c 有个名为vimrc的文件 xff0c 这是系统中公共的vim配置文件 xff0c 对所有用户都有效 而在每个用户的主目录下 xff0c 都可以自己建立私有的配置文件 xff0c
  • atexit注册函数

    函数名 atexit 头文件 include lt stdlib h gt 功 能 注册终止函数 即main执行结束后调用的函数 用 法 int atexit void func void 注意 xff1a 按照ISO C的规定 xff0c
  • Linux管道的容量大小及管道的数据结构

    一 管道容量 xff1a 我们通过ulimit a 命令查看到的pipo size定义的是内核管道缓冲区的大小 xff0c 这个值的大小是由内核设定的 xff1b 而pipe capacity指的是管道的最大值 xff0c 即容量 xff0
  • 线程初体验

    线程的概念 xff1a 线程是一个进程地址空间的一个控制流程 xff0c 是调度的基本单位 xff0c 由于同一进程的多个线程共享同一地址空间 因此Text Segment Data Segment都是共享的 如果定义一个函数 在各线程中都
  • 死锁的四个必要条件

    死锁产生的四个必要条件 互斥条件 xff1a 资源是独占的且排他使用 xff0c 进程互斥使用资源 xff0c 即任意时刻一个资源只能给一个进程使用 xff0c 其他进程若申请一个资源 xff0c 而该资源被另一进程占有时 xff0c 则申
  • 线程安全与可重入函数的区别

    线程安全 xff1a 一般来讲就是一个代码块被多个并发线程反复调用时会一直产生正确的结果 如何确保线程安全 xff1a 确保线程安全 主要 考虑线程之间共享变量的安全 xff0c 每个线程私有的内容包括 xff1a 线程id xff0c e
  • Linux模拟实现sleep

    工作原理 linux中的sleep函数能够让程序休眠一定的秒数 xff0c 到时间后自动恢复运行 实现思路 设定睡眠的秒数 睡眠 xff08 挂起 xff09 恢复运行实现机制 设定睡眠的秒数 xff1a 采用alarm 函数设定需要睡眠的
  • 基于ESP32C3处理器创建Hello World工程-并使用OpenOCD进行Debug

    1 编程环境 1 1 硬件 序号 名称 描述 备注 1 ESP C3 12F KIT 深圳安信可开发的基于其自家ESP C3 12F模块的开发板 淘宝购买 2 ESP Prog 乐鑫官方推出基于FT2232HL接口芯片的JTAG调试器 淘宝
  • 平衡二叉树旋转详解

    平衡二叉树的定义 xff08 AVL xff09 定义 平衡二叉树或者是一棵空树 xff0c 或者满足以下的性质 xff1a 它的左子树和右子树的高度之差的绝对值不超过1 xff0c 并且左子树和右子树也是一个平衡二叉树 平衡因子 左子树高
  • Linux进程组,作业,会话,作业控制详解

    进程组 xff08 1 xff09 每个进程除了有一个进程id之外还属于进程组 xff0c 进程组是一个或者多个进程的集合 xff0c 通常 xff0c 他们与同一作业相关联 xff0c 可以接收来自同一终端的各种信号 xff08 2 xf
  • 如何写一个linux精灵进程

    什么是精灵进程 精灵进程也称守护进程 xff08 Daemon xff09 xff1a 是运行在后台的一种特殊进程 xff0c 它独立于控制终端并周期性的执行某种任务 xff0c 或等待处理某些发生的事件 Linux大多数服务器就是用精灵进
  • TCP的四种定时器

    TCP使用的四种定时器 xff08 Timer xff09 重传计时器 xff08 Retransmission Timer xff09 坚持计时器 xff08 Persistent Timer xff09 保活计时器 xff08 keep
  • Linux进程池与线程池以及线程池的简单实现

    通过动态创建子进程 xff08 或者子线程 xff09 来实现并发服务器的 这样做有如下缺点 xff1a 1 动态创建进程 xff08 或线程 xff09 是比较耗费时间的 xff0c 这将导致较慢的客户响应 2 动态创建的子进程 xff0
  • linux下vim中多行注释和删除多行注释

    多行注释 xff1a a 按下Ctrl 43 v xff0c 进入列模式 b 在行首选择需要注释的行 c 按下 I xff0c 进入插入模式 xff1b d 然后输入注释符 xff08 等 xff09 e 按下 Esc 键 删除多行注释 x
  • socket编程以及select、epoll、poll示例详解

    socket编程 socket这个词可以表示很多概念 xff0c 在TCP IP协议中 IP地址 43 TCP或UDP端口号 唯一标识网络通讯中的一个进程 xff0c IP 43 端口号 就称为socket 在TCP协议中 xff0c 建立