linux-socket编程(七)五种IO模型

1. 五种I/O模型

阻塞I/O

       linux-socket编程(七)五种IO模型_第1张图片

当套接口完成连接,可以使用recv函数向系统提出receive请求,来接收数据,这个请求是阻塞的,直到对等方发送数据过来。

非阻塞I/O

linux-socket编程(七)五种IO模型_第2张图片

使用fcntl函数来将套接字改为非阻塞模式。fcntl(fd, F_SETFL, flag|O_NONBLOCK);这时候recv函数即使没有收到数据,也不会阻塞,会返回一个错误,返回值为-1,错误代码为EWOULDBLOCK。如果还想获取到数据,就再次提出请求。这个很少使用,由于它接受的过程相当于一个循环,对cpu资源的一种浪费,这种循环接收被称之为忙等待。

I/O复用(select和poll)

linux-socket编程(七)五种IO模型_第3张图片

主要是通过select来实现的,select是用来管理多个文件描述符,当其中的一个或者多个检测到有数据到来,select就返回,这时候在调用recv就不会阻塞了。实际上就是将阻塞的位置提前到了select上。核心思想就是使用select来管理多个文件描述符。

信号驱动I/O

linux-socket编程(七)五种IO模型_第4张图片

当数据到来,它是以信号的方式来通知应用进程,应用进程要在信号处理程序中去处理接收数据的细节。

异步I/O

linux-socket编程(七)五种IO模型_第5张图片

这个模型是效率最高的。使用aio_read来实现的。该函数提交一个请求,会递交一个应用层缓存区,即使没有数据到来,该函数也立刻返回,这时候应用进程就可以处理其它的事情达到异步处理的效果。当有数据到达时,就会将数据拷贝到该函数提供的缓存区中,复制完成后,会通过一个信号来通知应用进程的程序来处理数据。数据直接从内核推送到用户的缓存区中。

2.select

int select(int nfds, fd_set *readfds, fd_set *writefds, 
fd_set *exceptfds, struct timeval *timeout);

参数分析:
fd_set *readfds 读集合检测到有数据可读的套接口就会存放在这里。
fd_set *writefds 可写集合
fd_set *exceptfds 异常集合
struct timeval *timeout (超时结构体)超时时间,如果设置为NULL表示必须等到有事件发生才返回,
如果设置了时间,就表示在这个时间范围内如果没有事件发生也会返回,返回事件个数为0。
如果select返回失败返回-1.
int nfds 存放在集合中的描述符的最大值+1,有关文件描述符的含义可以查看https://www.jianshu.com/p/a2df1d402b4d这篇文章。

select函数的作用相当于一个管理者,用来管理多个I/O一旦其中的一个I/O或者多个I/O检测到我们感兴趣的事件,select函数返回,返回值为检测到的事件个数。并且会返回那些I/O发生的事件。这样就可以去遍历这些事件,进而去处理这些事件。该函数的后四个参数都是输入输出参数,会根据实际情况,来修改内部的值。

使用到的函数:

void FD_CLR(int fd, fd_set *set);
作用是将fd这个文件描述符号,从这个set中移除。
int FD_ISSET(int fd, fd_set *set);
作用是判定fd该文件描述符,是否在这个集合当中
void FD_SET(int fd,fd_set *set);
作用是将fd添加到集合当中。
void FD_ZERO(fd_set *set);
作用是清空集合

使用select改进回射客户端程序

使用select来管理标准输入I/O和套接口I/O

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


using namespace std;

struct packet
{
    int len;
    char buf[1024];
};

#define ERR_EXIT(m) \
        do  \
        {   \
            perror(m);  \
            exit(EXIT_FAILURE); \
        } while(0);

