I/O复用之epoll系统调用

1、内核事件表

    epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都是重复传入描述符或者时间集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符使用如下的epoll_create函数来创建:

 C++ Code 
1
2
#include<sys/epoll.h>
int epoll_create( int size);
    size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。

   下面的函数用来操作epoll的内核事件表:

 C++ Code 
1
2
#include<sys/epoll.h>
int epoll_ctl( int epfd,  int op,  int fd,  struct epoll_event *event);
    fd参数是要操作的文件描述符,op参数则指定操作类型,操作类型有如下3种:

   ?EPOLL_CTL_ADD ,往事件表中注册fd上的事件。

   ?EPOLL_CTL_MOD,修改fd上的注册事件。

   ?EPOLL_CTL_DEL,删除fd上的注册事件。

  event参数指定事件,它是epoll_event结构指针类型。epoll_event的定义如下:

 

 C++ Code 
1
2
3
4
5
struct epoll_event
{
    __uint32_t event;   //epoll事件
    epoll_data_t data;  //用户数量
};
    其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”,比如epoll的数据可读事件时EPOLLIN。但epoll有两个额外的基本类型——EPOLLET和EPOLLONSHOT。它们对于epoll的高效运作非常有关键,我们将在后面讨论它们。data成员用于存储用户数据,其类型epoll_data_t的定义如下:

 

 C++ Code 
1
2
3
4
5
6
7
typedef  union epoll_data
{
     void *ptr;
     int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
   epoll_data_t 是一个联合体,其4个成员使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体,我们不能同时使用其ptr成员和fd成员,因此,如果要将文件描述符和用户数据关联起来,以实现快速的数据访问,只能使用其他手段,比如放弃使用epoll_data_t的fd成员,而在ptr指向的用户数据中包含fd。

   epoll_ctl 成功时返回0,失败则返回-1,并设置errno。


2、epoll_wait函数

    epoll系列系统调用的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件,其原型如下:

 

 C++ Code 
1
2
#include<sys/epoll.h>
int epoll_wait( int epfd,  struct epoll_event *events,  int maxvents,  int timeout);
   该函数成功时返回就绪的文件描述符的个数,失败返回-1并设置errno。

    关于该函数的参数,我们从后往前讨论。timeout参数的含义与poll接口的timeout参数相同。maxevents参数指定最多监听多少个事件,它必须大于0.

   epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪时间,而不像select和poll的数组参数那样即用于用户注册的事件,又用于输出内核检测的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。

   poll和epoll在使用上的差别   

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//如何索引poll返回的就绪文件描述
int ret = poll(fds, MAX_EVENT_NUMBER, - 1);
//必须遍历所有已注册文件描述符并找到其中的就绪这
//(当然,可以利用ret来稍做优化)
for( int i =  0; i < MAX_EVENT_NUMBER; ++i)
{
     if(fds[i].revents & POLLIN)
    {
         int sockfd = fds[i].fd;
         //处理sockfd
    }
}

//如何索引epoll返回的就绪文件描述符
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, - 1);
//仅遍历就绪的ret个文件描述符
for( int i =  0; i < ret; i++)
{
     int sockfd = events[i].data.fd;
     //sockfd肯定就绪,直接处理
}

    3、LT和ET模式

    epoll对文件描述符的操作有两种模式:LT(Level Trigger,电平触发)模式和ET(Edge Trigger,边沿触发)模式,LT模式是默认的工作模式,这种模式下epoll相当于效率较高的poll。当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高效工作模式。

   对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,知道该事件被处理。而对采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件,可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。

    体现LT和ET工作方式的差异代码如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER  1024
