zoukankan      html  css  js  c++  java
  • 第15章 高并发服务器编程(1)_非阻塞I/O模型

    1. 高性能I/O

    (1)通常,recv函数没有数据可用时会阻塞等待。同样,当socket发送缓冲区没有足够多空间来发送消息时,函数send会阻塞。

    (2)当socket在非阻塞模式下,这些函数不会阻塞,如果发送/接收缓冲区没有数据时调用会失败并设置errno为EWOULDBLOCK或EAGAIN。

    (3)可以调用fcntl函数实现非阻塞式I/O或调用select实现I/O多路复用以提高使用I/O而出现的效率问题。

    2. 非阻塞I/O模型: fcntl函数

     

    【编程实验】echo服务器(非阻塞IO方式实现)

     

    (1)主线程创建一个服务于所有客户端的子线程。

    (2)主线程调用accept与客户端建立连接,并将新的socket设置为非阻塞方式。然后将这个新的socket放入数组中。

    (3)利用一个子线程遍历(轮询)数组中各个socket,并调用read/write(非阻塞式)与客户端进行通信。

    //vector_fd.h

    #ifndef __VECTOR_H__
    #define __VECTOR_H__
    
    #include <pthread.h>
    
    //用于存放sock的动态数组(线程安全!)
    typedef struct{
        int     *fd;
        int     counter;    //元素个数
        int     max_counter;//最多存数个数,会动态增长
        pthread_mutex_t mutex; 
    }VectorFD, *PVectorFD;
    
    //动态数组相关的操作函数
    extern  VectorFD*  create_vector_fd(void);
    extern  void       destroy_vector_fd(VectorFD* vfd);
    extern  int        get_fd(VectorFD* vfd, int index);
    extern  void       remove_fd(VectorFD* vfd, int fd);
    extern  void       add_fd(VectorFD* vfd, int fd);
    
    #endif

    //vector_fd.c  //动态数组操作函数

    #include "vector_fd.h"
    #include <memory.h>
    #include <malloc.h>
    #include <assert.h>
    
    //查找指定fd在数组中的索引值
    static int indexof(VectorFD* vfd, int fd)
    {
        int ret = -1;
    
        int i=0;
        for(; i<vfd->counter; i++){
            if(vfd->fd[i] == fd){
                ret = i;
                break;
            }
        }
    
        return ret;
    }
    
    //数组空间的动态增长
    static void encapacity(VectorFD* vfd)
    {
        if(vfd->counter >=vfd->max_counter){
            int* fds = (int*)calloc(vfd->counter + 5, sizeof(int));
            assert(fds != NULL);
            memcpy(fds, vfd->fd, sizeof(int) * vfd->counter);
            
            free(vfd->fd);
            vfd->fd = fds;
            vfd->max_counter += 5;
        }
    }
    
    //动态数组相关的操作
    VectorFD*  create_vector_fd(void)
    {
        VectorFD* vfd = (VectorFD*)calloc(1, sizeof(VectorFD));
        assert(vfd != NULL);
        
        //分配存放fd的数组空间
        vfd->fd = (int*)calloc(5, sizeof(int));
        assert(vfd->fd != NULL);
    
        vfd->counter = 0;
        vfd->max_counter = 0;
    
        //对互斥锁进行初始化
        pthread_mutex_init(&vfd->mutex, NULL);
    
        return vfd;
    }
    
    void  destroy_vector_fd(VectorFD* vfd)
    {
        assert(vfd != NULL);
        //销毁互斥锁
        pthread_mutex_destroy(&vfd->mutex);
        
        free(vfd->fd);
        free(vfd);
    }
    
    int  get_fd(VectorFD* vfd, int index)
    {
        int ret = 0;
        assert(vfd != NULL);
        
        pthread_mutex_lock(&vfd->mutex);
    
        if((0 <= index) && (index < vfd->counter)){
            ret = vfd->fd[index];
        }
    
        pthread_mutex_unlock(&vfd->mutex);
    
        return ret;
    }
    
    void  remove_fd(VectorFD* vfd, int fd)
    {
        assert(vfd != NULL);
        
        pthread_mutex_lock(&vfd->mutex);
    
        int index = indexof(vfd, fd);
    
        if(index >= 0){
            int i = index;
            for(; i<vfd->counter-1; i++){
                 vfd->fd[i] = vfd->fd[i+1];   
            }
            
            vfd->counter--;
        }
       
        pthread_mutex_unlock(&vfd->mutex);
    }
    
    void  add_fd(VectorFD* vfd, int fd)
    {
        assert(vfd != NULL);
        
        encapacity(vfd);
        vfd->fd[vfd->counter++] = fd;
    }

    //echo_tcp_server_fcntl.c

    #include <netdb.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <time.h>
    #include <pthread.h>
    #include <signal.h>
    #include <errno.h>
    #include "vector_fd.h"
    #include <fcntl.h>
    
    /*基于非阻塞IO的高并发服务器编程
    测试:telnet 127.0.0.1 xxxx 
          http://xxx.xxx.xxx.xxx:端口号
    注意:演示时可关闭服务器的防火墙,防火墙口被过滤
          #service iptables status     查看防火墙
          #service iptables stop       关闭防火墙
    */
    
    VectorFD* vfd;
    int sockfd;
    int bStop = 0;
    
    void sig_handler(int signo)
    {
        if(signo == SIGINT){
            bStop = 1;
            printf("server close
    ");
            exit(1);
        }
    }
    
    void out_addr(struct sockaddr_in* clientAddr)
    {
        char ip[16];
        memset(ip, 0, sizeof(ip));
        int port = ntohs(clientAddr->sin_port);
        inet_ntop(AF_INET, &clientAddr->sin_addr.s_addr, ip, sizeof(ip));
    
        printf("%s(%d) connnected!
    ", ip, port);
    }
    
    /*服务程序
     *  fd对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
     */
    void do_service(int fd)
    {
        /*服务端和客户端进行读写操作(双向通信)*/
        char buff[512];
        
        memset(buff, 0, sizeof(buff));
        size_t size = read(fd, buff, sizeof(buff));
    
        //读取客户端发送过来的消息
        //由于采用非阻塞方式,若读不到数据直接返回了,直接服务于下一个客户端
        //因此不需要判断size小于0的情况。
        if(size == 0){  //客户端己关闭连接
            char info[] = "client close
    ";
            write(STDOUT_FILENO, info, sizeof(info));
    
            //将fd从动态数组中删除
            remove_fd(vfd, fd);
            close(fd);
        }else if(size > 0){
            write(STDOUT_FILENO, buff, sizeof(buff));//显示客户端发送的消息
            //写回客户端(回显功能)
            if(write(fd, buff, sizeof(buff)) != size){
                if(errno == EPIPE){
                    //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号
                    //并将errno设置为EPIPE
                    perror("write error");
                    remove_fd(vfd, fd);
                    close(fd);   
                }
            }
        }
    }
    
    //线程函数
    void* th_fn(void* arg)
    {
        int i= 0;
        //轮询动态数组中的socket描述符
        while(!bStop){
            for(i=0; i<vfd->counter; i++){
                do_service(get_fd(vfd, i));
            }
        }
    
        return (void*)0;
    }
    
    int main(int argc, char* argv[])
    {
        if(argc < 2){
            printf("usage: %s port
    ", argv[0]);
            exit(1);
        }
    
        //按ctrl-c时中止服务端程序
        if(signal(SIGINT, sig_handler) == SIG_ERR){
            perror("signal sigint error");
            exit(1);
        }
    
        /*步骤1:创建socket(套接字)
         *注:socket创建在内核中,是一个结构体
         *AF_INET:IPv4
         *SOCK_STREAM:tcp协议
         */
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        /*步骤2:将sock和地址(包括ip、port)进行绑定*/
        struct sockaddr_in servAddr; //使用专用地址结构体
        memset(&servAddr, 0, sizeof(servAddr));
        //往地址中填入ip、port和Internet地址族类型
        servAddr.sin_family = AF_INET;//IPv4
        servAddr.sin_port = htons(atoi(argv[1])); //port
        servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP
    
        if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) <0 ){
            perror("bind error");
            exit(1);
        }
    
        /*步骤3:调用listen函数启动监听
         *       通知系统去接受来自客户端的连接请求
         */
        if(listen(sockfd, 10) < 0){  //队列中最多允许10个连接请求
            perror("listen error");
            exit(1);
        }
    
        //创建放置套接字描述符的动态数组
        vfd = create_vector_fd();
    
        //设置线程的分离属性
        pthread_t  th;
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        //启动子线程
        int err;
        if((err = pthread_create(&th, &attr, th_fn, (void*)0)) != 0){
            perror("pthread create error");
            exit(1);
        }
        pthread_attr_destroy(&attr);
        
        /*(1)主线程获得客户端连接,将新的socket描述符放置到动态数组中
         *(2)子线程负责遍历动态数组中socket描述符并和对应的客户端进行
         *   双向通信(采用非阻塞方式读写)
         */
    
        struct sockaddr_in clientAddr;
        socklen_t len = sizeof(clientAddr);
    
        while(!bStop){
            /*步骤4:调用accept函数,从请求队列中获取一个连接
             *       并返回新的socket描述符
             * */
            int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &len);
           
            if(fd < 0){
                perror("accept error");
                continue;
            }
            
            //将读写修改为非阻塞方式
            int val;
            fcntl(fd, F_GETFL, &val);
            val |= O_NONBLOCK; //非阻塞式
            fcntl(fd, F_SETFL, val);
    
            //输出客户端信息
            out_addr(&clientAddr);
            
            //将返回的新socket描述符加入到动态数组中
            add_fd(vfd, fd);
        }
    
        close(sockfd);
        destroy_vector_fd(vfd);
    
        return 0;
    }
    /*输出结果
     * [root@localhost 15.AdvNet]# gcc -o bin/echo_tcp_client src/echo_tcp_client.c 
     * [root@localhost 15.AdvNet]# bin/echo_tcp_server_fcntl 8888                               
     * 127.0.0.1(40694) connnected!
     * abcdefaadeafcdafacdaegadeageadfacadegadaddeagdafddeagd^Cserver close
     */

    //echo_tcp_client.c

    #include <netdb.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    
    int main(int argc, char* argv[])
    {
        if(argc < 3){
            printf("usage: %s ip port
    ", argv[0]);
            exit(1);
        }
    
        /*步骤1: 创建socket(套接字)*/
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0){
            perror("socket error");
        }
    
        //往servAddr中填入ip、port和地址族类型
        struct sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;
        servAddr.sin_port = htons(atoi(argv[2]));
        //将ip地址转换成网络字节序后填入servAdd中
        inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr);
    
        /*步骤2: 客户端调用connect函数连接到服务器端*/
        if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){
            perror("connect error");
            exit(1);
        }
    
        /*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/
        char buff[512];
        size_t size;
        char* prompt = ">";
    
        while(1){
            memset(buff, 0, sizeof(buff));
            write(STDOUT_FILENO, prompt, 1);
            size = read(STDIN_FILENO, buff, sizeof(buff));
            if(size < 0) continue;
    
            buff[size-1] = '';
            //将键盘输入的内容发送到服务端
            if(write(sockfd, buff, sizeof(buff)) < 0){
                perror("write error");
                continue;
            }else{
                memset(buff, 0, sizeof(buff));
                //读取来自服务端的消息
                if(read(sockfd, buff, sizeof(buff)) < 0){
                    perror("read error");
                    continue;
                }else{
                    printf("%s
    ", buff);
                }
            }
        }
    
        /*关闭套接字*/
        close(sockfd);
    }
    /*输出结果
     *[root@localhost 15.AdvNet]# bin/echo_tcp_client 127.0.0.1 8888
     >abcdef
     abcdef
     >aade
     aade
     >afcdaf
     afcdaf
     >acdaeg
     acdaeg
     >^C
     */
  • 相关阅读:
    SDWebImage笔记
    ASIHTTPRequest类库简介和使用说明
    UIBezierPath 的使用
    SQL Server中的系统表sysobjects使用方法,实现循环遍历所有表(转)
    字符串位数不足8位,则在左边补充0以补足8位数的方法
    SQL表中的自连接定义与用法示例
    SQL Server中 左联接,右联接,内联接的区别
    关于Page_ClientValidate方法,完美实现验证控件成功后confirm确认效果
    给密码类型的TextBox赋值
    利用List的Sort()、Find()、FindAll()、Exist()来解决一些问题
  • 原文地址:https://www.cnblogs.com/5iedu/p/6683358.html
Copyright © 2011-2022 走看看