高级应用一:非阻塞connect
connect系统调用的man手册中有如下的一段内容:
  1. EINPROGRESS
  2. The socket is non-blocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing.
  3. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is
  4. zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).

这段话描述了connect出错时的一种errno值:EINPROGRESS这种错误发生在对非阻塞的connect,而连接又没有建立时。根据 man 文档解释,在这种情况下我们可以调用 select 、 poll等函数来监听这个连接失败的socket上的可写事件。当select、poll等函数返回后,再利用 getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功,否则连接失败。

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <netinet/in.h>
  4. #include <arpa/inet.h>
  5. #include <stdlib.h>
  6. #include <assert.h>
  7. #include <stdio.h>
  8. #include <time.h>
  9. #include <errno.h>
  10. #include <fcntl.h>
  11. #include <sys/ioctl.h>
  12. #include <unistd.h>
  13. #include <string.h>
  14.  
  15. #define BUFFER_SIZE 1023
  16.  
  17. int setnonblocking( int fd )
  18. {
  19. int old_option = fcntl( fd, F_GETFL );
  20. int new_option = old_option | O_NONBLOCK;
  21. fcntl( fd, F_SETFL, new_option );
  22. return old_option;
  23. }
  24.  
  25. /*超时连接函数,参数分别是服务器的IP地址、端口号和超时时间(毫秒)。函数成功时返回已经处于连接状态的socket,失败则返回-1*/
  26. int unblock_connect( const char* ip, int port, int time )
  27. {
  28. int ret = 0;
  29. struct sockaddr_in address;
  30. bzero( &address, sizeof( address ) );
  31. address.sin_family = AF_INET;
  32. inet_pton( AF_INET, ip, &address.sin_addr );
  33. address.sin_port = htons( port );
  34.  
  35. int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
  36. int fdopt = setnonblocking( sockfd );
  37. ret = connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) );
  38. if ( ret == 0 )
  39. {
  40. /*如果连接成功,则恢复sockfd的属性,并立即返回之*/
  41. printf( "connect with server immediately\n" );
  42. fcntl( sockfd, F_SETFL, fdopt );
  43. return sockfd;
  44. }
  45. else if ( errno != EINPROGRESS )
  46. {
  47. /*如果连接没有立即建立,那么只有当errno是EINPROGRESS时才表示连接还在进行,否则出错返回*/
  48. printf( "unblock connect not support\n" );
  49. return -1;
  50. }
  51.  
  52. fd_set readfds;
  53. fd_set writefds;
  54. struct timeval timeout;
  55.  
  56. FD_ZERO( &readfds );
  57. FD_SET( sockfd, &writefds );
  58.  
  59. timeout.tv_sec = time;
  60. timeout.tv_usec = 0;
  61.  
  62. ret = select( sockfd + 1, NULL, &writefds, NULL, &timeout );
  63. if ( ret <= 0 )
  64. {
  65. /* select超时或者出错,立即返回*/
  66. printf( "connection time out\n" );
  67. close( sockfd );
  68. return -1;
  69. }
  70.  
  71. if ( ! FD_ISSET( sockfd, &writefds ) )
  72. {
  73. printf( "no events on sockfd found\n" );
  74. close( sockfd );
  75. return -1;
  76. }
  77.  
  78. int error = 0;
  79. socklen_t length = sizeof( error );
  80. /*调用getsockopt来获取并清除sockfd上的错误*/
  81. if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length ) < 0 )
  82. {
  83. printf( "get socket option failed\n" );
  84. close( sockfd );
  85. return -1;
  86. }
  87. /*错误码不为0表示连接出错*/
  88. if( error != 0 )
  89. {
  90. printf( "connection failed after select with the error: %d \n", error );
  91. close( sockfd );
  92. return -1;
  93. }
  94. /*连接成功*/
  95. printf( "connection ready after select with the socket: %d \n", sockfd );
  96. fcntl( sockfd, F_SETFL, fdopt );
  97. return sockfd;
  98. }
  99.  
  100. int main( int argc, char* argv[] )
  101. {
  102. if( argc <= 2 )
  103. {
  104. printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
  105. return 1;
  106. }
  107. const char* ip = argv[1];
  108. int port = atoi( argv[2] );
  109.  
  110. int sockfd = unblock_connect( ip, port, 10 );
  111. if ( sockfd < 0 )
  112. {
  113. return 1;
  114. }
  115. close( sockfd );
  116. return 0;
  117. }

