本章主要是复习标准I/O函数收发数据的方法,例如fopen()、feof()、fgetc()、fputs(),以及将套接字文件描述符转换成FILE*结构体指针并采用标准I/O修改程序。
标准I/O函数不同于系统函数,标准I/O函数是在系统函数的基础上,进行优化。系统I/O函数不再介绍,感兴趣可以去查阅《Unix系统编程》这本书,里面讲的非常全。这里主要是介绍标准I/O函数,我们的目标是将套接字变成标准I/O使其拥有标准I/O的优点。
标准I/O函数主要有以下优点:
- 标准I/O函数具有良好的移植性
- 标准I/O函数可以利用缓冲提高性能。(重要)
在我们将套接字变成标准I/O实现之前,我们需要明白几个小的容易忽略的细节。
文件描述符不等于FILE文件指针
我们通过两个小程序看看区别,这两个程序都是将一个文件里面的内容复制到另外一个文件当中。文件描述符只能调用系统函数,FILE文件指针只能调用标准I/O函数。
//文件描述符实现, syscpy.c
#include <stdio.h>
#include <fcntl.h>
#define BUF_SIZE 3
int main(int argc, char const *argv[])
{
int fd1,fd2;
int len;
char buf[BUF_SIZE];
fd1=open("news.txt",O_RDONLY);
fd2=open("cpy.txt",O_WRONLY|O_CREAT|O_TRUNC);
while((len=read(fd1,buf,sizeof(buf)))>0) write(fd2,buf,len);
close(fd1);
close(fd2);
return 0;
}
//FILE指针实现, stdcpy.c
#include <stdio.h>
#define BUF_SIZE 3
int main(int argc, char const *argv[])
{
FILE *fp1,*fp2;
char buf[BUF_SIZE];
fp1=fopen("news.txt","r");
fp2=fopen("cpy1.txt","w");
while(fgets(buf,BUF_SIZE,fp1)!=NULL)fputs(buf,fp2);
fclose(fp1);
fclose(fp2);
return 0;
}
注意: news.txt找个数据集数据验证最好,因为只有300M以上的文件才能体会到之间的差距。
主要是FILE*文件指针是使用标准的I/O函数fopen的前提条件,标准I/O函数拥有缓冲区。而文件描述符(如程序syscpy.c)使用的系统函数是没有缓冲区的,这导致复制文件时每次都要访问内存,系统延迟非常明显。标准I/O函数优点很明显,但是并不意味着采用系统函数就一无是处,标准I/O函数还是存在以下几个缺点:
-
不容易进行双向通信
-
有时可能频繁调用fflush函数
打开文件时,如果希望同时进行读写操作,则应该以r+、w+、a+模式打开文件,但是由于存在缓冲区,导致每次切换读写都要调用一次fflush函数
-
需要以FILE结构体指针的形式返回文件描述符。
为了使用标准函数,我们需要将文件描述符转换成FILE结构体指针。
套接字创建时返回的时文件描述符
即通过调用socket()返回的是文件描述符
通过上面小程序,我们知道,如果我们在进行网络编程的过程,如果要使用标准I/O函数,我们需要将套接字文件描述符转换成FILE结构体指针。
转换函数
#include<stdio.h>
//文件描述符转FILE结构体指针
FILE *fdopen(int fildes,const char* mode);
//FILE结构体指针转文件描述符
int fileno(FILE* stream);
修改程序
接下来,我们将在第四章、基于TCP的服务器端、客户端(上)程序的基础上进行更改,将套接字描述符转换成FILE指针,并使用I/O标准函数进行实现同样的操作。
//std_server.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[])
{
/**
* @brief 先声明FILE结构体指针
* @note
* @retval None
*/
FILE *readfp;
FILE *writefp;
int serv_sock,clnt_sock;
char message[BUF_SIZE];
int str_len,i;
struct sockaddr_in serv_adr,clnt_adr;
socklen_t clnt_adr_sz;
if(argc!=2)
{
printf("Usage:%s<port>\n",argv[0]);
exit(0);
}
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("Connect client %d\n",i+1);
}
/**
* @brief 将套接字描述符转换成FILE结构体指针
* @note
* @retval
*/
readfp=fdopen(clnt_sock,"r");
writefp=fdopen(clnt_sock,"w");
// while((str_len=read(clnt_sock,message,BUF_SIZE))!=0)
while(!feof(readfp))//换用标准I/O函数
{
//write(clnt_sock,message,str_len);
fgets(message,BUF_SIZE,readfp);
fputs(message,writefp);
}
//close(clnt_sock);
fclose(readfp);
fclose(writefp);
}
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
//std_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[])
{
/**
* @brief 声明FILE结构体指针
* @note
* @retval None
*/
FILE* readfp;
FILE* writefp;
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
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.....");
}
/**
* @brief 将套接字文件描述符转换成FILE结构体指针
* @note
* @retval
*/
fdopen(sock,"r");
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"))
{
printf("testing......\n");
break;
}
// write(sock,message,strlen(message));
// str_len=read(sock,message,BUF_SIZE-1);
/**
* @brief 使用标准I/O函数进行替代write与read函数
* @note
* @retval None
*/
fputs(message,writefp);
fflush(writefp); //读写转换
fgets(message,BUF_SIZE,readfp);
message[str_len]=0;
printf("Message from server:%s",message);
}
fclose(writefp);
fclose(readfp);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}