第二版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函数的缓冲区里面。