tcp/ip

socket

套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行通信。我们可以用套接字中的相关函数来完成通信过程。

套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
1
2
3
一、域(domain)
  域指定套接字通信中使用的网络介质。最常见的套接字域是 AF_INET(IPv4)或者AF_INET6(IPV6),它是指 Internet 网络,许多 Linux 局域网使用的都是该网络,当然,因特网自身用的也是它。

二、类型(type)
流套接字(SOCK_STREAM):
  流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

数据报套接字(SOCK_DGRAM):
  数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

原始套接字(SOCK_RAW):
  原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

三、协议(protocol)
0:
   使用默认协议;

IPPROTO_TCP;
   使用TCP协议;

IPPROTO_UDP;
   使用UDP协议;

四、socket缓冲区以及阻塞模式
1、缓冲区简介
  每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
  read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
在这里插入图片描述

I/O缓冲区在每个TCP套接字中单独存在;
I/O缓冲区在创建套接字时自动生成;
即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
关闭套接字将丢失输入缓冲区中的数据。
2、使用write()/send()发送数据
【阻塞模式下】:

首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据;

如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒;

如果要写入的数据大于缓冲区的最大长度,那么将分批写入。如果要写入的数据大于缓冲区的最大长度,那么将分批写入;直到所有数据被写入缓冲区 write()/send() 才能返回。直到所有数据被写入缓冲区 write()/send() 才能返回。

send()函数默认情况下会使用Nagle算法。Nagle算法通过将未确认的数据存入缓冲区直到积攒到一定数量一起发送的方法,来降低主机发送零碎小数据包的数目。所以假设send()函数发送数据过快的话,该算法会将一些数据打包后统一发出去。通过setsockopt()的TCP_NODELAY选项来禁用Nagle算法。

【非阻塞模式下】:

send()函数的过程仅仅是将数据拷贝到协议栈的缓冲区而已,如果缓冲区可用空间不够,则尽可能拷贝,返回成功拷贝的大小;如果缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN。
3、使用read()/recv()读取数据
【阻塞模式下】:

首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来;

如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。

【非阻塞模式下】:

接收数据时perror时常遇到“Resource temporarilyunavailable”的提示,errno代码为11(EAGAIN)。这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,继续循环接着recv就可以。
4、系统调用read()的返回错误场景
EINTR:在读取到数据以前调用被信号所中断。

EAGAIN:使用 O_NONBLOCK 标志指定了非阻塞式输入输出,但当前没有数据可读或者使用了阻塞操作。

EIO:输入输出错误.可能是正处于后台进程组进程试图读取其控制终端,但读操作无效,或者被信号SIGTTIN所阻塞,或者其进程组是孤儿进程组.也可能执行的是读磁盘或者磁带机这样的底层输入输出错误。

EISDIR:fd 指向一个目录。

EBADF:fd 不是一个合法的文件描述符,或者不是为读操作而打开。

EINVAL:fd 所连接的对象不可读。

EFAULT:buf 超出用户可访问的地址空间。

EWOULDBLOCK:用于非阻塞模式,表示不需要重新读或者写。

五、面试题—>TCP服务端一直sleep,客户端发送数据问题
1、TCP发送数据的过程
  TCP发送数据的大体过程:首先,TCP是有链接的可靠传输协议,所谓可靠也就是说保证客户端发送的数据服务端都能够收到,并且是按序收到。那么对于上面的问题就不可能存在数据的丢弃。那么客户端一直发送数据越来越多怎么办?下面我们分析一下TCP的传输过程。
在这里插入图片描述

  1. 数据首先由应用程序缓冲区复制到发送端的输出缓冲区(位于内核),注意这个过程是用类似write功能的函数完成的。有的人通常看到write成功就以为数据发送到了对端主机,其实这是错误的,write成功仅仅表示数据成功的由应用进程缓冲区复制到了输出缓冲区。

  2. 然后内核协议栈将输出缓冲区中的数据发送到对端主机,注意这个过程不受应用程序控制,而是发送端内核协议栈完成,其中包括使用滑动窗口、拥塞控制等功能。

  3. 数据到达接收端主机的输入缓冲区,注意这个接收过程也不受应用程序控制,而是由接收端内核协议栈完成,其中包括发送ack确认等。

  4. 数据由套接字接收缓冲区复制到接收端应用程序缓冲区,注意这个过程是由类似read等函数来完成。

2、阻塞方式的情况
  阻塞方式下,如果服务端一直sleep不接收数据,而客户端一直write,也就是只能执行上述过程中的前三步,这样最终结果肯定是接收端的输入缓冲区和发送端的输出缓冲区都被填满,这样write就无法继续将数据从应用程序复制到发送端的输出缓冲区了,从而使进程进入睡眠。
