参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨
测试环境:Ubuntu 10.10
GCC版本:4.4.5
一、标准I/O函数的两个优点
* 标准I/O函数具有良好的移植性
* 标准I/O函数可以利用缓冲提高性能
二、为什么I/O函数可以利用缓冲提高性能?
使用标准I/O函数,将得到额外的另一缓冲的支持,如下图:
缓冲并非在所以情况下都能带来卓越的性能。但需要传输的数据越多,有无缓冲带来的性能差异越大。可以通过如下两种角度说明性能的提高:
* 传输的数据量
* 数据向输出缓冲移动的次数
举例说明:
1.传输的数据量:比较1个字节的数据发送10次(10个数据包)的情况和累计10个字节发送1次的情况。发送数据时使用的数据包中含有头信息。头信息与数据大小无关,是按照一定的格式填入的。即使假设该头信息占用40个字节(实际更大),需要传递的数据量也存在较大的差别。
* 1个字节 10次 40*10=400字节
* 10个字节 1次 40*1=40字节
2.向套接字输出缓冲移动数据也会消耗不少时间:1个字节数据共移动10次花费的时间将近10个字节数据移动1次花费时间的10倍。
三、标准I/O函数和系统函数之间的性能对比
1.利用系统函数复制文件的示例
#include <stdio.h>
#include <fcntl.h>
#include <sys/time.h>
#define BUF_SIZE 3
int main(int argc, char* argv[])
{
int fd1, fd2;
int len;
char buf[BUF_SIZE];
struct timeval start, end;
double timeuse;
fd1 = open("news.txt", O_RDONLY);
fd2 = open("cpy.txt", O_WRONLY | O_CREAT | O_TRUNC);
if(fd1 == -1)
printf("fd1 open() error\n");
if(fd2 == -1)
printf("fd2 open() error\n");
gettimeofday(&start, NULL);
while((len = read(fd1, buf, sizeof(buf))) > 0)
write(fd2, buf, len);
gettimeofday(&end, NULL);
timeuse = /*end.tv_sec - start.tv_sec +*/ (end.tv_usec - start.tv_usec);
printf("timeuse = %f\n", timeuse);
close(fd1);
close(fd2);
return 0;
}
编译:gcc syscpy.c -o syscpy.out,运行:
timeuse = 71967.000000
注意:news.txt中数据自己随意填充。
2.采用标准I/O函数复制文件:
#include <stdio.h>
#include <sys/time.h>
#define BUF_SIZE 3
int main(int argc, char* argv[])
{
FILE* fp1;
FILE* fp2;
char buf[BUF_SIZE];
struct timeval start, end;
double timeuse;
fp1 = fopen("news.txt", "r");
fp2 = fopen("cpy.txt", "w");
gettimeofday(&start, NULL);
while(fgets(buf, BUF_SIZE, fp1) != NULL)
fputs(buf, fp2);
gettimeofday(&end, NULL);
timeuse = /*end.tv_sec - start.tv_sec +*/ (end.tv_usec - start.tv_usec);
printf("timeuse = %f\n", timeuse);
fclose(fp1);
fclose(fp2);
return 0;
}
编译:gcc stdcpy.c -o stdcpy.out,运行:
timeuse = 3712.000000
上述示例利用了fputs&fgets函数复制文件,因此是基于缓冲的复制。对比耗时,使用标准函数要比系统函数快得多。
3. 标准I/O函数的几个缺点
* 不容易进行双向通信
* 有时可能频繁调用fflush函数
* 需要以FILE结构体指针的形式返回文件描述符
四、使用标准I/O函数
1. 利用fdopen函数转换为FILE结构体指针——将创建套接字返回的文件描述符转为标准I/O函数中使用的结构体指针
头文件:#include <stdio.h>
函数功能:将文件描述符转换为FILE结构体指针
返回值:成功时返回转换的FILE结构体指针,失败时返回NULL
函数原型:FILE* fdopen(int fildes, const char* mode);
参数:
fildes——需要转换的文件描述符
mode——将要创建的FILE结构体指针的模式(mode)信息
使用示例:
desto.c
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
FILE* fp;
int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
if(-1 == fd)
{
fputs("file open error", stdout);
return -1;
}
fp = fdopen(fd, "w");
fputs("Network C programming\n", fp);
fclose(fp);
return 0;
}
操作:gcc main.c -o desto.out,运行:./desto.out
命令:cat data.data
显示:Network C programming
2.利用fileno函数转换为文件描述符
头文件:#include <stdio.h>
函数功能:向函数传递FILE指针参数时返回相应文件描述符
返回值:成功时返回转换后的文件描述符,失败时返回-1
函数原型:int fileno(FILE* stream);
使用示例:
todes.c
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
FILE* fp;
int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
if(-1 == fd)
{
fputs("file open error", stdout);
return -1;
}
printf("First file descriptor: %d\n", fd);
fp = fdopen(fd, "w");
fputs("TCP/IP SOCKET PROGRAMMING\n", fp);
printf("Second file descriptor: %d\n", fileno(fp));
fclose(fp);
return 0;
}
编译:gcc todes.c -o todes.out,运行:./todes.out
First file descriptor: 3
Second file descriptor: 3
五、基于套接字的标准I/O函数使用
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void errorHandling(const char* message);
int main(int argc, char* argv[])
{
int servSock, clientSock;
char message[BUF_SIZE];
int strLen, i;
struct sockaddr_in servAddr, clientAddr;
socklen_t clientAddrSize;
FILE* readFp;
FILE* writeFp;
if(2 != argc)
{
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
servSock = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == servSock)
errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
errorHandling("bind() error");
if(-1 == listen(servSock, 5))
errorHandling("listen() error");
clientAddrSize = sizeof(clientAddr);
for(i = 0; i < 5; i++)
{
clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
if(-1 == clientSock)
errorHandling("accept() error");
else
printf("connected client %d...", i+1);
readFp = fdopen(clientSock, "r");
writeFp = fdopen(clientSock, "w");
while(!feof(readFp)) //feof——如果文件结束,则返回非0值
{
fgets(message, BUF_SIZE, readFp);
fputs(message, writeFp);
fflush(writeFp);
}
fclose(readFp);
fclose(writeFp);
}
return 0;
}
void errorHandling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void errorHandling(const char* message);
int main(int argc, char* argv[])
{
int sock;
char message[BUF_SIZE];
int strLen;
struct sockaddr_in servAddr;
FILE* readFp;
FILE* writeFp;
if(3 != argc)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == sock)
errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if(1 == connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
errorHandling("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\n", message);
}
fclose(writeFp);
fclose(readFp);
return 0;
}
void errorHandling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
测试:
服务器端:gcc echoServer.c -o echoServer.out,运行:
客户端:gcc echoClient.c -o echoClient.out,运行:
connected........
Input message(Q to quit): abc
Message from server: abc
Input message(Q to quit): 123
Message from server: 123
Input message(Q to quit): asdasd
Message from server: asdasd
Input message(Q to quit): Q