非阻塞connect的细节:

  • 尽管套接字是非阻塞的,如果连接到的服务器在同一个主机上,那么当我们调用connect时,连接通常立即建立,我们必须处理这种情况。
  • 源自Berkeley的实现(和POSIX)有关于select和非阻塞connect的以下两个规则:(1)当连接成功建立时,描述符变为可写。 (2)当连接建立遇到错误时,描述符变为既可读又可写。
       对于阻塞的socket,如果其上的connect调用在TCP三次握手完成前被中断(譬如说捕获了某个信号),将会发生什么呢? 
       假设被中断的connect调用不由内核自动重启,那么它将返回EINTR ,我们不能再次调用connect 等待未完成的连接继续完成。这样做将导致返回EADDRINUSE 错误。这种情况下我们只能调用select,连接建立成功时select返回socket可写条件,连接建立失败时,select返回socket可读又可写条件。

高级应用二:同时处理TCP和UDP服务

在此之前,我们讨论的服务器程序都只监听一个端口。在实际应用中,有不少服务器程序能同时监听多个端口,比如超组服务xinet。
       从bind系统调用的参数看,一个socket只能绑定一个socket地址,即一个socket只能用来监听一个端口。因此,服务器如果要监听多个端口就必须创建多个socket,并将它们分别绑定到各个端口上。这样一来,服务器程序就需要同时管理多个监听socket,I/O复用技术就有了用武之地。
       另外,即使是同一个端口,如果服务器要同时处理该端口上TCP和UPD请求,也是需要创建两个不同的socket,并将它们都绑到该端口上。

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <netinet/in.h>
  4. #include <arpa/inet.h>
  5. #include <assert.h>
  6. #include <stdio.h>
  7. #include <unistd.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <fcntl.h>
  11. #include <stdlib.h>
  12. #include <sys/epoll.h>
  13. #include <pthread.h>
  14.  
  15. #define MAX_EVENT_NUMBER 1024
  16. #define TCP_BUFFER_SIZE 512
  17. #define UDP_BUFFER_SIZE 1024
  18.  
  19. int setnonblocking( int fd )
  20. {
  21. int old_option = fcntl( fd, F_GETFL );
  22. int new_option = old_option | O_NONBLOCK;
  23. fcntl( fd, F_SETFL, new_option );
  24. return old_option;
  25. }
  26.  
  27. void addfd( int epollfd, int fd )
  28. {
  29. epoll_event event;
  30. event.data.fd = fd;
  31. //event.events = EPOLLIN | EPOLLET;
  32. event.events = EPOLLIN;
  33. epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
  34. setnonblocking( fd );
  35. }
  36.  
  37. int main( int argc, char* argv[] )
  38. {
  39. if( argc <= 2 )
  40. {
  41. printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
  42. return 1;
  43. }
  44. const char* ip = argv[1];
  45. int port = atoi( argv[2] );
  46.  
  47. int ret = 0;
  48. struct sockaddr_in address;
  49. bzero( &address, sizeof( address ) );
  50. address.sin_family = AF_INET;
  51. inet_pton( AF_INET, ip, &address.sin_addr );
  52. address.sin_port = htons( port );
  53.  
  54. int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
  55. assert( listenfd >= 0 );
  56.  
  57. ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
  58. assert( ret != -1 );
  59.  
  60. ret = listen( listenfd, 5 );
  61. assert( ret != -1 );
  62.  
  63. bzero( &address, sizeof( address ) );
  64. address.sin_family = AF_INET;
  65. inet_pton( AF_INET, ip, &address.sin_addr );
  66. address.sin_port = htons( port );
  67. int udpfd = socket( PF_INET, SOCK_DGRAM, 0 );
  68. assert( udpfd >= 0 );
  69.  
  70. ret = bind( udpfd, ( struct sockaddr* )&address, sizeof( address ) );
  71. assert( ret != -1 );
  72.  
  73. epoll_event events[ MAX_EVENT_NUMBER ];
  74. int epollfd = epoll_create( 5 );
  75. assert( epollfd != -1 );
  76. addfd( epollfd, listenfd );
  77. addfd( epollfd, udpfd );
  78.  
  79. while( 1 )
  80. {
  81. int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
  82. if ( number < 0 )
  83. {
  84. printf( "epoll failure\n" );
  85. break;
  86. }
  87.  
  88. for ( int i = 0; i < number; i++ )
  89. {
  90. int sockfd = events[i].data.fd;
  91. if ( sockfd == listenfd )
  92. {
  93. struct sockaddr_in client_address;
  94. socklen_t client_addrlength = sizeof( client_address );
  95. int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
  96. addfd( epollfd, connfd );
  97. }
  98. else if ( sockfd == udpfd )
  99. {
  100. char buf[ UDP_BUFFER_SIZE ];
  101. memset( buf, '\0', UDP_BUFFER_SIZE );
  102. struct sockaddr_in client_address;
  103. socklen_t client_addrlength = sizeof( client_address );
  104.  
  105. ret = recvfrom( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, &client_addrlength );
  106. if( ret > 0 )
  107. {
  108. sendto( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, client_addrlength );
  109. }
  110. }
  111. else if ( events[i].events & EPOLLIN )
  112. {
  113. char buf[ TCP_BUFFER_SIZE ];
  114. while( 1 )
  115. {
  116. memset( buf, '\0', TCP_BUFFER_SIZE );
  117. ret = recv( sockfd, buf, TCP_BUFFER_SIZE-1, 0 );
  118. if( ret < 0 )
  119. {
  120. if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
  121. {
  122. break;
  123. }
  124. close( sockfd );
  125. break;
  126. }
  127. else if( ret == 0 )
  128. {
  129. close( sockfd );
  130. }
  131. else
  132. {
  133. send( sockfd, buf, ret, 0 );
  134. }
  135. }
  136. }
  137. else
  138. {
  139. printf( "something else happened \n" );
  140. }
  141. }
  142. }
  143.  
  144. close( listenfd );
  145. return 0;
  146. }