在这里插入图片描述

3、非阻塞方式的情况
  非阻塞情况下,服务端一直sleep,客户端一直write数据的结果:开始客户端write成功,随着客户端write,接收端的输入缓冲区和发送端的输出缓冲区会被填满。当发送端的输出缓冲区的可用空间小于write请求写的字节数时,write立即返回-1,并将errno置为EWOULDBLOCK。
在这里插入图片描述

参考:
https://www.cnblogs.com/kex1n/p/6501977.html
https://blog.csdn.net/lianghe_work/article/details/45170359
http://blog.chinaunix.net/uid-28541347-id-4730278.html
https://www.cnblogs.com/suanec/p/4248207.html

tcp server/client

下面用TCP协议编写一个简单的服务器、客户端,其中服务器端一直监听本机的6666号端口。如果收到连接请求,将接收请求并接收客户端发来的消息;客户端与服务器端建立连接。连接建立成功后,读取文件内容(/root/workspace/socket-picture/bizhi.jpg),发送给服务器端,服务器端新建new1.jpg文件,将接收到的文件内容保存到new1.jpg中,new1.jpg在当前目录下;

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>

#define MAXLINE 4096

int main(int argc, char** argv){
    int  listenfd, connfd;
    struct sockaddr_in  servaddr;
    char  buff[4096];
    FILE *fp;
    int  n;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }
    printf("----init socket----\n");

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666);
    //设置端口可重用
    int contain;
    setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &contain, sizeof(int));

    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }
    printf("----bind sucess----\n");

    if( listen(listenfd, 10) == -1){
        printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }
    if((fp = fopen("new1.jpg","ab") ) == NULL )
    {
        printf("File.\n");
        close(listenfd);
        exit(1);
    }

    printf("======waiting for client's request======\n");
    while(1){
        struct sockaddr_in client_addr;
        socklen_t size=sizeof(client_addr);
        if( (connfd = accept(listenfd, (struct sockaddr*)&client_addr, &size)) == -1){
            printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
            continue;
        }
        while(1){
            n = read(connfd, buff, MAXLINE);
            if(n == 0)
                break;
            fwrite(buff, 1, n, fp);
        }
        buff[n] = '\0';
        printf("recv msg from client: %s\n", buff);
        close(connfd);
        fclose(fp);
    }
    close(listenfd);
    return 0;
}
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MAXLINE 4096

int main(int argc, char** argv){
    int   sockfd, len;
    char  buffer[MAXLINE];
    struct sockaddr_in  servaddr;
    FILE *fq;

    if( argc != 2){
        printf("usage: ./client <ipaddress>\n");
        return 0;
    }

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        return 0;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
        printf("inet_pton error for %s\n",argv[1]);
        return 0;
    }

    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }
    if( ( fq = fopen("/root/workspace/socket-picture/bizhi.jpg","rb") ) == NULL ){
        printf("File open.\n");
        close(sockfd);
        exit(1);
    }

    bzero(buffer,sizeof(buffer));
    while(!feof(fq)){
        len = fread(buffer, 1, sizeof(buffer), fq);
        if(len != write(sockfd, buffer, len)){
            printf("write.\n");
            break;
        }
    }
    close(sockfd);
    fclose(fq);

    return 0;
}
all:server client
server:server.o
    g++ -g -o server server.o
client:client.o
    g++ -g -o client client.o
server.o:server.cpp
    g++ -g -c server.cpp
client.o:client.cpp
    g++ -g -c client.cpp
clean:all
    rm all

event-based方法和epoll

epoll是event-based的方法实现异步io/non-blocking io。从Linux kernel 2.5.44之后epoll加入Linux kernel中,代替loop style方法的select和poll,比后者更加高效更适用于高并发多client的应用。loop style方法的时间复杂度为O(n)(因为需要线性地检测指定的file descriptor),而epoll等event-based方法的时间复杂度为O(1)。event-based通过为不同的events设置callback函数,在该event发生的时候自动执行相应函数(epoll uses callbacks in the kernel file structure)。

和epoll类似的其他event-based的方法还有:kqueue(FreeBSD/NetBSD/OpenBSD/Darwin), /dev/poll(Solaris/HPUX), pollset(AIX), Event Completion(Solaris 10), I/O Completion Ports(Windows)等。因此如果程序运行目标平台是Linux(Kernel > 2.5.44)可以使用epoll即可,如果其他Unix平台可以考虑相应的方法(如在FreeBSD等UNIX上使用kqueue)。如果希望使用跨平台可移植的event-based可以使用libevent库,它支持多种方法(select/poll, epoll, kqueue, /dev/poll等)。

