第15章 套接字和标准I/O

15.1标准I/O函数的优点

1.标准I/O函数的两个优点

  • 标准I/O函数具有良好的可移植性
  • 标准I/O函数可以利用缓冲提高性能

所有的标准函数都具有良好的可移植性,为了支持所有的操作系统(编译器),这些函数都是按照ANSIC标准定义的。所以移植性好。

使用标准I/O函数会得到额外的缓冲支持,创建套接字时操作系统会生成用于I/O的缓冲,缓冲在TCP通信中相当重要,若使用标准I/O函数,会得到额外的另一个缓冲支持。 假设用fputs函数传输字符串“Hello”,首先将数据传递到标准I/O缓冲,然后数据将移动套接字输出缓冲,最后将字符串发往主机。 设置缓冲主要是为了提高性能,但套接字中的缓冲主要是为了实现TCP协议而设立,TCP传输丢失数据将重传,重传意味着还残留着数据在套接字输出缓冲。 使用标准I/O函数缓冲主要是为了提高性能: 将Hello一个字符分一个数据包发和所有字符一起发,前者发送的数据量明显更大,因为每个数据包头信息是固定的字长40字节(可能更长),所以一次性发送字符串数据量更小,向套接字输出缓冲移动数据也会消耗大量时间,先存在I/O缓冲中再整体一次性移动会节约大量时间。

2.标准I/O函数的几个特点

  • 不容易进行双向通信
  • 有时可能会需要频繁调用fflush函数(用于强制将缓冲的数据输出到目标文件)
  • 需要以FILE结构体指针的形式返回文件描述符

因为缓冲的缘故,每次切换读写工作状态时应该调用fflush,为了使用标准I/O函数,需要FILE指针,因此需要将创建套接字返回的文件描述符转化成FILE指针

15.2 使用标准I/O函数

1.利用fdopen函数转化为FILE结构体指针

#include<stdion.h>
FILE * fdopen(int fildes,const char * mode);
成功时返回FILE结构体指针,失败返回NULL
fildes    需要转换的文件描述符
mode      将要创建的FILE结构体指针的模式信息

示例

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
	FILE *fp;
	int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
	if(fd==-1)
	{
		fputs("file open error", stdout);
		return -1;
	}

	fp=fdopen(fd, "w");//用fdopen函数将文件描述符转化为FILE指针
	fputs("Network C programming \n", fp);//用指针向指定文件写数据
	fclose(fp);
	return 0;
}

2.利用fileno函数转换为文件描述符

#include<stdio.h>
int fileno(FILE * stream)

成功返回转换后的文件描述符,失败返回-1

详细使用方法略

15.3 基于套接字的标准I/O函数的使用

echo_stdserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	char message[BUF_SIZE];
	int str_len, i;
	
	struct sockaddr_in serv_adr;
	struct sockaddr_in clnt_adr;
	socklen_t clnt_adr_sz;
	FILE * readfp;
	FILE * writefp;
	
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(serv_sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_adr_sz=sizeof(clnt_adr);

	for(i=0; i<5; i++)
	{
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		if(clnt_sock==-1)
			error_handling("accept() error");
		else
			printf("Connected client %d \n", i+1);
	
		readfp=fdopen(clnt_sock, "r");
		writefp=fdopen(clnt_sock, "w");
	
		while(!feof(readfp))//判断是否读到文件尾,如果没有读到就传输数据
		{
			fgets(message, BUF_SIZE, readfp);
			fputs(message, writefp);
			fflush(writefp);//标准I/O为了提高性能提供额外的缓冲,调用fflush函数为了确保数据立即传输到客户端。
		}
		fclose(readfp);
		fclose(writefp);
	}
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

echo_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;
	FILE * readfp;
	FILE * writefp;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected...........");

	readfp=fdopen(sock, "r");
	writefp=fdopen(sock, "w");	

	while(1) 
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;

		fputs(message, writefp);
		fflush(writefp);//同样是为了确保数据尽快传到客户端
 		fgets(message, BUF_SIZE, readfp);
		printf("Message from server: %s", message);
	}	
	fclose(writefp);
	fclose(readfp);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

第四章的回声客户端需要将接收的数据转换为字符串(数据的尾部插入0),但是上述并这一过程,因为标准I/O函数就是按字符串单位进行数据交换。这就是为什么前面的代码,一般都在数组后面加个0,而且接受数据的时候用BUF_SIZE-1。确保至少给0留一位。


本文章使用limfx的vscode插件快速发布