ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

谈谈Linux网络编程

2021-05-27 21:57:33  阅读:164  来源: 互联网

标签:文件 函数 epoll int 编程 描述符 谈谈 FILE Linux


标准I/O

常见标准I/O函数

常用标准I/O库函数总结

fopen

fopen()函数主要用于对文件和终端的输入输出。

#include <stdio.h>
 
FILE *fopen(const char *filename, const char *mode);
  • filaname:指定的文件名,fopen会把它与一个文件流关联起来。

  • mode:指定文件的打开方式:
    “r”/“rb”:只读方式打开
    “w”/“wb”:只写方式打开,把文件长度截为0(你可把它想象为>)
    “a”/“ab”:只写方式打开,新内容追加到文件尾(你可以想象成>>)
    “r+”/“r+b”/“rb+”:以修改方式打开(读写)
    “w+”/“w+b”/“wb+”:以修改方式打开,同时把文件内容截为0
    “a+”/“a+b”/“ab+”:以修改方式打开,新内容追加到文件结尾
    注意:linux会把所有文件都看成是二进制文件,所以那个"b"表示文件是二进制。

返回值:成功时返回一个FILE*指针,失败返回NULL值,NULL定义在stdio.h中。

fread

主要作用是从一个文件流里读取数据,数据从stream读到由ptr指定的数据缓冲区里面。

#include<stdio.h>

size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
  • ptr:数据读到ptr指定的缓冲区里面。
  • size:每个数据记录的长度(类似与char,int,long,float之类,指代每次读取块的大小)。
  • nitems:传输的记录个数。
  • stream:指定要读取的数据缓冲区。

返回值:读到缓冲区的记录个数(非字节),如果读到文件尾,其返回值可能小于nitems,甚至可以是0(读取文件为空)。

fwrite

主要从stream获取数据记录写到ptr中,返回值是成功写入的记录个数。

#include<stdio.h>

size_t fwrite(const void *ptr, size_t size, size_t items, FILE *stream);

它的接口和fread很像,参考fread。

fclose

关闭指定的文件流stream,使所有未写出的内容全部写出。

#include<stdio.h>

int fclose(FILE *stream);
  • stream:指定要关闭的文件流stream。
    返回值:如果成功返回0,失败返回EOF,同时会向全局变量errorn写入错误信息码。

fflush

fflush()函数的主要作用是把文件流的所有未写出的内容立刻写出。

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

接口同fclose。

fseek

主要作用是在文件流里面为下一次读写指定位置。

#include<stdio.h>
 
int fseek(FILE *stream, long int offset, int whence);
  • stream:指定操作的文件流。

  • offset:指定位置。

  • whence:指定偏移方式,具体形式类似系统调用lseek。

返回值:成功返回0,失败返回-1,同时设置errno指出错误。

fgetc,getc,getchar

fgetc()从文件流取出下一个字节并把它作为一个字符返回,如果到达文件结尾或者出现错误的时候返回EOF。其中getchar()从标准输入读取数据。

#include<stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();

fputc,putc,putchar

fputc()函数把一个字符写道一个输出文件流中。如果成功返回写入的值,失败则返回EOF。其中puchar()函数将单个字符写道标准输出。

#include<stdio.h>
 
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar();

fgets和gets

fgets()函数从输入文件流stream读取一个字符串,并把读到的字符写到ptr指向的缓冲区,当遇到如下情况停止:遇到换行符,已经传输n-1个字符,或者到达文件尾。它会把遇到的换行符也传递到接收字符串里去,再加上一个表示结尾的空字节\0。
gets()函数从标准输入读取数据并丢弃遇到的换行符,它在接受字符串的尾部加上NULL。

#include<stdio.h>
 
char *fgets(char *ptr, int n, FILE *stream);
char *gets(char *ptr);

printf,fprintf,sprintf

printf系列函数能够对各种不同类型的参数进行格式化编排和输出。
printf函数把自己的输出送到标准输出。
fprintf函数把自己的输出送到一个指定的文件流。
sprintf函数把自己的输出和一个结尾空字符写到s指向的缓冲区。