epoll提供的系统调用

epoll包含了3个系统调用:epoll_create, epoll_ctl和epoll_wait(需#include )。具体步骤是:首先使用epoll_create创建一个epoll file descriptor;然后使用epoll_ctl添加要监听的IO file descriptor到epoll中;最后循环地调用epoll_wait检测各IO fd相关events的变化,然后采取相应的措施。

epoll_create

int epoll_create(int size); //创建epoll

其中size告知kernel需要为之后添加的IO file descriptor准备的event backing store的大小。Open an epoll file descriptor by requesting the kernel allocate an event backing store dimensioned for size descriptors. The size is not the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures.

从Linux 2.6.8开始size值不再被使用,但是其赋值需要> 0。参考: http://www.kernel.org/doc/man-pages/online/pages/man2/epoll_create.2.html

使用epoll_create创建的epoll file descriptor在程序结束的时候需要使用close()将其关闭,例如:

int epfd;
if ( (epfd = epoll_create(1)) == -1 ) {
perror(“epoll_create failed”);
exit(EXIT_FAILURE);
}

close(epfd);

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // add/update/del IO file descriptor to be watched on the epoll instance



//epfd: 即是如上epoll_create创建的epoll file descriptor

//op:   指定要对指定的fd进行何种操作,支持的操作包括:

            EPOLL_CTL_ADD   将指定的file descriptor添加到epoll中

            EPOLL_CTL_MOD   修改指定file descriptor的event,相当于update

            EPOLL_CTL_DEL   将指定的file descriptor从epoll中清除,the event is ignored and can be NULL

//fd:   要操作的IO file descriptor

//event: 表示the event linked to this file descriptor

其中struct epoll_event的定义如下:

struct epoll_event {

    __uint32_t      events; //epoll events

    epoll_data_t    data;   //user data variable

};

typedef union epoll_data {

    void        *ptr;

    int         fd;

    __uint32_t  u32;

    __uint64_t  u64;

} epoll_data_t;

其中的struct epoll_event的events域是一个bit set(可以通过|操作符进行多赋值),支持的event type有:

EPOLLIN     //ready for read

EPOLLOUT    //ready for write

EPOLLPRI    //urgent data available for read



EPOLLERR    //error condition happened, epoll_wait会默认检测该event不需要设置

EPOLLHUP    //hang up happended on the fd, epoll_wait会默认检测该event不需要设置



EPOLLET     //设置使用Edge Triggered模式,epoll模式使用Level Triggered

更多参看:http://linux.die.net/man/2/epoll_ctl

epoll_ctl的返回值:成功返回0, 发生错误返回-1

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);



//epfd      为如上定义的epoll file descriptor

//events    返回发生改变的events

//maxevents 最多返回events的个数,必须 > 0

//timeout   等待的milliseconds;和poll类似,如果timeout设置为-1则epoll_wait将持续等待下去;如果timeout设置为0,则epoll_wait将立即返回

该函数类似select和poll函数,执行的时候会等待直到epfd定义的指定IO fd的events发生变化或timeout参数指定的milliseconds时间到期才返回。wait and block until events on the watched set happens or timeout expires。

返回值:成功返回number of fd ready for requested io; 0表示在timeout以后没有ready的fd; -1表示发生错误。

epoll检测events改变的两种模式:edge-triggered和level-triggered

调用epoll_wait会返回events发生变化的IO fd,epoll支持两种模式:

§ level triggered:

只要发生的events没有结束,每次调用epoll_wait都显示该events存在。例如:当一个IO fd的状态变为available for reading的时候,调用epoll_wait会将该event返回;如果下次调用epoll_wait的时候该read过程还没有完成,则epoll_wait仍旧会返回该event。

§ edge triggered(EPOLLET )

和level triggered不同,它只在event产生的时候发出event信息,之后即使event没有结束不再发送此信息。例如:当一个IO fd状态变为available for reading的时候,调用epoll_wait会将该event返回;如果下次调用epoll_wait的时候该read过程还没有完成,epoll_wait不会立即返回而是需要等待新的events或直到timetout的时间。Edge Triggered event distribution delivers events only when events happens on the monitored file.

epoll默认采用Level Triggered模式,如果需要对某个IO fd采用Edge Triggered模式,在调用epoll_ctl的时候指定其struct epoll_event的events的时候添加EPOLLET。