#define BUFFER_SIZE  10
//将文件描述符设置成非阻塞
int setnonblocking(  int fd )
{
     int old_option = fcntl( fd, F_GETFL );
     int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
     return old_option;
}
//将文件描述符fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中,参数enable_et
//指定是否对fd启用ET模式
void addfd(  int epollfd,  int fd,  bool enable_et )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
     if( enable_et )
    {
        event.events |= EPOLLET;  //ET模式
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}
//LT模式的工作流程
void lt( epoll_event *events,  int number,  int epollfd,  int listenfd )
{
     char buf[ BUFFER_SIZE ];
     for (  int i =  0; i < number; i++ )
    {
         int sockfd = events[i].data.fd;
         if ( sockfd == listenfd )
        {
             struct sockaddr_in client_address;
            socklen_t client_addrlength =  sizeof( client_address );
             int connfd = accept( listenfd, (  struct sockaddr * )&client_address, &client_addrlength );
            addfd( epollfd, connfd,  false );
             //对connfd禁用ET模式
        }
         else  if ( events[i].events & EPOLLIN )
        {
         //只要socket读缓存中还有未读出的数据,这段代码被触发
            printf(  "event trigger once\n" );
            memset( buf,  '\0', BUFFER_SIZE );
             int ret = recv( sockfd, buf, BUFFER_SIZE -  10 );
             if( ret <=  0 )
            {
                close( sockfd );
                 continue;
            }
            printf(  "get %d bytes of content: %s\n", ret, buf );
        }
         else
        {
            printf(  "something else happened \n" );
        }
    }
}
//ET模式的工作流程
void et( epoll_event *events,  int number,  int epollfd,  int listenfd )
{
     char buf[ BUFFER_SIZE ];
     for (  int i =  0; i < number; i++ )
    {
         int sockfd = events[i].data.fd;
         if ( sockfd == listenfd )
        {
             struct sockaddr_in client_address;
            socklen_t client_addrlength =  sizeof( client_address );
             int connfd = accept( listenfd, (  struct sockaddr * )&client_address, &client_addrlength );
            addfd( epollfd, connfd,  true );
        }
         else  if ( events[i].events & EPOLLIN )
        {
         //这段代码不会被重复触发,所以我们循环读取数据
         //以确保把socket读缓存中的所有数据独处
            printf(  "event trigger once\n" );
             while1 )
            {
                memset( buf,  '\0', BUFFER_SIZE );
                 int ret = recv( sockfd, buf, BUFFER_SIZE -  10 );
                 if( ret <  0 )
                {
                 //对于非阻塞IO,下面的条件成立表示数据已经全部读取完毕。此后epoll
                 //就能再次触发sockfd上的EPOLLIN时间,以驱动下一次读操作。
                     if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {
                        printf(  "read later\n" );
                         break;
                    }
                    close( sockfd );
                     break;
                }
                 else  if( ret ==  0 )
                {
                    close( sockfd );
                }
                 else
                {
                    printf(  "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
         else
        {
            printf(  "something else happened \n" );
        }
    }
}

int main(  int argc,  char *argv[] )
{
     if( argc <=  2 )
    {
        printf(  "usage: %s ip_address port_number\n", basename( argv[ 0] ) );
         return  1;
    }
     const  char *ip = argv[ 1];
     int port = atoi( argv[ 2] );

     int ret =  0;
     struct sockaddr_in address;
    bzero( &address,  sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

     int listenfd = socket( PF_INET, SOCK_STREAM,  0 );
    assert( listenfd >=  0 );

    ret = bind( listenfd, (  struct sockaddr * )&address,  sizeof( address ) );
    assert( ret != - 1 );

    ret = listen( listenfd,  5 );
    assert( ret != - 1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
     int epollfd = epoll_create(  5 );
    assert( epollfd != - 1 );
    addfd( epollfd, listenfd,  true );

     while1 )
    {
         int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, - 1 );  //阻塞于epoll事件
         if ( ret <  0 )
        {
            printf(  "epoll failure\n" );
             break;
        }

        lt( events, ret, epollfd, listenfd );  //使用LT模式
         //et( events, ret, epollfd, listenfd );//使用ET模式
    }

    close( listenfd );
     return  0;
}
    读者不妨运行一下这段代码,然后telnet到这个服务器程序上并一次传输超过10个字节的数据,然后比较LT模式和ET模式的异同。你会发现,ET模式下事件被触发的次数要比LT模式下少很多。

    每个使用ET模式的文件描述符都应该是非阻塞的。如果文件描述符是阻塞的,那么读或者写操作将会因为没有后续的事件而一直处理阻塞状态(饥渴状态)。


    4、EPOLLONESHOT事件(防止两个线程操作一个socket)

     即使我们使用ET模式,一个socket上的某个时间还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个socket上色数据后开始处理这些数据,而在数据的处理过程中该socket上又有心数据可读(EPOLLIN再次触发),此时另外一个线程被唤醒来读取这些信的数据。于是就出现了两个线程同时操作一个socket局面。这当然不是我们期望的。我们期望的是一个socket连接在任一时刻都只被一个线程处理。这一点可以使用epoll的EPOLLONESHOT事件实现。

    对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读,可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕后,该线程就应该立即重置这个socket的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。

   如下是EPOLLONETSHOT事件的使用。

 

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER  1024
#define BUFFER_SIZE  1024
struct fds
{
     int epollfd;
     int sockfd;
};

int setnonblocking(  int fd )
{
     int old_option = fcntl( fd, F_GETFL );
     int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
     return old_option;
}
//将fd上的EPOLLIN和EPOLLET事件注册到epollfd指示的epoll内核事件表中
//参数oneshot指定是否注册fd上的EPOLLONESHOT事件。
void addfd(  int epollfd,  int fd,  bool oneshot )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
     if( oneshot )
    {
        event.events |= EPOLLONESHOT;  //设置oneshot
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}
//重置fd上的事件,这样操作之后,尽管fd上的EPOLLONESHOT事件被注册,但是
//操作系统仍然会触发fd上的EPOLLIN事件,且只触发一次
void reset_oneshot(  int epollfd,  int fd )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
}
//工作线程
void *worker(  void *arg )
{
     int sockfd = ( (fds *)arg )->sockfd;
     int epollfd = ( (fds *)arg )->epollfd;
    printf(  "start new thread to receive data on fd: %d\n", sockfd );
     char buf[ BUFFER_SIZE ];
    memset( buf,  '\0', BUFFER_SIZE );
     //循环读取sockfd上的数据,直到遇到EAGAIN错误
     while1 )
    {
         int ret = recv( sockfd, buf, BUFFER_SIZE -  10 );
         if( ret ==  0 )
        {
            close( sockfd );
            printf(  "foreiner closed the connection\n" );
             break;
        }
         else  if( ret <  0 )
        {
             if( errno == EAGAIN )
            {
                reset_oneshot( epollfd, sockfd );
                printf(  "read later\n" );
                 break;
            }
        }
         else
        {
            printf(  "get content: %s\n", buf );
            sleep(  5 );
        }
    }
    printf(  "end thread receiving data on fd: %d\n", sockfd );
}

int main(  int argc,  char *argv[] )
{
     if( argc <=  2 )
    {
        printf(  "usage: %s ip_address port_number\n", basename( argv[ 0] ) );
         return  1;
    }
     const  char *ip = argv[ 1];
     int port = atoi( argv[ 2] );

     int ret =  0;
     struct sockaddr_in address;
    bzero( &address,  sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

     int listenfd = socket( PF_INET, SOCK_STREAM,  0 );
    assert( listenfd >=  0 );

    ret = bind( listenfd, (  struct sockaddr * )&address,  sizeof( address ) );
    assert( ret != - 1 );

    ret = listen( listenfd,  5 );
    assert( ret != - 1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
     int epollfd = epoll_create(  5 );
    assert( epollfd != - 1 );
    addfd( epollfd, listenfd,  false );
//监听socke listenfd上是不能注册EPOLLONESHOT事件的,否则应用程序只能处理
//一个客户连接,因为后续的客户连接请求将不再触发listenfd上的EPOLLIN事件
     while1 )
    {
         int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, - 1 );
         if ( ret <  0 )
        {
            printf(  "epoll failure\n" );
             break;
        }

         for (  int i =  0; i < ret; i++ )
        {
             int sockfd = events[i].data.fd;
             if ( sockfd == listenfd )
            {
                 struct sockaddr_in client_address;
                socklen_t client_addrlength =  sizeof( client_address );
                 int connfd = accept( listenfd, (  struct sockaddr * )&client_address, &client_addrlength );
                 //对每个非监听文件描述符都注册EPOLLONESHOT事件
                addfd( epollfd, connfd,  true );
            }
             else  if ( events[i].events & EPOLLIN )
            {
                pthread_t thread;
                fds fds_for_new_worker;
                fds_for_new_worker.epollfd = epollfd;
                fds_for_new_worker.sockfd = sockfd;
                 //新启动一个工作线程为sockfd服务
                pthread_create( &thread,  NULL, worker, (  void * )&fds_for_new_worker );
            }
             else
            {
                printf(  "something else happened \n" );
            }
        }
    }

    close( listenfd );
     return  0;
}
        从工作线程函数worker来看,如果一个工作线程处理完某个socket上的一次请求(我们用休眠5s来模拟这个过程)之后,又接收到该socket上新的客户请求,则该线程将继续为这个socket服务。并且因为该socket上注册了EPOLLONESHOT事件,其他线程没有机会接触到这个socket,如果工作线程等待5s仍然没收到该socket上的下一批客户数据,则它将放弃为该socket服务。同时,它调用reset_oneshot函数来重置该socket上的注册事件,这将使epoll有机会再次检测到该socket上的EPOLLIN事件,进而使得其他线程有机会为该socket服务。

   由此看来,尽管一个socket在不同时间可能被不同的线程处理,但同一时刻肯定只有一个线程为它服务。这就保证了连接完整性,从而避免了很多可能的竞态条件。


    5、I/O复用的应用:同时处理TCP和UDP服务

   从bind系统调用的参数来看,一个socket只能与一个socket地址绑定,即一个socket只能用来监听一个端口。因此,服务器如果要同时监听多个端口,就必须创建多个socket,并将它们分别绑定在各个端口上。这样一来,服务器要同时管理多个监听socket,I/O复用就有用武之地了。另外,即使是同一个端口,如果服务器要同时处理该端口上的TCP和UDP请求,则也需要创建两个不同的socket:一个是流socket,另一个是数据报socket,并将它们都绑定到该端口上。代码如下所示:

   

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER  1024
#define TCP_BUFFER_SIZE  512
#define UDP_BUFFER_SIZE  1024

int setnonblocking(  int fd )
{
     int old_option = fcntl( fd, F_GETFL );
     int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
     return old_option;
}

void addfd(  int epollfd,  int fd )
{
    epoll_event event;
    event.data.fd = fd;
     //event.events = EPOLLIN | EPOLLET;
    event.events = EPOLLIN;
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

int main(  int argc,  char* argv[] )
{
     if( argc <=  2 )
    {
        printf(  "usage: %s ip_address port_number\n", basename( argv[ 0] ) );
         return  1;
    }
     const  char* ip = argv[ 1];
     int port = atoi( argv[ 2] );

     int ret =  0;
     struct sockaddr_in address;
    bzero( &address,  sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
//创建TCP_socket,并将其绑定到端口port上
     int listenfd = socket( PF_INET, SOCK_STREAM,  0 );
    assert( listenfd >=  0 );

    ret = bind( listenfd, (  struct sockaddr* )&address,  sizeof( address ) );
    assert( ret != - 1 );

    ret = listen( listenfd,  5 );
    assert( ret != - 1 );
//创建UDP_socket,并将其绑定到端口port上
    bzero( &address,  sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
     int udpfd = socket( PF_INET, SOCK_DGRAM,  0 );
    assert( udpfd >=  0 );

    ret = bind( udpfd, (  struct sockaddr* )&address,  sizeof( address ) );
    assert( ret != - 1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
     int epollfd = epoll_create(  5 );
    assert( epollfd != - 1 );
     //注册TCP socket和UDPsocket到可读事件上
    addfd( epollfd, listenfd );
    addfd( epollfd, udpfd );

     while1 )
    {
         int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, - 1 ); //阻塞
         if ( number <  0 )
        {
            printf(  "epoll failure\n" );
             break;
        }
    
         for (  int i =  0; i < number; i++ )
        {
             int sockfd = events[i].data.fd;
             if ( sockfd == listenfd )  //接收tcp数据
            {
                 struct sockaddr_in client_address;
                socklen_t client_addrlength =  sizeof( client_address );
                 int connfd = accept( listenfd, (  struct sockaddr* )&client_address, &client_addrlength );
                addfd( epollfd, connfd ); //设置另外读出来
            }
             else  if ( sockfd == udpfd )  //接收udp数据
            {
                 char buf[ UDP_BUFFER_SIZE ];
                memset( buf,  '\0', UDP_BUFFER_SIZE );
                 struct sockaddr_in client_address;
                socklen_t client_addrlength =  sizeof( client_address );

                ret = recvfrom( udpfd, buf, UDP_BUFFER_SIZE- 10, (  struct sockaddr* )&client_address, &client_addrlength );
                 if( ret >  0 )
                {
                    sendto( udpfd, buf, UDP_BUFFER_SIZE- 10, (  struct sockaddr* )&client_address, client_addrlength );
                }
            }
             else  if ( events[i].events & EPOLLIN ) //读取TCP数据
            {
                 char buf[ TCP_BUFFER_SIZE ];
                 while1 )
                {
                    memset( buf,  '\0', TCP_BUFFER_SIZE );
                    ret = recv( sockfd, buf, TCP_BUFFER_SIZE- 10 );
                     if( ret <  0 )
                    {
                         if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                        {
                             break;
                        }
                        close( sockfd );
                         break;
                    }
                     else  if( ret ==  0 )
                    {
                        close( sockfd );
                    }
                     else
                    {
                        send( sockfd, buf, ret,  0 );
                    }
                }
            }
             else
            {
                printf(  "something else happened \n" );
            }
        }
    }

    close( listenfd );
     return  0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值