第十五章:套接字和标准I/O

15.1 标准I/O函数

这里需要熟练掌握一些文件操作时使用的函数(fopen、feof、fgetc、fputs等)
啥是标准I/O函数呢?
下面列出一些常用的

  1. fopen fclose
FILE *fopen(const char *path,const char *mode);其中,path是我们要打开的流,而mode就是我们打开文件的方式了,也就决定你所打开的文件将被怎样的去对待啦,有如下几种方式:"r":只读方式打开,打开的文件必须存在。
"r+"  :读写方式打开,文件必须存在。
"w" : 只写方式打开,文件不存在则创建,文件存在则清空。
"w+" : 读写方式打开,文件不存在则创建,文件存在则清空。
"a" :  只写方式打开,追加的方式写到文件的尾部,文件不存在则创建。
"a+": 读写方式打开,文件不存在创建,从头开始读,从尾开始写。
  1. fgetc fputc
 功能:  从文件中读取字符。头文件: #include <stdio.h>函数原型: int fgetc(FILE *stream);返回值: 返回所读取的一个字节。如果读到文件末尾或者读取出错时返回EOF功能:  将字符写到文件中。头文件: #include <stdio.h>函数原型: int fputc(int c, FILE *stream);返回值: 在正常调用情况下,函数返回写入文件的字符的ASCII码值,出错时,返回EOF(-1)。当正确写入一个字符或一个字节的数据后,文件内部写指针会自动后移一个字节的位置。EOF是在头文件 stdio.h中定义的宏。
  1. fgets fputs
 fgets();功能:  从文件中读取字符串 头文件: #include <stdio.h>函数原型: char *fgets(char *buf, int size, FILE *stream);参数说明: *s  字符型指针,指向用来存储所得数据的地址。size 整型数据,指明存储数据的大小。*stream 文件结构体指针,将要读取的文件流。返回值: 1.成功,则返回第一个参数buf;2.在读字符时遇到end-of-file,则eof指示器被设置,如果还没读入任何字符就遇到这种情况,则buf保持原来的内容,返回NULL;3.如果发生读入错误,error指示器被设置,返回NULL,buf的值可能被改变.fputs();功能:  向指定的文件写入一个字符串(不自动写入字符串结束标记符‘\0’)头文件: #include <stdio.h>函数原型: int fputs(const char *s, FILE *stream);参数说明: *s  s是字符型指针,可以是字符串常量,或者存放字符串的数组首地址。返回值: 返回值为非负整数;否则返回EOF(符号常量,其值为-1)。
  1. fread fwrite
fread();功能:  从文件流中读数据,最多读取nmemb个项,每个项size个字节头文件: #include <stdio.h>函数原型: size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);参数说明: *ptr 用于接收数据的内存地址size 要读的每个数据项的字节数,单位是字节nmemb 要读nmemb个数据项,每个数据项size个字节.*stream 文件流返回值: 返回真实读取的项数,若大于nmemb则意味着产生了错误。另外,产生错误后,文件位置指示器是无法确定的。若其他stream或buffer为空指针,或在unicode模式中写入的字节数为奇数,此函数设置errno为EINVAL以及返回0.fwrite();功能:  向文件写入一个数据块头文件: #include <stdio.h>函数原型: size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);返回值: 返回实际写入的数据块数目

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

  1. 标准I/O函数具有良好的移植性(Portability)。
  2. 标准I/O函数可以利用缓冲提高性能、

关于移植性:
其实所有的标准函数都具有这种性质。为了支持所有操作系统(编译器),这些函数都是按照ANSI C标准定义的。这不仅仅局限于网络编程,而是适用于所有编程领域。

关于利用缓冲提高性能:
使用标准I/O函数时会得到额外的缓冲支持。之前说过,在创建套接字时操作系统会为套接字生成用于I/O的缓冲,这两者是什么关系呢?
如下图所示:

上图可以看出,使用标书nI/O函数传输数据时,经过了2个缓冲。
例如:通过fputs函数传输字符串“xxxxx”时,数据先传递到标准I/O函数缓冲。然后数据将移动到套接字输出缓冲中,最后将字符串发送到对方主机。

套接字中的缓冲主要是为了实现TCP协议而设立的,例如TCP传输中数据没有接收到需要再次传递,这是就从保存有数据的输出缓冲中读取数据。

另一方面,使用标准I/O函数缓冲的主要目的是:为了提高性能。

那么如何提高性能呢?使用缓冲可以提高性能?为啥?
通常需要传输的数据越多,有无缓冲带来的性能差异越大
我们换个角度说,性能的提高可以从下面两个方面进程描述:

  1. 传输的数据量
  2. 数据向输出缓冲移动的次数