#include<stdio.h>
 
int printf(const char *format, ...);
 
int sprintf(char *s, const char *format, ...);
 
int fprintf(FILE *stream, const char *format, ...);
  • format:指定输出的格式。

  • s:字符串缓冲区。

  • stream:指定的文件流。

scanf,fscanf和sscanf

scanf系列函数从一个文件流读取数据并写到指定地址的变量。
scanf函数将读入的值保存在对应的变量里。

#include<stdio.h>
 
int scanf(const char *format, ...);
 
int fscanf(FILE *stream, const char *format, ...);
 
int ssacnf(const char *s, const char *format, ...);

文件流错误

通过检查文件流的状态来确定是否发生错误。
ferror函数测试一个文件流的错误标识,如果被设置返回一个非0值,否则返回0。
feof函数测试一个文件流的文件尾标识,如果被设置就返回一个非0值,否则返回0。
clearerr函数清楚有stream指定的文件流的文件尾标识和错误标识。

#include<stdio.h>
 
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);

文件流和文件描述符

每个文件流都有一个底层描述符相关联。
fileno函数可以确定文件流使用那个底层描述符。
fdopen函数在一个已经打开的文件描述符上创建一个新的文件流。

#include<stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);

使用标准I/O函数

标准IO函数的两个优点:

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

标准IO函数的两个缺点:

  • 不容易进行双向通信。
  • 有时可能频繁调用fflush函数。
  • 需要以FILE结构体指针的形式返回文件描述符。

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

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

#include <stdio.h>

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

上述函数的第二个参数与fopen函数中的打开模式相同。常用的参数有读模式"r"和写模式"w"。

示例说明

#include <stdio.h>#include <fcnt1.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");
	fputs("Network C programming ln", fp);
	fclose(fp);
	return 0;
}

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

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

示例说明

#include <stdio.h>
#include <fcnt1.h>
int main(void){
	FILE *fp;
	int fd=open("data.dat",o_WRONLYlO_CREATIO_TRUNC);
	if(fd==-1)
	{
		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 ln", fileno(fp));
	fclose(fp);
	return 0;
}

I/O分离问题

"流"分离带来的EOF问题

调用的fclose函数完全终止了套接字而不是半关闭。
解决办法:创建FILE指针前复制文件描述符即可,复制后另外创建1个文件描述符,然后利于各自的文件描述符生成读模式FILE指针和写模式FILE指针。这就为半关闭准备好了环境,因为套接字和文件描述符之间具有如下关系:
销毁所有文件描述符后才能销毁套接字。

但是这样做只是准备了半关闭环境,要进入真正的半关闭状态需要特殊处理,还剩余的一个文件描述符可以同时进行I/O。

复制文件描述符

在这里插入图片描述

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes,int fildes2);
//成功时返回复制的文件描述符,失败时返回-1。
  • fildes:需要复制的文件描述符。
  • fildes2:明确指定的文件描述符整数值。