Linux 高性能服务器编程——I/O复用的高级应用的更多相关文章

  1. Linux 高性能服务器编程——I/O复用

    问题聚焦:     前篇提到了I/O处理单元的四种I/O模型.     本篇详细介绍实现这些I/O模型所用到的相关技术.     核心思想:I/O复用 使用情景: 客户端程序要同时处理多个socket ...

  2. Linux 高性能服务器编程——高性能服务器程序框架

    问题聚焦:     核心章节.     服务器一般分为如下三个主要模块:I/O处理单元(四种I/O模型,两种高效事件处理模块),逻辑单元(两种高效并发模式,有效状态机)和存储单元(不讨论). 服务器模 ...

  3. Linux 高性能服务器编程——Linux网络编程基础API

    问题聚焦:     这节介绍的不仅是网络编程的几个API     更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系.     这节主要介绍三个方面的内容:套接字(so ...

  4. linux高性能服务器编程

    <Linux高性能服务器编程>:当当网.亚马逊 目录: 第一章:tcp/ip协议族 第二章:ip协议族 第三章:tcp协议详解 第四章:tcp/ip通信案例:访问Internet 第五章: ...

  5. Linux 高性能服务器编程——多线程编程

    问题聚焦:     在简单地介绍线程的基本知识之后,主要讨论三个方面的内容:    1 创建线程和结束线程:    2 读取和设置线程属性:    3 线程同步方式:POSIX信号量,互斥锁和条件变量 ...

  6. Linux 高性能服务器编程——多进程编程

    问题聚焦:     进程是Linux操作系统环境的基础.     本篇讨论以下几个内容,同时也是面试经常被问到的一些问题:     1 复制进程映像的fork系统调用和替换进程映像的exec系列系统调 ...

  7. Linux 高性能服务器编程——Linux服务器程序规范

    问题聚焦:     除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范.     工欲善其事,必先利其器,这篇主要来探 ...

  8. Linux 高性能服务器编程——TCP协议详解

    问题聚焦:     本节从如下四个方面讨论TCP协议:     TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流     TCP状态转移过程:TCP连接的任意一 ...

  9. Linux 高性能服务器编程——IP协议详解

    1 IP服务特点 IP协议是TCP/IP协议族的动力,它为上层协议提供无状态.无连接.不可靠的服务. 无状态:IP通信双方不同步传输数据的状态信息,因此IP数据包的发送.传输和接收都是无序的.     ...

随机推荐

  1. [C#]使用 Jenkins 为 .Net Core 实现持续集成/部署

    在前后端分离开发的项目当中为了避免重复构建发布,我们需要部署一个持续发布环境,而目前的开发环境服务器都是基于 CentOS 的,因此每次在本地发布之后还需要打包,上传,部署,十分繁琐.故这里采用了比较 ...

  2. [LeetCode] String Compression 字符串压缩

    Given an array of characters, compress it in-place. The length after compression must always be smal ...

  3. [LeetCode] Decode Ways II 解码方法之二

    A message containing letters from A-Z is being encoded to numbers using the following mapping way: ' ...

  4. 实验吧_密码忘记了(vim编辑器+代码审计)&天网管理系统(php弱比较+反序列化)

    密码忘记了 一开始尝试了各种注入发现都无效,在网页源码中找到了admin 的地址,输入地址栏发现并没有什么有用的信息,随便输个邮箱,网页返回了一个地址 ./step2.php?email=youmai ...

  5. 是否有必要学习使用纯Verilog写一个SDRAM控制器

    在做这个SDRAM控制器之前,博主有一个疑问,对于学生来说,是否有必要学习用纯Verilog写一个SDRAM控制器?因为目前X家和A家都有了DDR IP Core,对于要实现一个应用可以直接调用IP ...

  6. [POI 2004]ZAW

    Description 在 Byte 山的山脚下有一个洞穴入口. 这个洞穴由复杂的洞室经过隧道连接构成. 洞穴的入口是 1 号点.两个洞室要么就通过隧道连接起来,要么就经过若干隧道间接的相连. 现在决 ...

  7. [Luogu 3807]【模板】卢卡斯定理

    Description 给定n,m,p(1≤n,m,p≤10​^5​​) 求 C_{n+m}^{m} \mod p 保证P为prime C表示组合数. 一个测试点内包含多组数据. Input 第一行一 ...

  8. Codeforces Round #407 (Div. 1)

    人傻不会B 写了C正解结果因为数组开小最后RE了 疯狂掉分 AC:A Rank:392 Rating: 2191-92->2099 A. Functions again 题目大意:给定一个长度为 ...

  9. hdu5666 BestCoder Round #80

    Segment  Accepts: 418  Submissions: 2020  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: 6553 ...

  10. Python【第二课】 字符串,列表,字典,集合,文件操作

    本篇内容 字符串操作 列表,元组操作 字典操作 集合操作 文件操作 其他 1.字符串操作 1.1 字符串定义 特性:不可修改 字符串是 Python 中最常用的数据类型.我们可以使用引号('或&quo ...