首先从数据量角度:
先提个问题:一个字节数据发送10次(10个数据包)与累计10个字节发送1尺的情况分别传输了多少个字节?
发送数据时使用的数据包中包含有头信息,头信息与数据大小无关,是按照一定的格式填入的。也就是说,假设一个头信息占用40个字节(实际更大):
前者:10 + 40 * 10 = 410 字节 后者:40 * 1 + 10 = 50字节。可以看到,这两种方式需要传递的数据量有很大的差别。

接着从向输出缓冲移动的次数:
前者移动10次花费的时间将近后者移动一次花费的10倍。

15.1.2 标准I/O 函数与系统函数之间的性能对比

上面讲了为什么缓冲能够提升性能。下面我们实际来测试一下,看看效果是否和分析的一样。
分别利用标准I/O函数和系统函数编写文件复制程序,这主要是为了检验缓冲提高性能的程度。首先是利用系统函数复制文件的示例。

syscpy.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 3int main(int argc, char *argv[])
{int fd1, fd2, 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;
}

下面是采用标准I/O函数复制文件的代码:
利用fputs和fgets函数复制文件,因此这是一种基于缓冲的复制。

stdcpy.c

#include <stdio.h>
#define BUF_SIZE 3int main(int argc, char *argv[])
{FILE* fp1;FILE* fp2;char buf[BUF_SIZE];fp1 = fopen("news.txt","r");fp2 = fopen("cyp.txt", "w");while(fgets(buf, BUF_SIZE, fp1) != NULL)fputs(buf, fp2);fclose(fp1);fclose(fp1);return 0;
}

这两种方法在文件不大的情况下差异并不明显,如果文件越大,差别越明显。

15.1.3 标准I/O 函数的几个缺点

  1. 不容易进行双向通信
  2. 又是可能频繁调用fflush函数
  3. 需要FILE结构体指针的形式返回文件描述符

在c语言中,打开文件时,如果希望同时进行读写操作,应该以 r+、w+、a+模式打开。
但是由于缓冲,每次切换读写工作状态时应该刁颖fflush函数。 这也会影响基于缓冲的性能提高。 而且为了使用标准I/O函数,需要FILE结构体指针。而创建套接字时默认返回的是文件描述符。因此需要将文件描述符转化为FILE指针。

15.2 使用标准I/O函数

如前所述,创建套接字时返回文件描述符,而为了使用标准I/O函数,只能将其转换为FILE结构体指针。下面介绍转换方法。

15.2.1 利用fdopen函数转换为FILE结构体指针

可以通过fdopen函数将创建套接字时返回的文件描述符转换为标准I/O函数中使用的FILE结构体指针

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

上面第二个参数 mode与fopen函数中的打开模式相同。常用的参数有
读模式:“r” 和 写模式:“w”。下面简单使用一下

desto.c

#include <stdio.h>
#define BUF_SIZE 3int main(int argc, char *argv[])
{FILE* fp1;FILE* fp2;char buf[BUF_SIZE];fp1 = fopen("news.txt","r");fp2 = fopen("cyp.txt", "w");while(fgets(buf, BUF_SIZE, fp1) != NULL)fputs(buf, fp2);fclose(fp1);fclose(fp1);return 0;
}


捋一下思路:

  1. 使用open函数得到的int类型的文件描述符
  2. 使用fdopen函数将上一步得到的文件描述符进行转换,得到FILE* 类型的结构体指针
  3. 利用新得到的结构体指针,使用系统函数fputs进行写入。

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

接下来介绍与fdopen函数提供相反功能的函数,如下

#include <stdio.h>int fileno(FILE* stream);
->成功时返回转换后的文件描述符,失败时返回-1

这个用法很简单,向该函数传递FILE指针参数返回相应文件描述符,接下来给出fileno函数的调用示例。

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(fd == -1){fputs("file open error", stdout);return -1;}printf("First file descripor:%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;
}

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

