所有的标准函数都具有良好的可移植性,为了支持所有的操作系统(编译器),这些函数都是按照ANSIC标准定义的。所以移植性好。
使用标准I/O函数会得到额外的缓冲支持,创建套接字时操作系统会生成用于I/O的缓冲,缓冲在TCP通信中相当重要,若使用标准I/O函数,会得到额外的另一个缓冲支持。 假设用fputs函数传输字符串“Hello”,首先将数据传递到标准I/O缓冲,然后数据将移动套接字输出缓冲,最后将字符串发往主机。 设置缓冲主要是为了提高性能,但套接字中的缓冲主要是为了实现TCP协议而设立,TCP传输丢失数据将重传,重传意味着还残留着数据在套接字输出缓冲。 使用标准I/O函数缓冲主要是为了提高性能: 将Hello一个字符分一个数据包发和所有字符一起发,前者发送的数据量明显更大,因为每个数据包头信息是固定的字长40字节(可能更长),所以一次性发送字符串数据量更小,向套接字输出缓冲移动数据也会消耗大量时间,先存在I/O缓冲中再整体一次性移动会节约大量时间。
因为缓冲的缘故,每次切换读写工作状态时应该调用fflush,为了使用标准I/O函数,需要FILE指针,因此需要将创建套接字返回的文件描述符转化成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;
}
#include<stdio.h>
int fileno(FILE * stream)
成功返回转换后的文件描述符,失败返回-1
详细使用方法略
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插件快速发布