ssize_t readn(int fd, void *buf, size_t count)
{
    size_t nleft = count;   // 剩余字节数
    ssize_t nread;
    char *bufp = (char*) buf;

    while (nleft > 0)
    {
        nread = read(fd, bufp, nleft);
        if (nread < 0)
        {
            if (errno == EINTR)
            {
                continue;
            }
            return  -1;
        } else if (nread == 0)
        {
            return count - nleft;
        }

        bufp += nread;
        nleft -= nread;
    }
    return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten;
    char* bufp = (char*)buf;

    while (nleft > 0)
    {
        if ((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
            {
                continue;
            }
            return -1;
        }
        else if (nwritten == 0)
        {
            continue;
        }
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count;
}


ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while (1)
    {
        int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看传入消息
        if (ret == -1 && errno == EINTR)
        {
            continue;
        }
        return ret;
    }
}

ssize_t readline(int sockfd, void *buf, size_t maxline)
{
    int ret;
    int nread;
    char *bufp = (char*)buf;    // 当前指针位置
    int nleft = maxline;
    while (1)
    {
        ret = recv_peek(sockfd, buf, nleft);
        if (ret < 0)
        {
            return ret;
        }
        else if (ret == 0)
        {
            return ret;
        }
        nread = ret;
        int i;
        for (i = 0; i < nread; i++)
        {
            if (bufp[i] == '\n')
            {
                ret = readn(sockfd, bufp, i+1);
                if (ret != i+1)
                {
                    exit(EXIT_FAILURE);
                }
                return ret;
            }
        }
        if (nread > nleft)
        {
            exit(EXIT_FAILURE);
        }
        nleft -= nread;
        ret = readn(sockfd, bufp, nread);
        if (ret != nread)
        {
            exit(EXIT_FAILURE);
        }
        bufp += nread;
    }
    return -1;
}

void ehco_cli(int sockfd)
{
//    char recvbuf[1024];
//    char sendbuf[1024];
//    // struct packet recvbuf;
//    // struct packet sendbuf;
//    memset(recvbuf, 0, sizeof recvbuf);
//    memset(sendbuf, 0, sizeof sendbuf);
//    int n = 0;
//    while (fgets(sendbuf, sizeof sendbuf, stdin) != NULL)   // 键盘输入获取
//    {
//        writen(sockfd, sendbuf, strlen(sendbuf)); // 写入服务器
//
//        int ret = readline(sockfd, recvbuf, sizeof recvbuf);    // 服务器读取
//        if (ret == -1)
//        {
//            ERR_EXIT("readline");
//        }
//        if (ret == 0)
//        {
//            printf("server close\n");
//            break;
//        }
//
//        fputs(recvbuf, stdout); // 服务器返回数据输出
//
//        // 清空
//        memset(recvbuf, 0, sizeof recvbuf);
//        memset(sendbuf, 0, sizeof sendbuf);
//    }
    fd_set rset;
    FD_ZERO(&rset);

    int nready;
    int maxfd;
    int fd_stdin = fileno(stdin);
    if (fd_stdin > sockfd)
    {
        maxfd = fd_stdin;
    } else {
        maxfd = sockfd;
    }

    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};

    while (1)
    {
        FD_SET(fd_stdin, &rset);
        FD_SET(sockfd, &rset);
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (nready == -1)
        {
            ERR_EXIT("select");
        }
        if (nready == 0)
        {
            continue;
        }

        if (FD_ISSET(sockfd, &rset))    // sock数据 读取
        {
            int ret = readline(sockfd, recvbuf, sizeof recvbuf);    // 服务器读取
            if (ret == -1)
            {
                ERR_EXIT("readline");
            }
            if (ret == 0)
            {
                printf("server close\n");
                break;
            }

            fputs(recvbuf, stdout); // 服务器返回数据输出
            memset(recvbuf, 0, sizeof(recvbuf));
        }

        if (FD_ISSET(fd_stdin, &rset))
        {
            if (fgets(sendbuf, sizeof sendbuf, stdin) == NULL)   // 键盘输入获取
            {
                break;
            }
            writen(sockfd, sendbuf, strlen(sendbuf)); // 写入服务器
        }
    }
    close(sockfd);
};

void handle_sigchld(int sig)
{
    // wait(NULL);
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main(int argc, char** argv) {
    // signal(SIGCHLD, SIG_IGN);
    signal(SIGCHLD, handle_sigchld);
    // 1. 创建套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        ERR_EXIT("socket");
    }

    // 2. 分配套接字地址
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof servaddr);
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    // servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // inet_aton("127.0.0.1", &servaddr.sin_addr);

    // 3. 请求链接
    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
        ERR_EXIT("connect");
    }

    struct sockaddr_in localaddr;
    socklen_t addrlen = sizeof localaddr;
    if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0)
    {
        ERR_EXIT("getsockname");
    }
    printf("id = %s, ", inet_ntoa(localaddr.sin_addr));
    printf("port = %d\n", ntohs(localaddr.sin_port));

    // 4. 数据交换
    ehco_cli(sockfd);

    // 5. 断开连接
    close(sockfd);


    return 0;
}

 

你可能感兴趣的:(linux)