epoll实现non-blocking socket实例:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include



#define DEFAULT_PORT    1984    //默认端口

#define BUFF_SIZE       1024    //buffer大小



#define EPOLL_MAXEVENTS 64      //epoll_wait的最多返回的events个数

#define EPOLL_TIMEOUT   5000    //epoll_wait的timeout milliseconds



//函数:设置sock为non-blocking mode

void setSockNonBlock(int sock) {

    int flags;

    flags = fcntl(sock, F_GETFL, 0);

    if (flags < 0) {

        perror("fcntl(F_GETFL) failed");

        exit(EXIT_FAILURE);

    }

    if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {

        perror("fcntl(F_SETFL) failed");

        exit(EXIT_FAILURE);

    }

}



int main(int argc, char *argv[]) {



    //获取自定义端口

    unsigned short int port;

    if (argc == 2) {

        port = atoi(argv[1]);

    } else if (argc < 2) {

        port = DEFAULT_PORT;

    } else {

        fprintf(stderr, "USAGE: %s [port]\n", argv[0]);

        exit(EXIT_FAILURE);

    }



    //创建socket

    int sock;

    if ( (sock = socket(PF_INET, SOCK_STREAM, 0)) == -1 ) {

        perror("socket failed");

        exit(EXIT_FAILURE);

    }

    printf("socket done\n");



    //in case of 'address already in use' error message

    int yes = 1;

    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) {

        perror("setsockopt failed");

        exit(EXIT_FAILURE);

    }



    //设置sock为non-blocking

    setSockNonBlock(sock);



    //创建要bind的socket address

    struct sockaddr_in bind_addr;

    memset(&bind_addr, 0, sizeof(bind_addr));

    bind_addr.sin_family = AF_INET;

    bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //设置接受任意地址

    bind_addr.sin_port = htons(port);               //将host byte order转换为network byte order



    //bind sock到创建的socket address上

    if ( bind(sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1 ) {

        perror("bind failed");

        exit(EXIT_FAILURE);

    }

    printf("bind done\n");



    //listen

    if ( listen(sock, 5) == -1) {

        perror("listen failed");

        exit(EXIT_FAILURE);

    }

    printf("listen done\n");



    //创建epoll (epoll file descriptor)

    int epfd;

    if ( (epfd = epoll_create(1)) == -1 ) {

        perror("epoll_create failed");

        exit(EXIT_FAILURE);

    }

    //将sock添加到epoll中

    struct epoll_event event;

    event.events = EPOLLIN;

    event.data.fd = sock;

    if ( epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &event) == -1 ) {

        perror("epoll_ctl");

        exit(EXIT_FAILURE);

    }



    //初始化epoll_wait的参数

    struct epoll_event events[EPOLL_MAXEVENTS];

    memset(events, 0, sizeof(events));



    //循环侦听

    int conn_sock;

    struct sockaddr_in client_addr;

    socklen_t client_addr_len;

    char client_ip_str[INET_ADDRSTRLEN];

    int res;

    int i;

    char buffer[BUFF_SIZE];

    int recv_size;



    while (1) {



        //每次循环调用依次epoll_wait侦听

        res = epoll_wait(epfd, events, EPOLL_MAXEVENTS, EPOLL_TIMEOUT);

        if (res < 0) {

            perror("epoll_wait failed");

            exit(EXIT_FAILURE);

        } else if (res == 0) {

            fprintf(stderr, "no socket ready for read within %d secs\n", EPOLL_TIMEOUT / 1000);

            continue;

        }



        //检测到res个IO file descriptor的events,loop各个fd进行响应

        for (i = 0; i < res; i++) {

            //events[i]即为检测到的event,域events[i].events表示具体哪些events,域events[i].data.fd即对应的IO fd



            if ( (events[i].events & EPOLLERR) ||

                 (events[i].events & EPOLLHUP) ||

                 (!(events[i].events & EPOLLIN)) ) {

                //由于events[i].events使用每个bit表示event,因此判断是否包含某个具体事件可以使用`&`操作符

                //这里判断是否存在EPOLLERR, EPOLLHUP等event

                fprintf (stderr, "epoll error\n");

                close (events[i].data.fd);

                continue;

            }



            //对检测到event的各IO fd进行响应

            if (events[i].data.fd == sock) {



                //当前fd是server的socket,不进行读而是accept所有client连接请求

                while (1) {

                    client_addr_len = sizeof(client_addr);

                    conn_sock = accept(sock, (struct sockaddr *) &client_addr, &client_addr_len);

                    if (conn_sock == -1) {

                        if ( (errno == EAGAIN) || (errno == EWOULDBLOCK) ) {

                            //non-blocking模式下无新connection请求,跳出while (1)

                            break;

                        } else {

                            perror("accept failed");

                            exit(EXIT_FAILURE);

                        }

                    }

                    if (!inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip_str, sizeof(client_ip_str))) {

                        perror("inet_ntop failed");

                        exit(EXIT_FAILURE);

                    }

                    printf("accept a client from: %s\n", client_ip_str);

                    //设置conn_sock为non-blocking

                    setSockNonBlock(conn_sock);

                    //把conn_sock添加到epoll的侦听中

                    event.events = EPOLLIN;

                    event.data.fd = conn_sock;

                    if ( epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock, &event) == -1 ) {

                        perror("epoll_ctl(EPOLL_CTL_ADD) failed");

                        exit(EXIT_FAILURE);

                    }

                }



            } else {



                //当前fd是client连接的socket,可以读(read from client)

                conn_sock = events[i].data.fd;

                memset(buffer, 0, sizeof(buffer));

                if ( (recv_size = recv(conn_sock, buffer, sizeof(buffer), 0)) == -1  && (errno != EAGAIN) ) {

                    //recv在non-blocking模式下,返回-1且errno为EAGAIN表示当前无可读数据,并不表示错误

                    perror("recv failed");

                    exit(EXIT_FAILURE);

                }

                printf("recved from conn_sock=%d : %s(%d length string)\n", conn_sock, buffer, recv_size);



                //立即将收到的内容写回去

                if ( send(conn_sock, buffer, recv_size, 0) == -1 && (errno != EAGAIN) && (errno != EWOULDBLOCK) ) {

                    //send在non-blocking模式下,返回-1且errno为EAGAIN或EWOULDBLOCK表示当前无可写数据,并不表示错误

                    perror("send failed");

                    exit(EXIT_FAILURE);

                }

                printf("send to conn_sock=%d done\n", conn_sock);



                //将当前socket从epoll的侦听中移除(有文章说:关闭con_sock之后,其会自动从epoll中删除,因此此段代码可以省略)

                if ( epoll_ctl(epfd, EPOLL_CTL_DEL, conn_sock, NULL) == -1 ) {

                    perror("epoll_ctl(EPOLL_CTL_DEL) failed");

                    exit(EXIT_FAILURE);

                }



                //关闭连接

                if ( close(conn_sock) == -1 ) {

                    perror("close failed");

                    exit(EXIT_FAILURE);

                }

                printf("close conn_sock=%d done\n", conn_sock);

            }

        }



    }



    close(sock);    //关闭server的listening socket

    close(epfd);    //关闭epoll file descriptor



    return 0;

}

