Linux 高性能服务器编程——I/O复用的高级应用
- EINPROGRESS
- 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.
- 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
- 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,表示连接成功,否则连接失败。
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- #include <assert.h>
- #include <stdio.h>
- #include <time.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <sys/ioctl.h>
- #include <unistd.h>
- #include <string.h>
- #define BUFFER_SIZE 1023
- 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;
- }
- /*超时连接函数,参数分别是服务器的IP地址、端口号和超时时间(毫秒)。函数成功时返回已经处于连接状态的socket,失败则返回-1*/
- int unblock_connect( const char* ip, int port, int time )
- {
- 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 sockfd = socket( PF_INET, SOCK_STREAM, 0 );
- int fdopt = setnonblocking( sockfd );
- ret = connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) );
- if ( ret == 0 )
- {
- /*如果连接成功,则恢复sockfd的属性,并立即返回之*/
- printf( "connect with server immediately\n" );
- fcntl( sockfd, F_SETFL, fdopt );
- return sockfd;
- }
- else if ( errno != EINPROGRESS )
- {
- /*如果连接没有立即建立,那么只有当errno是EINPROGRESS时才表示连接还在进行,否则出错返回*/
- printf( "unblock connect not support\n" );
- return -1;
- }
- fd_set readfds;
- fd_set writefds;
- struct timeval timeout;
- FD_ZERO( &readfds );
- FD_SET( sockfd, &writefds );
- timeout.tv_sec = time;
- timeout.tv_usec = 0;
- ret = select( sockfd + 1, NULL, &writefds, NULL, &timeout );
- if ( ret <= 0 )
- {
- /* select超时或者出错,立即返回*/
- printf( "connection time out\n" );
- close( sockfd );
- return -1;
- }
- if ( ! FD_ISSET( sockfd, &writefds ) )
- {
- printf( "no events on sockfd found\n" );
- close( sockfd );
- return -1;
- }
- int error = 0;
- socklen_t length = sizeof( error );
- /*调用getsockopt来获取并清除sockfd上的错误*/
- if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length ) < 0 )
- {
- printf( "get socket option failed\n" );
- close( sockfd );
- return -1;
- }
- /*错误码不为0表示连接出错*/
- if( error != 0 )
- {
- printf( "connection failed after select with the error: %d \n", error );
- close( sockfd );
- return -1;
- }
- /*连接成功*/
- printf( "connection ready after select with the socket: %d \n", sockfd );
- fcntl( sockfd, F_SETFL, fdopt );
- return 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 sockfd = unblock_connect( ip, port, 10 );
- if ( sockfd < 0 )
- {
- return 1;
- }
- close( sockfd );
- return 0;
- }
非阻塞connect的细节:
- 尽管套接字是非阻塞的,如果连接到的服务器在同一个主机上,那么当我们调用connect时,连接通常立即建立,我们必须处理这种情况。
- 源自Berkeley的实现(和POSIX)有关于select和非阻塞connect的以下两个规则:(1)当连接成功建立时,描述符变为可写。 (2)当连接建立遇到错误时,描述符变为既可读又可写。
在此之前,我们讨论的服务器程序都只监听一个端口。在实际应用中,有不少服务器程序能同时监听多个端口,比如超组服务xinet。
从bind系统调用的参数看,一个socket只能绑定一个socket地址,即一个socket只能用来监听一个端口。因此,服务器如果要监听多个端口就必须创建多个socket,并将它们分别绑定到各个端口上。这样一来,服务器程序就需要同时管理多个监听socket,I/O复用技术就有了用武之地。
另外,即使是同一个端口,如果服务器要同时处理该端口上TCP和UPD请求,也是需要创建两个不同的socket,并将它们都绑到该端口上。
- #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 );
- 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 );
- 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 );
- addfd( epollfd, listenfd );
- addfd( epollfd, udpfd );
- while( 1 )
- {
- 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 )
- {
- 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 )
- {
- 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-1, 0, ( struct sockaddr* )&client_address, &client_addrlength );
- if( ret > 0 )
- {
- sendto( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, client_addrlength );
- }
- }
- else if ( events[i].events & EPOLLIN )
- {
- char buf[ TCP_BUFFER_SIZE ];
- while( 1 )
- {
- memset( buf, '\0', TCP_BUFFER_SIZE );
- ret = recv( sockfd, buf, TCP_BUFFER_SIZE-1, 0 );
- 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;
- }
Linux 高性能服务器编程——I/O复用的高级应用的更多相关文章
- Linux 高性能服务器编程——I/O复用
问题聚焦: 前篇提到了I/O处理单元的四种I/O模型. 本篇详细介绍实现这些I/O模型所用到的相关技术. 核心思想:I/O复用 使用情景: 客户端程序要同时处理多个socket ...
- Linux 高性能服务器编程——高性能服务器程序框架
问题聚焦: 核心章节. 服务器一般分为如下三个主要模块:I/O处理单元(四种I/O模型,两种高效事件处理模块),逻辑单元(两种高效并发模式,有效状态机)和存储单元(不讨论). 服务器模 ...
- Linux 高性能服务器编程——Linux网络编程基础API
问题聚焦: 这节介绍的不仅是网络编程的几个API 更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系. 这节主要介绍三个方面的内容:套接字(so ...
- linux高性能服务器编程
<Linux高性能服务器编程>:当当网.亚马逊 目录: 第一章:tcp/ip协议族 第二章:ip协议族 第三章:tcp协议详解 第四章:tcp/ip通信案例:访问Internet 第五章: ...
- Linux 高性能服务器编程——多线程编程
问题聚焦: 在简单地介绍线程的基本知识之后,主要讨论三个方面的内容: 1 创建线程和结束线程: 2 读取和设置线程属性: 3 线程同步方式:POSIX信号量,互斥锁和条件变量 ...
- Linux 高性能服务器编程——多进程编程
问题聚焦: 进程是Linux操作系统环境的基础. 本篇讨论以下几个内容,同时也是面试经常被问到的一些问题: 1 复制进程映像的fork系统调用和替换进程映像的exec系列系统调 ...
- Linux 高性能服务器编程——Linux服务器程序规范
问题聚焦: 除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范. 工欲善其事,必先利其器,这篇主要来探 ...
- Linux 高性能服务器编程——TCP协议详解
问题聚焦: 本节从如下四个方面讨论TCP协议: TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流 TCP状态转移过程:TCP连接的任意一 ...
- Linux 高性能服务器编程——IP协议详解
1 IP服务特点 IP协议是TCP/IP协议族的动力,它为上层协议提供无状态.无连接.不可靠的服务. 无状态:IP通信双方不同步传输数据的状态信息,因此IP数据包的发送.传输和接收都是无序的. ...
随机推荐
- [C#]使用 Jenkins 为 .Net Core 实现持续集成/部署
在前后端分离开发的项目当中为了避免重复构建发布,我们需要部署一个持续发布环境,而目前的开发环境服务器都是基于 CentOS 的,因此每次在本地发布之后还需要打包,上传,部署,十分繁琐.故这里采用了比较 ...
- [LeetCode] String Compression 字符串压缩
Given an array of characters, compress it in-place. The length after compression must always be smal ...
- [LeetCode] Decode Ways II 解码方法之二
A message containing letters from A-Z is being encoded to numbers using the following mapping way: ' ...
- 实验吧_密码忘记了(vim编辑器+代码审计)&天网管理系统(php弱比较+反序列化)
密码忘记了 一开始尝试了各种注入发现都无效,在网页源码中找到了admin 的地址,输入地址栏发现并没有什么有用的信息,随便输个邮箱,网页返回了一个地址 ./step2.php?email=youmai ...
- 是否有必要学习使用纯Verilog写一个SDRAM控制器
在做这个SDRAM控制器之前,博主有一个疑问,对于学生来说,是否有必要学习用纯Verilog写一个SDRAM控制器?因为目前X家和A家都有了DDR IP Core,对于要实现一个应用可以直接调用IP ...
- [POI 2004]ZAW
Description 在 Byte 山的山脚下有一个洞穴入口. 这个洞穴由复杂的洞室经过隧道连接构成. 洞穴的入口是 1 号点.两个洞室要么就通过隧道连接起来,要么就经过若干隧道间接的相连. 现在决 ...
- [Luogu 3807]【模板】卢卡斯定理
Description 给定n,m,p(1≤n,m,p≤10^5) 求 C_{n+m}^{m} \mod p 保证P为prime C表示组合数. 一个测试点内包含多组数据. Input 第一行一 ...
- Codeforces Round #407 (Div. 1)
人傻不会B 写了C正解结果因为数组开小最后RE了 疯狂掉分 AC:A Rank:392 Rating: 2191-92->2099 A. Functions again 题目大意:给定一个长度为 ...
- hdu5666 BestCoder Round #80
Segment Accepts: 418 Submissions: 2020 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 6553 ...
- Python【第二课】 字符串,列表,字典,集合,文件操作
本篇内容 字符串操作 列表,元组操作 字典操作 集合操作 文件操作 其他 1.字符串操作 1.1 字符串定义 特性:不可修改 字符串是 Python 中最常用的数据类型.我们可以使用引号('或&quo ...