之前我们一直使用的read,write函数以及它们的变体recv, send等函数执行I/O,这些函数都是要使用描述符的,通常这些函数都作为unix内核中的系统调用实现。
除了以上说的系统调用,我们也可以使用标准I/O函数库(standard I/O libary),这个函数库由 ANSI C 标准进行规范,不过使用标准I/O函数需要创建一个标准 I/O 流,我们可以使用fdopen函数来完成,与 fdopen 函数功能相反的函数是 fileno,该函数是从标准 I/O 流创建一个文件描述符。
这两个函数原型如下:
#include <stdio.h>
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);
fdopen函数的fd参数表示文件描述符,mode则是文件的读写权限,例如:”w”表示写权限,“r”表示读权限。
fileno函数的stream参数表示需要传入一个文件流形式的指针。
使用标准I/O函数库需要考虑以下几点:
1. 当我们想要在标准I/O中调用select时,由于select只能用于描述符,因此需要获取标准I/O流的描述符,当然我们可以使用fileno函数来完成。
2. tcp套接字和udp套接字是全双工的,标准I/O流也可以是全双工的,但是为了避免标准I/O缓冲区的问题,解决办法是给套接字创建两个标志I/O流,一个用于读,另一个用于写。
使用标准I/O改写TCP服务器,代码如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define SERV_PORT 10001
#define SERV_IP "127.0.0.1"
int main(void) {
int sfd, cfd;
int len, i;
//BUFSIZ是系统内嵌的一个宏,用来指定buf大小
char buf[BUFSIZ], clie_IP[BUFSIZ];
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
sfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET , SERV_IP , &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(SERV_PORT);
//绑定套接字
bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
//设定连接上限,此处不阻塞
listen(sfd, 64);
clie_addr_len = sizeof(clie_addr);
//阻塞,等待客户端发起连接
cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
//根据描述符创建两个标准I/O流缓冲,一个读,一个写
FILE *fpin = NULL;
FILE *fpout = NULL;
fpin = fdopen(cfd , "r");
fpout = fdopen(cfd , "w");
while (fgets(buf , sizeof(buf) , fpin)!=NULL) {
printf("%s" , buf);
//处理客户端数据,小写转大写
for (i = 0; i < strlen(buf); i++){
buf[i] = toupper(buf[i]);
}
//处理完数据,回写给客户端
if(fputs(buf , fpout) == EOF){
puts("fputs error");
break;
}
//刷新标准I/O
fflush(fpout);
}
//关闭连接
close(sfd);
close(cfd);
return 0;
}
此时服务端并没有刷新标准I/O,在客户端输入一些数据,执行结果如下:
可以看到,在客户端处连续输入几次数据后,却没有收到任何服务端的回应。
此时开启服务端刷新标准I/O,然后在客户端输入数据:
从上面的结果我们知道,开启服务端刷新标准I/O后,客户端才会收到服务端的回应,原因在于标准I/O的缓冲问题,也就是说服务端调用fputs写入的回射实际上是写入到了标准I/O的缓冲区,而不是套接字的缓冲区,因为标准I/O类调用都有一个用户缓冲区,当调用标准I/O函数时还要把数据从用户缓冲区拷贝到内核缓冲区(即套接字的缓冲区),但问题在于此时标准I/O的缓冲区没有满。
只有当标准I/O的缓冲区满了之后,才会把数据拷贝到套接字描述符的缓冲区,最终回射给客户端,而fflush函数则正好是干这件事情的。通常标准I/O有三大类缓冲区,关于标准I/O的缓存具体可参考:2-C标准的I/O缓存和FILE结构体 , 5-文件I/O—read/write函数,这里就不详细介绍了。