测试:编译并运行程序;然后尝试运行多个telnet localhost 1984和server进行通信。

注意:epoll在使用epoll_ctl为file descriptor指定events的时候,默认采用Level Triggered,即如果events未完成调用epoll_wait的话每次都会返回该事件;通过如下方式:

struct epoll_event event;

event.data.fd   =   sock;

event.events    =   EPOLLIN | EPOLLET;

if ( epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &event) == -1 ) {

    perror("epoll_ctl(EPOLL_CTL_ADD) failed");

    exit(EXIT_FAILURE);

}

可以指定该fd的event采用Edge Triggered模型,如果采用该模型,epoll_wait检测到每次事件变化只通知一次,因此在epoll_wait之后的处理的时候需要注意(例如有可读的event的时候,注意数据读取完整)。可参考该文章介绍的代码。

小结:

epoll这种event-based的方法比较1。select/poll等loop style方法;2。多进程(forking)/多线程(threading)方法(每个进程或线程对应一个connection socket),在多client高并发下性能更优越。因此推荐在实际中应用。例如:Nginx, Lighttpd, Memcached等都采用有该event-based的异步IO模型。

另外,event-handling库libevent也支持epoll方法(还支持kqueue(FreeBSD/NetBSD/OpenBSD/Darwin), /dev/poll(Solaris/HPUX), select, poll等方法)在实际中也可使用该库编写高性能的Server,方便实现跨平台可移植

http://en.wikipedia.org/wiki/Epoll
http://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/
http://banu.com/2011/06/how-to-use-epoll-complete-example/
http://stackoverflow.com/questions/2583350/is-epoll-the-essential-reason-that-tornadowebor-nginx-is-so-fast
http://stackoverflow.com/questions/2540000/serving-large-file-using-select-epoll-or-kqueue
http://stackoverflow.com/questions/4039832/select-vs-poll-vs-epoll

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值