网络编程(6)-----IO模型以及多路复用的基本原理

IO模型

阻塞I/O模式:

1.阻塞I/O模式是最普通使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O.

2.缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式。

读阻塞:

以read函数为例
进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发生阻塞。
它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。
经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。
如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。

写阻塞:

在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。
这时,写操作不进行任何拷贝工作,将发生阻塞一量发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区
UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞

非阻塞I/O模式:

当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/0操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling )

应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU源的操作。
这种模式使用中不普遍

多路复用I\O

应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间:若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂:
比较好的方法是使用I/0多路复用。其基本思想是:
- 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/0时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/0操作。

基本常识:

linux中每个进程默认情况下,最多可以打开1024个文件,最多有1024个文件描述
文件描述符的特点:
1.非负整数
2.从最小可用的数字来分配
3.每个进程启动时默认打开0,1,2 三个文件描述符

多路复用针对不止套接字fd,也针对普通的文件描述fd

1.把关心文件描述符加入到集合中(fd_set) 

void FD_ZERO():清零 集合

void FD_SET():加入集合

void FD_CLR();从集合中删除

void FD_ISSET();

   2.调用select()/poll( )函数去监控集合fd-set中那些文件描述符,等待集合中的一个或多个文件描述符有数据。

 int select()

select退出后:集合表示有数据的集合

if(FD_ISSET(fd,)){   }

(1)若是监听套接字上有数据:则有新客户端连接,则accept().

(2)若是已建立连接的套接字上有数据,则去读数据。

3.当有数据时,退出select()阻塞

4.依次判断哪个文件描述符有数据

5.依次处理那有数据的文件描述符的数据


int main(void)
{

    fd_set rset;
    int maxfd = -1;
    fd = socket(....);
    bind(fd,...);
    listen(fd,....);
while(1){
    maxfd = fd;
    FD_ZERO(&rset);
    
    FD_SET(fd,&rset);
    //依次把已经建立好连接fd加入到集合中,记录下来最大的文件描述符maxfd
    //....
#if 0 
    select(maxfd+1,&rset,NULL,NULL,NULL);
#else
    tout.tv_sec = 5;
    tout.tv_usec = 0;
    select(maxfd+1,&rset,NULL,NULL,&tout);
#endif
    if(FD_ISSET(fd,&rset))
    {
        newfd = accept(fd,....);
    }
    //依次判断已建立连接的客户端是否有数据
    //...FIXME
    
 }


    return 0;

}

1.select()函数里面的各个文件描述符fd_set集合的参数在select( )前后发生了变化:

前:表示关心的文件描述符集合

后:有数据的集合(如不是在超时还回情况下)
2.那么究竟是谁动了fd_set集合的奶酪?

答曰: kernel

/*UDP demo*/
/*usage:
*./client serv_ip serv_port
*/
 
void usage(char *s)
{
    printf("\nthis is udp demo!\n");
    printf("\n usage:\n\t%s serv_ip serv_port",s);
    printf("\n\t serv_ip:udp server ip address");
    printf("\n\t serv_port:udp server port\n\n");
}
 
int main(int argc,char *argv[])
{
    int fd = -1;
    int port = SERV_PORT;
 
    port = atoi(argv[2]);
    if(port < 0 ||(port > 0 && port < 5000))
    {
        usage(argv[0]);
        exit(1);    
    }
 
    struct sockaddr_in sin;
    /*1.创建socket fd*/
    if(argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    /*1.创建socket fd*/
    if(fd = socket(AF_INET,SOCK_DGRAM,0)<0){ //UDP编程
        perror("socket");
        exit(1);
    }
 
    /*2.1填充struct sockaddr_in 结构体变量*/
    bzero(&sin,sizeof(sin));
 
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);  //网络字节序端口号
    
    /*2.2让服务器程序能绑定在任意IP上*/
#if 0
    sin.sin_addr.s_addr = hton1(INADDR_ANY);
#else
    if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr)! =1)
    {
        perror("inet_pton");
        exit(1);
    }
#endif
#if 0 
    char buf[BUFSIZ];
    while(1){
        bzero(buf,BUFSIZ);
        if(fgets(buf,BUFSIZ-1,stdin) == NULL){
            perror("fgets");
            continue;
        }
        sendto(fd,buf,strlen(buf),0,(struct sockaddr *)&sin,sizeof(sin));
        if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))   //用户输入了quit字符
        {
            printf("client is exited!\n");
            break;
        }
#else
    int ret = -1;
    fd_set rset;
    sruct timeval;
    int maxfd = -1;
    while(1){
        FD_SERO(&rset);
        FD_SET(0,&rset);
        FD_SET(fd,&rset);
        maxfd = fd;
        
        tout.tv_sec = 5;
        tout.tv_usec = 0;
    
        select(maxfd+1,&rset,NULL,NULL,&tout);
        if(FD_ISSET(0,&rset))
        {
            //标准键盘上有输入
            //读取键盘输入,发送到网络套接字fd
            //。。。FIXME!
            bzero(buf,BUFSIZ);
            do{
                ret =  read(0,buf,BUFSIZ-1);
               }while(ret < 0 && EINTER == errno);
                if(ret < 0){
                       perror("read");
                       continue; 
                }
                if(!ret) continue;  /*服务器关闭*/
                if(write(fd,buf,strlen(buf)<0))
                {
                    perror("write() to socket");
                    continue;
                }
            
        }
        if(FD_ISSET(0,&rset))
        {
            //服务器给发送过来的数据
            //读取套接字数据,处理
            bzero(buf,BUFSIZ);
            do{
                ret =  read(0,buf,BUFSIZ-1);
               }while(ret < 0 && EINTER == errno);
                if(ret < 0){
                       perror("read");
                       continue; 
                }
                if(!ret) continue;  /*服务器关闭*/                
    
                if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))   //用户输入了quit字符
                {
                    printf("client is exited!\n");
                    break;
                }
        }
    }   
    }
    close(fd);
 
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netint/ip.h>
#include <errno.h>
#include <pthread.h>
 
 
void usage(char *s)
{
    printf("%s serv_ip serv_port",s);
    printf("\n\t serv_ip: server ip address");
    printf("\n\t serv_port: server port(>5000)\n\t");
 
}
 
 
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.7.246"  //服务器地址
#define BACKLOG 5                     //backlog同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)一般填5,测试得知,ARM最大为8
#define QUIT_STR  "quit"
 
