19-高级I/O函数——套接字和标准I/O

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35733751/article/details/85786419

之前我们一直使用的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函数,这里就不详细介绍了。

 

猜你喜欢

转载自blog.csdn.net/qq_35733751/article/details/85786419