复制文件描述符后"流"的分离

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#tdefine BUF_SIZE 1024
int main(int argc,char *argv[]){

	int serv_sock,c1nt_sock;
	FILE* readfp;
	FILE* writefp;

	struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;
	char buf[BUF_SIZE]={0,};
	serv_sock=socket(PF_INET,sOCK_STREAM,0);
	memset(&serv_adr,0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=hton1(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
	listen(serv_sock, 5);
	clnt_adr_sz=sizeof(clnt_adr);
	clnt_sock=accept(serv_sock,(struct sockaddr*)&c1nt_adr,&c1nt_adr_sz);
	
	readfp=fdopen(clnt_sock,"r");
	writefp=fdopen(dup(clnt_sock),"w");
	
	fputs("FROM SERVER: Hi~ client? ln", writefp);
	fputs("I love all of the world ln", writefp);
	fputs( "You are awesome! ln", writefp);
	fflush(writefp);
	
	shutdown(fileno(writefp), SHUT_WR);
	fclose(writefp);
	
	fgets(buf, sizeof(buf),readfp);
	fputs(buf,stdout);
	fclose(readfp);
	return 0;
}

  • 调用fdopen函数生成FILE指针。特别是针对dup函数的返回值生成
    FILE指针。
  • 针对fileno函数返回的文件描述符调用shutdown函数。因此,服务器端进入半关闭状态,并向客户端发送EOF。这一行就是之前所说的发送EOF的方法。调用shutdown函数时,无论复制出多少文件描述符都进入半关闭状态,同时传递EOF。

优于select的epoll

select复用方法其实由来已久,因此,利用该技术后,无论如何优化程序性能也无法同时接人上百个客户端(当然,硬件性能不同,差别也很大)。这种select方式并不适合以Web服务器端开发为主流的现代开发环境,所以要学习Linux平台下的epoll。

基于select的I/O复用技术速度慢的原因

最主要的缺点是:

  • 调用select函数后常见的针对所有文件描述符的循环语句;
  • 每次调用select函数时都需要向该函数传递监视对象信息。

改进措施:
仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。
这种处理方式Linux支持方式是epoll,Windows的支持方式是IOCP。

select优点:

  • 服务器端接入者少;
  • 程序应具有兼容性。

实现epoll时必要的函数和结构体

能够克服select函数缺点的epoll函数具有如下优点,这些优点正好与之前的select函数缺点相反。

  • 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句;
  • 调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息。

epoll服务器端实现中需要的3个函数:

  • epoll_create:创建保存epoll文件描述符的空间;
  • epoll_ctl:向空间注册并注销文件描述符;
  • epoll_wait:与select函数类似,等待文件描述符发生变化。

select方式和epoll方式的不同

  • select方式中为了保存监视对象文件描述符,直接声明了fd_set变量。但epoll方式下由操作系统负责保存监视对象文件描述符,因此需要向操作系统请求创建保存文件描述符的空间,此时使用的函数就是epoll_create。

  • 为了添加和删除监视对象文件描述符,select方式中需要FD_SET、FD_CLR函数。但在epoll方式中,通过epoll_ctl函数请求操作系统完成。

  • select方式下调用select函数等待文件描述符的变化,而epoll中调用epoll_wait函数。

  • select方式中通过fd_set变量查看监视对象的状态变化(事件发生与否),而epoll方式中通过如下结构体epoll_event将发生变化的(发生事件的)文件描述符单独集中到一起。

结构体epoll_event

struct epol1_event{
	__uint32_tevents;
	epoll_data_t data;
}
typedef union epoll_data{
	void * ptr;
	int fd;
	__uint32_t u32;
	__uint64_t u64;
}epoll_data_t;

声明足够大的epoll_event结构体数组后,传递给epoll_wait函数时,发生变化的文件描述符信息将被填入该数组。因此,无需像select函数那样针对所有文件描述符进行循环。

epoll_create

#include <sys/epoll.h>
int epoll_create(int size);
//成功时返回epol1文件描述符,失败时返回-1。
  • size:epoll实例的大小。

调用epoll_create函数时创建的文件描述符保存空间称为"epoll例程",但有些情况下名称不同,需要稍加注意。通过参数size传递的值决定epoll例程的大小,但该值只是向操作系统提的建议。换言之,size并非用来决定epoll例程的大小,而仅供操作系统参考。Linux 2.6.8之后的内核将完全忽略传入epoll_create函数的size参数,因为内核会根据情况调整epoll例程的大小。

epoll_create函数创建的资源与套接字相同,也由操作系统管理。因此,该函数和创建套接字的情况相同,也会返回文件描述符。也就是说,该函数返回的文件描述符主要用与于区分epoll例程。需要终止时,与其他文件描述符相同,也要调用close函数。

epoll_ctl

epoll_wait

多线程服务器端

标签:文件,函数,epoll,int,编程,描述符,谈谈,FILE,Linux
来源: https://blog.csdn.net/XZ2585458279/article/details/117226452

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有