前面说了标准I/O函数的优缺点,以及文件描述符转换为FILE指针的方法。
下面将配合套接字进行操作,修改第四章的回声服务器以及回声客户端的代码,改为基于标准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(int 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", clnt_sock);}readfp = fdopen(clnt_sock, "r");writefp = fdopen(clnt_sock, "w");while(!feof(readfp)){fgets(message, BUF_SIZE, readfp);fputs(message, writefp);fflush(writefp);}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);      // 1if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))break;fputs(message, writefp);              // 2fflush(writefp);fgets(message, BUF_SIZE, readfp);       // 3printf("Messgae from server: %s\n", message);}fclose(writefp);fclose(readfp);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

看起来和使用write 函数以及 read函数实现的效果是完全一样的,而且代码看起来好像简单了起来= - =

fflush函数是用来将输入或输出缓冲中的数据全部输出的函数。
上面的过程中,调用基于字符串fgets 、fputs函数提供服务,并调用fflush函数。标准I/O函数为了提高性能,内部提供额外的缓冲。 因此如果不调用fflush函数则无法保证立即将数据传输到客户端。

第四章的回声客户端在接受到数据后,需要将数据转化为字符串(数据的尾部插入0),但是上面代码中却没有这一过程。

因为使用标准I/O函数后可以按字符串单位进行数据交换。

Part 2 Linux programming:chapter 15:套接字和标准I/O相关推荐

  1. 【Linux网络编程】套接字简介

    00. 目录 文章目录 00. 目录 01. 概述 02. 套接字属性 03. socket函数 04. 套接字地址结构 05. 附录 01. 概述 Socket套接字由远景研究规划局(Advance ...

  2. 【Linux网络编程】套接字的介绍

    套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行通信.我们可以用套接字中的相关函数来完成通信过程. 套接字的特性有三个属性确定,它们是:域(domain),类型( ...

  3. Linux网络编程 之 套接字(四)

    目录 1. 套接字的定义 2. 套接字的创建方法 3. 套接字的地址 本地套接字 网络套接字 1. 套接字的定义 套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行 ...

  4. Linux网络编程——原始套接字编程

    Linux网络编程--原始套接字编程 转自:http://blog.csdn.net/tennysonsky/article/details/44676377 原始套接字编程和之前的 UDP 编程差不 ...

  5. Linux网络编程之套接字基础

    Linux网络编程之套接字基础 1.套接字的基本结构 struct sockaddr 这个结构用来存储套接字地址. 数据定义: struct sockaddr { unsigned short sa_ ...

  6. Part 2 Linux programming:chapter 18:多线程服务器端实现

    内容概要: Web服务器端需要同时向多个用户提供服务,而进程的局限性使得人们开始利用更高效的线程来实现Web服务器端. 18.1 线程的概念 本章主要介绍线程的通用说明,是windows线程的基础. ...

  7. win客户端与linux服务器C语言套接字socket

    windows:(客户端) #include<WINSOCK2.H> #include<STDIO.H> #include<iostream> #include&l ...

  8. linux epoll监听套接字实例

    linux epoll机制用于IO多路复用,能够同时监听多个接字,使用起来比较简单. 相关接口: #include <sys/epoll.h>int epoll_create(int si ...

  9. Linux网络协议栈(二)——套接字缓存(socket buffer)

    Linux网络核心数据结构是套接字缓存(socket buffer),简称skb.它代表一个要发送或处理的报文,并贯穿于整个协议栈. 1.    套接字缓存 skb由两部分组成: (1)    报文数 ...

最新文章

  1. 一起谈.NET技术,asp.net控件开发基础(20)
  2. POJ 2853 Sequence Sum Possibilities
  3. atcoder E - Greedy Ant(最优解等价+dp)
  4. 共享打印机 需要查找inf文件_印前、快印常用的局域共享软件
  5. 冯偌依曼计算机的基本原理是,03级计算机专《计算机组成原理》试卷A.doc
  6. 拉普拉斯算子属于卷积方法吗_2020 年 GNN 开卷有益与再谈图卷积
  7. python中换行的转义字符_Python语言中表示换行的转义字符是____________。(2.5分)_学小易找答案...
  8. SPSS中介效应分析(Process和mediate插件)
  9. excel怎么把竖排变成横排_衣服如此凌乱?怎么能忍受的了?衣柜收纳,试试这些神器吧...
  10. 苹果手机屏幕镜像搜索不到电视_手机自带的无线投屏总说找不到设备?
  11. mysql stmt fetch_mysql_stmt_fetch()
  12. Excel ActiveX组合框项目选择宏
  13. 软件测试分类冒烟测试,什么是冒烟测试
  14. 单点登录(SSO)服务
  15. 关于编写公司软件测试规范操作手册计划书
  16. 沪牌学院-沪拍拍课堂1: 估价策略
  17. excel VBA 字典与集合(Dictionary与Collection)
  18. 171029 windows10 桌面美化
  19. 两台服务器公用一个显示器,两台服务器链接一台显示器
  20. 奥利给!2020年10月程序员工资最新统计,涨了!!!

热门文章

  1. ffffffffffffffffffffffffffffff
  2. 亿赛通为航空航天产业搭建数据安全保障体系
  3. 海川QK1209 低压按键台灯充电 LED 驱动 IC- 昱灿电子
  4. Qt实现气泡聊天的效果
  5. 兰州大学最新预测:新冠大流行将于2023年底结束
  6. Oracle11g使用dbms_parallel_execute对大表进行并行update
  7. 0基础快速入门WebPack(3)——图解详述plugins(插件)的安装及sourceMap的使用及WebpackDevServer正向代理和模块热更新等(附详细案例源码解析过程及版本迭代过程)
  8. 《Automated Spatial Calibration of HMD Systems with Unconstrained Eye-cameras》论文阅读
  9. linux中文输入搜狗,Linux配置中文输入法(搜狗输入法)
  10. 「聚变」前端 客户端,第十七届 D2 终端技术大会来了!