第二版str_cli函数(select加阻塞版)

1、I/O复用
  • 同步I/O
    • 阻塞I/O模型
    • 非阻塞I/O模型
    • I/O复用模型
    • 信号驱动I/O模型
  • 异步I/O模型

唯一需要注意的是信号驱动I/O与异步I/O的区别,信号驱动I/O是指当描述符可读/可写了,内核通知应用程序去读/写。而异步I/O是指在应用程序收到信号时,读/写已经由完成。

 

2、改进后的str_cli函数

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

#define SERV_ADDR "127.0.0.1"
#define SERV_PORT 5358
#define BUF_LEN 1024

#define MAX(a, b) (a)>(b)?(a):(b)

ssize_t writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwriten;
    const char *ptr;

    ptr = vptr;
    nleft = n;
    while(nleft>0) {
        if((nwriten = write(fd, ptr, nleft)) <= 0) {
            if(nwriten < 0 && errno == EINTR) {
                nwriten = 0;        /*interrupt by signal*/
            } else {
                return -1;
            }
        }

        nleft -= nwriten;
        ptr += nwriten;
    }
    return n;
}

void str_cli(FILE *fp, int sockfd)
{
    int maxfdp;
    fd_set rset;
    char sendbuf[BUF_LEN] = {0};
    char recvbuf[BUF_LEN] = {0};

    FD_ZERO(&rset);
    while(1) {
        FD_SET(fileno(fp), &rset);
        FD_SET(sockfd, &rset);
        maxfdp = MAX(fileno(fp), sockfd) + 1;
        select(maxfdp, &rset, NULL, NULL, NULL);

        if(FD_ISSET(sockfd, &rset)) {
            if(read(sockfd, recvbuf, BUF_LEN) == 0) {    //if the length of the data in kernal buffer > 1024 ?

                printf("EOF\n");
                exit(0);
            } else {
                fputs(recvbuf, stdout);
            }
        }
        if(FD_ISSET(fileno(fp), &rset)) {
            if(fgets(sendbuf, BUF_LEN, fp) == NULL) {
                return;
            }
            writen(sockfd, sendbuf, strlen(sendbuf));
        }
        bzero(sendbuf, BUF_LEN);
        bzero(recvbuf, BUF_LEN);
    }
    return;
}

int main(int argc, char **argv)
{
    int fd;
    struct sockaddr_in servaddr;

    fd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(SERV_ADDR);
    servaddr.sin_port = htons(SERV_PORT);

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

    str_cli(stdin, fd);
    return 0;
}

改进之处:

1、write函数调用改成了新添加的writen函数,防止了在内核buf不足的情况下不能将应用层buf全部复制进去的情况发生。

2、采用select进行I/O复用,防止当str_cli阻塞在fgets时,套接口发生某个事件(比如收到FIN)却得不到及时处理的情况发生。

依旧存在问题:

1、如果将client的标准输入重定向到文件,fgets会以很快的速度读取文件直到EOF,这时fgets返回 NULL,str_cli返回,程序退出,打开的fd被关闭。可是,众所周知,网络传输是有延时的,也就是说最后发出去的数据还没有从服务端返回,可是这 时已经关闭了client,导致数据的丢失。

2、select系统调用只能从read等系统调用的角度指出是否有数据可以读写,而不能够出fgets之类的库函数的角度指 出。这就产生一个问题,如果使用带有缓冲的I/O函数,在该I/O函数遇到'\n'就返回的情况下,并不能保证将所有的数据都发送出去,还有一部分数据会 被停放在I/O函数的缓冲区里面。

posted @ 2012-11-01 16:11  孤火  阅读(291)  评论(0编辑  收藏  举报