在Windows文件指的就是普通的肉眼可见的文件 , 而Linux一切皆文件 https://blog.csdn.net/nan_nan_nan_nan_nan/article/details/81233599
一定要注意生成文件的警告和报错,不能忽略了!!!!!!!
#include <stdlib.h>
#include <stdio.h>
#include <string.h> int main(int argc, char *argv[])
{
FILE* fp;
char buf[]="hello world";
char buff2[];
if((fp = fopen("1.txt","w+")) == NULL)
{
perror("file open failure");
exit();
}
fwrite(buf , sizeof(buf), , fp);
memset(buff2,,sizeof(buff2));
fseek(fp,,SEEK_SET);
fread(buff2 ,sizeof(buff2) ,, fp);
printf(">>%s\n",buff2);
getchar();
fclose(fp);
return ;
}
1.标准流和流功能 write和read 可以对任何文件读写
stdin 0 标准输入
stdout 1 标准输出
stderr 2 标准错误(报错)
可以使用write代替printf , printf是实现比较复杂局限性也小
write(目的/0, buff , length) 目的是写入的目标文件或使用上面的std进行打印和输出到控制台 , buff是要写入的数据 , length是写入的大小
FILE* 是指向一个内存 open返回的是一个句柄
2.缓冲区
1.缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
2.缓冲区有分为下列几种
_IOFBF 全缓冲 | 全缓冲区 默认大小BUFSIZE 4096 fflush() https://baike.baidu.com/item/fflush/10942194?fr=aladdin 默认开启全缓冲区
_ IOLBF 行缓冲 | 行缓冲区 遇到换行符才进行刷新 参考Linux终端和scanf
_IONBF 无缓冲 | 无缓冲区 stdio库 参考read, write ,stderr 都是不带缓冲区的
指定缓冲区 setbuf(FILE* stream , char* buf); buf的长度必须是指向长度为BUFSIZE的缓冲区
setbuffer(FILE* stream , char* buf , size_t size);
setlinebuf(FILE* stream);
3.设置缓冲区的函数
void setbuf(FILE *stream, char *buf);
void setbuffer(FILE *stream, char *buf, size_t size);
void setlinebuf(FILE *stream);
int setvbuf(FILE *stream, char *buf, int mode , size_t size);
4.为什么使用setvbuf函数
如果你的内存足够大,可以把文件IO的BUF设置大一些,这样每次你用fopen/fread/fwrite/fscanf/fprintf语句的时候,都会在内存里操作,减少内存到磁盘IO读写的操作次数,提高系统效率。如果你的程序的功能涉及到类似数据库、视频、音频、图像处理等大量需要爆发式磁盘到内存的IO情况下,可以考虑用setvbuf进行优化内存IO,其他情况下可以不考虑,LINUX/WINDOWS会自动处理这些问题。
3.fopen的权限 https://blog.csdn.net/gettogetto/article/details/72867757
函数原型:FILE * fopen(const char * path,const char * mode);
mode:
“r” 以只读方式打开文件,该文件必须存在。
“r+” 以可读写方式打开文件,该文件必须存在。
”rb+“ 读写打开一个二进制文件,允许读写数据(可以任意修改),文件必须存在。
“w” 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
“w+” 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
“a” 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
”a+“ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
“wb” 只写打开或新建一个二进制文件;只允许写数据(若文件存在则文件长度清为零,即该文件内容会消失)。
“wb+” 读写打开或建立一个二进制文件,允许读和写(若文件存在则文件长度清为零,即该文件内容会消失)
“ab” 追加打开一个二进制文件,并在文件末尾写数据
“ab+” 读写打开一个二进制文件,允许读,或在文件末追加数据
4.文件读写函数
write \ read (fd , buff , size(buff)); 任何文件都可读写 如:txt ,套接字等等...
fwrite \ fread (buff , sizeof(buff), 1 , fp); 只能读写标准文件
fgetc和getc他们的区别并不是在他们的使用上,而是在他们的实现上!具体来说,就是带f的(fgetc、fputc)实现的时候是通过函数来实现的,而不带f(putc、getc)的实现的时候是通过宏定义来实现的!
char *gets(char *str) 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。
int getc(FILE *stream) \ int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符。
int puts(const char *str) 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。
int fputc(int char, FILE *stream) \ int putc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。
5.文件流定位
iint fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
whence : (SEEK_SET 文件开始的位置)(SEEK_CUR 文件的当前位置)(SEEK_END 文件结束的的位置)
long int ftell(FILE *stream) 返回文件读写指针当前的位置距离开头的字节数
void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。
需求: 获得当前文件的真实大小 实现:fseek到文件end 再 ftell 获得
6.标准文件和socket文件的区别和联系 以上所述都是标准的IO , 以下的是socketIO
区别:
文件的量 不同 (网络文件的量更加庞大)
处理的实时性要求不同 (文件不能提前获取(如电影缓冲好的和未缓冲好的区别) , 标准文件是以阻塞的方式进行的在网络文件行不通)
联系: 都是文件可以使用相同的函数
7.socket的IO模型 IO模型 https://www.cnblogs.com/LittleHann/p/3897910.html
阻塞非阻塞区别 https://baijiahao.baidu.com/s?id=1623908582750588151&wfr=spider&for=pc
以下对照IO模型
阻塞IO处理 如: read() 如果没有收到对端数据 , 内核会处于挂起状态直到有数据才会往下执行
非阻塞IO处理 如: 把read()设置成非阻塞 无论是否收到数据都会立刻返回,再不断的访问socket(也是一种阻塞) ,直到有数据才往下执行
IO复用式 一个IO复用到多个IO , 并一起等待 , 一个文件夹里有多个文件,一起阻塞到内核里
信号驱动式IO 不等待socket是否有数据, 当socket有数据时会发起信号并优先处理数据
信号驱动式IO: 当在执行程序时, 绑定的信号IO发起信号时, 挂起当前执行的程序去处理发起信号的IO,处理完信号后再继续之前的操作
异步IO: 前面所述都会占用IO,一直等待内核拷贝完数据返回后才执行下个IO 无法同时并发处理多个IO
同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读
写操作。异步文件IO也就是重叠IO允许一个或多个线程同时发出IO请求。
异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了
8.IO复用的使用与流程
IO复用: IO复用是把多个IO的fd加入到fd_set这个文件集里,多个IO共同使用一个select的fd (一起阻塞), select()轮询fd_set , 但凡有数据的IO则立刻返回
select poll epoll
selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就立刻返回往下执行
select()的详解 https://blog.csdn.net/jiaqi_327/article/details/25657601
分配 struct fd_set rfds; 创建IO复用的文件集
设置 初始化FD_ZERO(&rfds); 加入句柄FD_SET(fd , &rfds);把fd加入到rfds 移除句柄FD_CLR(fd, &rfds));把fd移出rfds
使用 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
int nfds 文件集合中文件描述符最大值(fd是int类型,每个进程默认有3个标准fd,从3开始+1)+1
比如stdin(0) , stdout(1) , 因为select的起始位置不同,fd是从0开始的 ,select是从1开始所以要+1(0+1才能指向stdin代表第一个fd)
freadfds, writefds, exceptfds 是可读文件节点集, 可写文件节点集, 检查节点错误集,(给freadfds参数就行了其他NULL)
struct timeval *timeout 设置select的间隔时间,超过时间立刻返回
struct timeval { 创建对象设置好后传入
long tv_sec; /* seconds */ 秒
long tv_usec; /* microseconds */ 微秒};
int FD_ISSET(int fd, fd_set *set)是一个宏,不是函数,作用就是检察一下现在是否有数据可读。 通过select返回后(证明有数据)不再进行阻塞
经过第一次select后 , 每次重新select之前都要重新加入FD_SET ,因为select会改变rfds里的值
inet_aton(const char* cp,struct in_addr* inp) 将cp所指的网络地址字符串转换成网络使用的二进制的数,然后存于inp所指的in_addr结构中
inet_ntoa相反
continue回到while(1)
select的第二个参数在select之前是作为参数传入 , select之后 改变了rfds的值后作为返回数据的指针返回出来
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h> #define MAXBUF 1024 int main(int argc, char **argv)
{
int sockfd, new_fd;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + ];
fd_set rfds; //创建IO复用的容器
struct timeval tv; //select的超时时间
int retval, maxfd = -; if (argv[])
myport = atoi(argv[]); //输入端口
else
myport = ;
if (argv[])
lisnum = atoi(argv[]); //输入最大连接数,也可以NULL
else
lisnum = ;
if ((sockfd = socket(PF_INET, SOCK_STREAM, )) == -) { //创建socket对象
perror("socket");
exit(EXIT_FAILURE);
} bzero(&my_addr, sizeof(my_addr)); //置0
my_addr.sin_family = PF_INET; //设置本地信息
my_addr.sin_port = htons(myport);
if (argv[])
my_addr.sin_addr.s_addr = inet_addr(argv[]); //输入本地地址
else
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -) { //绑定
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(sockfd, lisnum) == -) { //监听
perror("listen");
exit(EXIT_FAILURE);
} while ()
{
printf ("\n----wait for new connect\n");
len = sizeof(struct sockaddr);
if ((new_fd =accept(sockfd, (struct sockaddr *) &their_addr,&len)) == -) { //接受连接并保存对端信息
perror("accept");
exit(errno);
} else
printf("server: got connection from %s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr),ntohs(their_addr.sin_port), new_fd);
while ()
{
FD_ZERO(&rfds); //把IO复用的容器(rfds)置0
FD_SET(, &rfds); //把stdin加入到 rfds
FD_SET(new_fd, &rfds); //把new_fd加入到 rfds
maxfd = new_fd;
tv.tv_sec = ; //超时1秒
tv.tv_usec = ;
retval = select(maxfd + , &rfds, NULL, NULL, &tv); //阻塞 , 轮询加入到rfds里的fd ,超时后跳过阻塞往下执行
if (retval == -)
{
perror("select");
exit(EXIT_FAILURE);
} else if (retval == ) { //如果select返回0证明没有数据更新
continue; //跳过当前循环,强制开始下一次循环
}
else
{
if (FD_ISSET(, &rfds)) //FD_ISSET 判断stdin是否有可读数据
{
bzero(buf, MAXBUF + );
fgets(buf, MAXBUF, stdin); //获取stdin里的可读数据存入buf
if (!strncasecmp(buf, "quit", )) {
printf("i will quit!\n");
break;
}
len = send(new_fd, buf, strlen(buf) - , ); //send buf
if (len > )
printf ("send successful,%d byte send!\n",len);
else {
printf("send failure!");
break;
}
}
if (FD_ISSET(new_fd, &rfds)) //FD_ISSET 判断new_fd是否有可读数据
{
bzero(buf, MAXBUF + );
len = recv(new_fd, buf, MAXBUF, ); //读取socket(new_fd)的可读数据
if (len > )
printf ("recv success :'%s',%dbyte recv\n", buf, len);
else
{
if (len < )
printf("recv failure\n");
else
{
printf("the ohter one end ,quit\n");
break;
}
}
}
}
}
close(new_fd);
printf("need othe connecdt (no->quit)");
fflush(stdout); //清空stdout的缓冲区
bzero(buf, MAXBUF + );
fgets(buf, MAXBUF, stdin);
if (!strncasecmp(buf, "no", ))
{
printf("quit!\n");
break;
}
}
close(sockfd);
return ;
}
select_sever
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h> #define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + ];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -; if (argc != )
{
printf("argv format errno,pls:\n\t\t%s IP port\n",argv[], argv[]);
exit(EXIT_FAILURE);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, )) < )
{
perror("Socket");
exit(EXIT_FAILURE);
} bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[]));
if (inet_aton(argv[], (struct in_addr *) &dest.sin_addr.s_addr) == )
{
perror(argv[]);
exit(EXIT_FAILURE);
} if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != )
{
perror("Connect ");
exit(EXIT_FAILURE);
} printf("\nget ready pls chat\n");
while ()
{
FD_ZERO(&rfds);
FD_SET(, &rfds);
FD_SET(sockfd, &rfds);
maxfd = sockfd;
tv.tv_sec = ;
tv.tv_usec = ;
retval = select(maxfd + , &rfds, NULL, NULL, &tv);
if (retval == -)
{
printf("select %s", strerror(errno));
break;
}
else if (retval == )
continue;
else
{
if (FD_ISSET(sockfd, &rfds))
{
bzero(buffer, MAXBUF + );
len = recv(sockfd, buffer, MAXBUF, );
if (len > )
printf ("recv message:'%s',%d byte recv\n",buffer, len);
else
{
if (len < )
printf ("message recv failure\n");
else
{
printf("the othe quit ,quit\n");
break;
}
}
}
if (FD_ISSET(, &rfds))
{
bzero(buffer, MAXBUF + );
fgets(buffer, MAXBUF, stdin);
if (!strncasecmp(buffer, "quit", )) {
printf("i will quit\n");
break;
}
len = send(sockfd, buffer, strlen(buffer) - , );
if (len < ) {
printf ("message send failure");
break;
} else
printf
("send success,%d byte send\n",len);
}
}
}
close(sockfd);
return ;
}
select_client
利用数组存放不确定数量的IO句柄fd判断其状态(数组全是-1,如果有句柄fd存放将改变状态(-1变成select的返回值),无数据时fd返回0,有数据时fd返回数据大小,而并非-1(error除外))
利用循环把数组里(要select的)的句柄加入到fd_set, 轮询过后再利用循环更新rfds
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h> #define _BACKLOG_ 5 //监听队列里允许等待的最大值 //当不确定有多少需要重载fd_set的fd时,把fd存入一个数组,方便循环重载
int fds[];//用来存放需要处理的IO事件 int creat_sock(char *ip,char *port)
{
int sock = socket(AF_INET,SOCK_STREAM,);
if(sock < ){
perror("creat_sock error");
exit();
} struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(port));
local.sin_addr.s_addr = inet_addr(ip); if( bind(sock,(struct sockaddr*)&local,sizeof(local)) < ){
perror("bind");
exit();
} if(listen(sock,_BACKLOG_) < ){
perror("listen");
exit();
} return sock;
} int main(int argc,char* argv[])
{
if(argc != ){
printf("Please use : %s [ip] [port]\n",argv[]);
exit();
}
int listen_sock = creat_sock(argv[],argv[]); //创建socket对象 size_t fds_num = sizeof(fds)/sizeof(fds[]); //获得数组长度
size_t i = ;
for(;i < fds_num;++i) //在socket里0也是句柄, 所以全部-1 , 以确定数组状态
{
fds[i] = -;
} int max_fd = listen_sock; //确定最大fd select时+1 fd_set rset; //创建rfds
while()
{
FD_ZERO(&rset); //置0
FD_SET(listen_sock,&rset); //把本地fd (加入\重载)rfds
max_fd = listen_sock;
//struct timeval timeout = {20 , 0};
fds[]=listen_sock; //确定第一个fd是本地直接加入
size_t i = ;
for(i=;i < fds_num;++i) //从第二个开始加入fd
{
if(fds[i] > ){ //大于0说明有fd
FD_SET(fds[i] ,&rset); //把fd (加入\重载)rfds
if(max_fd < fds[i]) //冒泡算法,找出最大的fd
{
max_fd = fds[i];
}
}
} switch(select(max_fd+,&rset,NULL,NULL,NULL)) //select最大fd+1
{
case -:
perror("select");
break;
case :
printf("time out..\n");
break;
default:
{
size_t i = ;
for(;i < fds_num;++i)
{
//当为listen_socket事件就绪的时候,就表明有新的连接请求
if(FD_ISSET(fds[i],&rset) && fds[i] == listen_sock) //判断数组里的第一个fd(本地fd)是否有数据
{
struct sockaddr_in client;
int accept_sock = accept(listen_sock,(struct sockaddr*)&client,sizeof(client)); //接受连接并保存对端信息
if(accept_sock < ){
perror("accept");
exit();
}
//char * paddr=NULL;
//char * saddr=NULL;
//paddr=inet_ntoa(client.sin_addr);
//saddr=inet_ntoa(client.sin_addr);
printf("connect by a client, ip:%s port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); size_t i = ;
for(;i < fds_num;++i)//将新接受的描述符存入集合中
{
if(fds[i] == -){
fds[i] = accept_sock;
break;
}
}
if(i == fds_num)
{
close(accept_sock);
}
}
//普通请求
else if(FD_ISSET(fds[i],&rset) && (fds[i] > ))
{
char buf[];
memset(buf,'\0',sizeof(buf));
ssize_t size = read(fds[i],buf,sizeof(buf)-);
if(size < ){
perror("read");
exit();
}else if(size == ){
printf("client close..\n");
close(fds[i]);
fds[i] = -;
}else{
printf("client say: %s\n",buf);
} }
else{}
} }
break;
}
}
return ;
}
更优化的select使用
9.IO复用的内核实现 Linux应用层的阻塞都可能会被信号所中断(应用层的中断叫可中断睡眠状态)
9.1 内核的实现其实是一个面向对象 步骤:
面向对象三步骤: (回顾04所描述的 , Linux一切皆文件(内核设计的核心之一,链表: 利用结构体里的函数指针针对不同的需求,进行不同的分配(设置函数指针指向驱动(实现需求的)函数) , 再把对象注册(加入)到管理链表的函数里使用)完成面向对象设计的思路)
注册对象: 内核里注册一个file_operations对象
分配对象: 把结构体里的函数指针分别指向对应的函数驱动(等同于赋值吧)
使用对象: 使用file提供的register(加入链表)函数, 把file_operations提交到file里
应用层要使用select,通过一系列的查找,找到file---->file_operations------->select(select其实是调用了poll的驱动函数)逐层查找
9.2: poll的实现和使用差不多(一般不会单独使用poll) 使用: https://blog.csdn.net/zhuxiaoping54532/article/details/51701549
select 和 poll 和epoll的 区别: https://www.cnblogs.com/aspirant/p/9166944.html
9.3 epoll的实现流程和使用 (IO复用大多数情况都是使用epoll了)
select和poll受限于fd(数量有1000就影响效率了,并不能满足高并发的服务器), epoll将事件抽象成event(不受限于fd)
epoll_even的结构:
struct epoll_event
{
uint32_t events; /* 事件的类型 */
epoll_data_t data; /* 事件的信息 */
} __attribute__ ((__packed__)); typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
一样是面向对象三步骤:
创建epollfd对象
int epoll_create(int size) //size = 监听事件的数量
返回值 epoll_fd , epoll_ event
设置epollfd对象
创建epoll_event对象
struct epoll_event ep_ev
设置epoll_event对象
ep_ev.events=(EPOLLIN 输入事件)(EPOLLOUT 输出事件)... 监听的事件类型,读取或者写入
ep_ev.events.fd=listen_sock 监听句柄是否有事件产生
使用:epoll_event对象 https://www.cnblogs.com/Dream-Chaser/p/7401184.html
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, epoll_event, ep_ev)
使用epollfd对象
int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout); events用来做返回的 http://blog.sina.com.cn/s/blog_488531130100i706.html
大白话的讲就是, 创建epoll_fd容器,里面放的是一个个的epoll_event事件, 事件对listen_sock监听,如果listen_sock有对客户端传入的数据进行读取(进而产生事件)
则进行一系列的返回, 而epoll_wait进行轮询(就是监听)也有超时(和select作用一样), epoll_ctl的作用是把事件加入到epoll_fd里
epoll使用的精髓 https://www.cnblogs.com/fnlingnzb-learner/p/5835573.html
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h> int creat_socket(char *ip,char* port)
{
int sock = socket(AF_INET,SOCK_STREAM,);
if(sock < ){
perror("socket");
exit();
} //调用setsockopt使当server先断开时避免进入TIME_WAIT状态,\
将其属性设定为SO_REUSEADDR,使其地址信息可被重用
int opt = ;
if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)) < ){
perror("setsockopt");
exit();
} struct sockaddr_in local; local.sin_family = AF_INET;
local.sin_port = htons(atoi(port));
local.sin_addr.s_addr = inet_addr(ip); if( bind(sock,(struct sockaddr*)&local,sizeof(local)) < ){
perror("bind");
exit();
} if(listen(sock,) < ){
perror("listen");
exit();
} printf("listen and bind succeed\n"); return sock;
} int set_noblock(int sock)
{
int fl = fcntl(sock,F_GETFL);
return fcntl(sock,F_SETFL,fl|O_NONBLOCK);
} int main(int argc,char *argv[])
{
if(argc != ){
printf("please use:%s [ip] [port]",argv[]);
exit();
}
int listen_sock = creat_socket(argv[],argv[]); //创建socket int epoll_fd = epoll_create(); //创建epoll_fd容器
if(epoll_fd < ){
perror("epoll creat");
exit();
} struct epoll_event ep_ev; //创建事件
ep_ev.events = EPOLLIN; //事件的类型 IN读取
ep_ev.data.fd = listen_sock; //产生事件的对象 listen_sock //添加关心的事件
//把事件和对象追加到epoll_fd
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ep_ev) < ){
perror("epoll_ctl");
exit();
} struct epoll_event ready_ev[];//申请空间来放就绪的事件。
int maxnum = ;
int timeout = ;//设置超时时间,若为-1,则永久阻塞等待。
int ret = ; int done = ;
//把产生的事件放入ready_ev里,再循环处理每一个事件, 先判断事件是否为socket且是PEOLLIN, 如果是则把socket里的事件\
追加到epoll_fd中,退出当次循环,进行下一次循环. 其实就是把所以事件都当成普通IO进行处理,因为epoll_event里有fd所以可以使用\
recv read等读写函数
while(!done){
//阻塞: 轮询epoll_fd里的所有事件直到(fd对象)产生事件并返回事件数量,或者超时后返回0往下执行\
产生的事件会存放到ready_ev里
switch(ret = epoll_wait(epoll_fd,ready_ev,maxnum,timeout)){
case -:
perror("epoll_wait");
break;
case :
printf("time out...\n");
break;
default://至少有一个事件就绪
{
int i = ;
for(;i < ret;++i){ //循环处理epoll_fd里的事件
//判断是否为监听套接字,是的话accept
int fd = ready_ev[i].data.fd;
if((fd == listen_sock) && (ready_ev[i].events & EPOLLIN)){
struct sockaddr_in remote;
socklen_t len = sizeof(remote); int accept_sock = accept(listen_sock,(struct sockaddr*)&remote,&len);
if(accept_sock < ){
perror("accept");
continue;
}
printf("accept a client..[ip]: %s,[port]: %d\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));
//将新的事件添加到epoll集合中
ep_ev.events = EPOLLIN | EPOLLET;
ep_ev.data.fd = accept_sock; set_noblock(accept_sock); //设置成非阻塞 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,accept_sock,&ep_ev) < ){
perror("epoll_ctl");
close(accept_sock);
}
}
else{//普通IO
if(ready_ev[i].events & EPOLLIN){
//申请空间同时存文件描述符和缓冲区地址 char buf[];
memset(buf,'\0',sizeof(buf)); ssize_t _s = recv(fd,buf,sizeof(buf)-,);
if(_s < ){
perror("recv");
continue;
}else if(_s == ){
printf("remote close..\n");
//远端关闭了,进行善后
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
}else{
//读取成功,输出数据
printf("client# %s",buf);
fflush(stdout); //将事件改写为关心事件,进行回写
ep_ev.data.fd = fd;
ep_ev.events = EPOLLOUT | EPOLLET; //在epoll实例中更改同一个事件
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ep_ev);
}
}else if(ready_ev[i].events & EPOLLOUT){
const char*msg = "HTTP/1.1 200 OK \r\n\r\n<h1> hi girl </h1>\r\n";
send(fd,msg,strlen(msg),);
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
}
}
}
}
break; }
}
close(listen_sock);
return ;
}
tcp_epoll
总结: IO复用是一种机制,一个进程可以监听多个描述符,一旦某个描述符就绪(读就绪和写就绪),能够对程序进行相应的读写操作
目前支持I/O复用的系统调用有select,poll,pselect,epoll,本质上这些I/O复用技术是同步I/O技术。一般都是使用epoll的
与多进程和多线程相比,I/O复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
select(): 创建fd_set结构体对象rfds(这是一个保存文件描述符的文件数组) , FD_ZERO(&rfds)置空rfds , FD_SET(fd, &rfds)文件描述符加入到rfds,
select(nfds+1, &rfds, NULL, NULL, &timeout);轮询监听 rfds 里的文件状态是否改变, 返回状态改变的fd的数量, 无则返回0 超时, error返回-1
FD_ISSET(fd, &rfds) 判断状态改变的fd是否是当前fd ,是则返回1 ,否则返回0 ,技巧:可以把fd放入一个数组通过下标 ,加入和判断
epoll(): 把监听fd状态的改变 ,转变成监听事件的发生 详细 https://www.cnblogs.com/fnlingnzb-learner/p/5835573.html
epoll_fd = epoll_create(size) //创建事件容器和其大小
struct epoll_event ep_ev; //创建事件
ep_ev.events = EPOLLIN; //事件的类型 如果发生IN读取,则统一做出对应处理
ep_ev.data.fd = fd; //产生事件的对象 ,如果产生事件的是一个socket_fd, 接受连接accept()后把其转化成事件,并加入rfds里
struct epoll_event ready_ev[128]; //申请空间来放就绪的事件。最大数不能超过创建epoll的大小
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&ep_ev); //把fd和其事件类型追加到epoll_fd里
epoll_wait(epoll_fd,ready_ev,maxnum,timeout); //等待事件的产生,类似于select()调用 ,把产生的事件依次存入ready_ev里 ,用循环[i]依次处理就绪事件
关于ET、LT两种工作模式: LT:只要内核缓冲区有数据就一直通知(一直触发),直到读完缓冲区, 这种模式可靠,但低效率
ET:只有状态发生变化才通知 ,只触发一次(文件描述符状态变化时),可能会导致数据读不完 ,这种模式不可靠 ,但高效率,
所以要自己实现一个能读取完整缓冲区的recv函数