Linux基础(06)IO复用

时间:2021-05-25 13:30:08

在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 内核的实现其实是一个面向对象 步骤:

    Linux基础(06)IO复用

  面向对象三步骤: (回顾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函数