首页 > 编程语言 >深入研究socket编程(5)——I/O复用的高级应用

深入研究socket编程(5)——I/O复用的高级应用

时间:2022-12-15 20:08:47浏览次数:35  
标签:socket int 编程 复用 connect address sockfd include


高级应用一:非阻塞connect




connect系统调用的man手册中有如下的一段内容:


[cpp]  ​​view plain​​ ​​copy​​ ​​print



  1. EINPROGRESS  
  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. for the failure).  


       这段话描述了connect出错时的一种errno值: EINPROGRESS

这种错误发生在对非阻塞的connect,而连接又没有建立时

。 根据 man 文档解释,在这种情况下我们可以调用 select 、 poll等函数来监听这个连接失败的socket上的可写事件。当select、poll等函数返回后,再利用 getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功,否则连接失败。

[cpp]  ​​view plain​​ ​​copy​​ ​​print



  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. 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. struct sockaddr* )&address, sizeof( address ) );  
  38. if ( ret == 0 )  
  39.     {  
  40. /*如果连接成功,则恢复sockfd的属性,并立即返回之*/  
  41. "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. "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. "connection time out\n" );  
  67.         close( sockfd );  
  68. return -1;  
  69.     }  
  70.   
  71. if ( ! FD_ISSET( sockfd, &writefds  ) )  
  72.     {  
  73. "no events on sockfd found\n" );  
  74.         close( sockfd );  
  75. return -1;  
  76.     }  
  77.   
  78. int error = 0;  
  79. sizeof( error );  
  80. /*调用getsockopt来获取并清除sockfd上的错误*/  
  81. if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length ) < 0 )  
  82.     {  
  83. "get socket option failed\n" );  
  84.         close( sockfd );  
  85. return -1;  
  86.     }  
  87. /*错误码不为0表示连接出错*/  
  88. if( error != 0 )  
  89.     {  
  90. "connection failed after select with the error: %d \n", error );  
  91.         close( sockfd );  
  92. return -1;  
  93.     }  
  94. /*连接成功*/  
  95. "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. "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,并将它们都绑到该端口上。

[cpp]  ​​view plain​​ ​​copy​​ ​​prin



  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. "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. 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. struct sockaddr* )&address, sizeof( address ) );  
  58.     assert( ret != -1 );  
  59.   
  60.     ret = listen( listenfd, 5 );  
  61.     assert( ret != -1 );  
  62.   
  63. 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. 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. "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. 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. '\0', UDP_BUFFER_SIZE );  
  102. struct sockaddr_in client_address;  
  103. sizeof( client_address );  
  104.   
  105. struct sockaddr* )&client_address, &client_addrlength );  
  106. if( ret > 0 )  
  107.                 {  
  108. 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. '\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. "something else happened \n" );  
  140.             }  
  141.         }  
  142.     }  
  143.   
  144.     close( listenfd );  
  145. return 0;  
  146. }  

标签:socket,int,编程,复用,connect,address,sockfd,include
From: https://blog.51cto.com/u_15133569/5945872

相关文章

  • Python编程中的常见语句
    4.1 if条件判断语句4.1.1 if条件判断语句单分支◆单分支格式:if判断条件:语句块1……else:语句块2……Ø例:name=input('请输入您的用户名:')ifname=='admin':    ......
  • 图形用户界面(GUI)编程可以学习C++ Builder,多图、实例、书籍
    个人觉得SDK纯API方式编写Windows程序已经过时了,效率太低,了解一下原理就可以了,主要是消息机制。图形用户界面(GUI)编程可以学习C++Builder,架构先进(和C#一样拖控件),入门比较......
  • Java 多线程编程
      ava给多线程编程提供了内置的支持。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的......
  • shell编程中常见使用命令
    awk工作原理awk-F":"'{print$1,$3}'access.log(1)awk使用一行作为输入,并将这一行赋给变量$0,每一行可称作为一个记录,以换行符结束(2)然后,行被空格分解成字段,每个字段......
  • 进程间通信-socketpair
    最近在看libcontainer中nsexec.c的实现,看到init进程的parent与child、grandchild之间的双工通信使用了socketpair。socketpair的使用与fifo类似,在不具名的情况下可以实现父......
  • goLang包以及并发编程
    1包包可以区分命名空间,一个文件夹中不能有两个同名文件,go中创建一个包一般是创建一个文件夹,在该文件夹里面的go文件中使用关键字package声明包名称,通常文件夹名称和包名称......
  • vue Socket-io使用
    api文档:https://socket.io/docs/v4/client-api/只需要下载它socket.io-client最新版本,可以不用vue-socket-io"socket.io-client":"^4.5.4",连接socketimport{io......
  • 【JUC】并发编程初探
    目录1、Java——天生的多线程1.1main:主线程1.2ReferenceHandle1.3Finalizer1.4SignalDispatcher1.5AttachListenner1.6MonitorCtrl-Break1.7线程1.7.1查看线程......
  • SECTION 15 函数和函数式编程(二)
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • Java Socket网络编程
    1.TCP流式SocketTCP是TCP/IP体系结构中位于传输层的面向连接的协议,提供可靠的字节流传输。通信双方需要建立连接,发送端的所有数据按顺序发送,接受端按顺序接收。......