void cli_data_handle(void *arg);
void sig_child_hanle(int signo)
{
       if(SIGCHLD == signo)
       {
            waitpid(-1,NULL,WNOHANG);
       }
 
 
}
 
 
 
 
 
 
int  main(int argc,char **argv)
{
    int fd =-1;
    short port;
    struct socke_in sin;
    signal(SIGCHLD,sig_child_handle);
 
 
 
 
 
 
   if(argc != 3)
    {
        usage(argv[0]);
exit(1);
    }
    port = atoi(argv[2]);
    if(port<5000) 
    {
        usage(argv[0]);
        exit(1);
    }
 
 
   /*1.创建socket fd*/
   if((fd = socket(AF_INET,SOCK_STREAM,0))<0)
   {
        perror("socket");
        exit(1);
    }
  /*优化4:允许绑定地址快速重用*/
      int b_reuse = 1;
      setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
 
    /*2绑定*/
    /*2.1填充struct sockaddr_in结构体变量*/
    bzero(&sin,sizeof(sin));
    sin.sin_family = AF_INET;   //IPV4
    sin.sin_port = htons(port);   //网络字节序的端口号
/*优化1:让服务器程序能绑定在任意的IP上*/
#if 1
    sin.sin_addr.s_addr = hton1(INADDY_ANY);  //点分形式的IP地址,结果为#32整数
#else
    if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr.s_addr)!=1)
    {
       perror("inet_pton");
       exit(1);
    }
#endif
     /*2.2绑定*/
    if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0 )
    {
           perror("bind");
           exit(1);
     }  
    //如果是IPV6编程呢个,要使用struct sockaddr_in6结构体(详细情况请参考man 7 ipv6 ),通常更通用的方法可以通过struct sockaddr_storage来编程
 
    /*3.调用listen()把主动套接字变成被动套接字*/
    
    if(listen(fd,BACKLOG)<0)
    {
        perror("listen");
        exit(1);
 
   }                
    /*4.阻塞等待客户端连接请求*/
 
# if 0 
   /*优化3:用多进程/多线程处理已经建立好连接的客户端数据*/
   pthread_t tid;
   struct sockaddr_in cin;
   socklen_t addrlen = sizeof(cin);
   while(1){
        pid_t pid = -1;
      if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen))< 0)
        {
            perror("accept");
            exit(1);
        }
       char ipv4_addr[16];
       if(!inet_ntop(AF_INET,(void * )&cin.sin_addr,ipv4_addr,sizeof(cin))){
        perror("inet_ntop");
        exit(1);
    }
    printf("client(%s:%d)  is connected!",ipv4_addr,htons(cin.sin_port));
    pthread_create(&tid,NULL,(void *)cli_data_handle,(void *)&newfd);
 
}
#else
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
    while(1){
    if ((newfd = accept(fd,(struct sockaddr *)&cin,&addrlrn))<0){
        perror("accept");
        break;    
    }
    /*创建一个子进程用于处理已建立连接的客户的交流数据*/
    if((pid = fork()) < 0)
    {
        perror("fork");
        break;
    }
    if(0 == pid)//子进程
    {
        close(fd);
        char ipv4_addr[16];
        if(!inet_ntop (AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
        {
            perror("inet_ntop");
            exit(1);
        }
        printf("client(%s:%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port));
        cli_data_handle(&newfd);
        return 0;
    }else{
        //实际上此处pid>0,父进程中
        close(newfd);
    }
 
 
 
 
 
}
 
#endif
   close(fd);
   return 0;
}
 #define SERV_RESP_STR "Server:"
void cli_data_handle(void * arg)
{

    int newfd = *(int *)arg;
    printf("handler thread: newfd = %d\n",newfd);
    int ret = -1;
    char buf[BUFSIZ];
    char resp_buf[BUFSIZ];
    while(1){
        bzero(buf,BUFSIZ);
        do{
            ret = read(newfd,buf,BUFSIZ-1);
         }while(ret<0 &&EINTR == errno);
        if(ret < 0)
        {
            perror("read");
            exit(1);
        }
        if(!ret)  //对方已关闭
        {
            break;
        }
        printf("receive data:  %s\n",buf);
        bzero(resp_buf,BUFFSIZ+10);
        strncpy(resp_buf,SERV_RESP_STR,strlen(SERV_RESP_STR));
        strcat(resp_buf,buf);
        do{
            write(newfd,resp_buf,strlen(resp_buf));
        }while(ret < 0 && EINTR == errno);
        if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){    //用户输入了quit字符
                   printf("client(fd%d) is exiting \n",newfd);
                   break;
         }
    }
 
 
 
 
}

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值