zoukankan      html  css  js  c++  java
  • Linux socket多进程服务器框架一

    重点:socket共用方法中错误码的定义以及错误码的解析
    底层辅助代码
    //serhelp.h
    
    #ifndef _vxser
    #define _vxser
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    /**
     * sersocket_init - socket初始化
     * @listenfd:文件描述符
     * 成功返回0,失败返回错误码
     * */
    int sersocket_init(int *listenfd);
    
    /**
     * listen_socket - 绑定端口号,监听套接字
     * @listenfd:文件描述符
     * @port:绑定的端口号
     * 成功返回0,失败返回错误码
     * */
    int listen_socket(int listenfd, int port);
    
    /**
     * run_server - 运行服务器
     * @listenfd:文件描述符
     * */
    void run_server(int listenfd);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    //sockhelp.c
    //socket发送接收底层辅助方法
    /*底层辅助方法不打印错误信息,由上层调用通过errno打印信息,并且不做参数验证,有调用函数验证*/
    
    #include "sockhelp.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <fcntl.h>
    
    
    
    /**
     * readn - 读取指定大小的字节
     * @fd:文件描述符
     * @buf:接收字节缓冲区
     * @count:指定的字节数
     * 成功返回指定字节数,失败返回-1,对方连接已经关闭,返回已经读取字节数<count
     * */
    int readn(int fd, void *buf, int count)
    {
        //定义剩余字节数
        int lread = count;
        //定义每次读取的字节数
        int nread = 0;
        //定义辅助指针变量
        char *pbuf = (char *) buf;
        //如果剩余字节数大于0,循环读取
        while (lread > 0)
        {
            nread = read(fd, pbuf, lread);
            if (nread == -1)
            {
                //read()是可中断睡眠函数,需要屏蔽信号
                if (errno == EINTR)
                    continue;
                //read()出错,直接退出
                return -1;
            } else if (nread == 0)
            {
                //对方关联连接
                return count - lread;
            }
            //重置剩余字节数
            lread -= nread;
            //辅助指针变量后移
            pbuf += nread;
        }
        return count;
    }
    
    /**
     * writen - 写入指定大小的字节
     * @fd:文件描述符
     * @buf:发送字节缓冲区
     * @count:指定的字节数
     * 成功返回指定字节数,失败返回-1
     * */
    int writen(int fd, void *buf, int count)
    {
        //剩余字节数
        int lwrite = count;
        //每次发送字节数
        int nwrite = 0;
        //定义辅助指针变量
        char *pbuf = (char *) buf;
        while (lwrite > 0)
        {
            nwrite = write(fd, pbuf, lwrite);
            if (nwrite == -1)
            {
                //注意:由于有TCP/IP发送缓存区,所以即使对方关闭连接,发送也不一定会失败
                //所以需要捕捉SIGPIPE信号
                return -1;
            }
            lwrite -= nwrite;
            pbuf += nwrite;
        }
        return count;
    }
    
    /**
     * read_timeout - 读超时检测函数,不含读操作
     * @fd:文件描述符
     * @wait_seconds:等待超时秒数,如果为0表示不检测超时
     * 成功返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
     * */
    int read_timeout(int fd, unsigned int wait_seconds)
    {
        int ret = 0;
        if (wait_seconds > 0)
        {
            //定义文件描述符集合
            fd_set readfds;
            //清空文件描述符
            FD_ZERO(&readfds);
            //将当前文件描述符添加集合中
            FD_SET(fd, &readfds);
            //定义时间变量
            struct timeval timeout;
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
            } while (ret == -1 && errno == EINTR);
            //ret==-1时,返回的ret正好就是-1
            if (ret == 0)
            {
                errno = ETIMEDOUT;
                ret = -1;
            } else if (ret == 1)
            {
                ret = 0;
            }
        }
        return ret;
    }
    
    /**
     * write_timeout - 写超时检测函数,不含写操作
     * @fd:文件描述符
     * @wait_seconds:等待超时秒数,如果为0表示不检测超时
     * 成功返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
     * */
    int write_timeout(int fd, unsigned int wait_seconds)
    {
        int ret = 0;
        if (wait_seconds > 0)
        {
            //定义文件描述符集合
            fd_set writefds;
            //清空集合
            FD_ZERO(&writefds);
            //添加文件描述符
            FD_SET(fd, &writefds);
            //定义时间变量
            struct timeval timeout;
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
            } while (ret == -1 && errno == EINTR);
            if (ret == 0)
            {
                errno = ETIMEDOUT;
                ret = -1;
            } else if (ret == 1)
            {
                ret = 0;
            }
        }
        return ret;
    }
    
    /**
     * accept_timeout - 带超时accept (方法中已执行accept)
     * @fd:文件描述符
     * @addr:地址结构体指针
     * @wait_seconds:等待超时秒数,如果为0表示不检测超时
     * 成功返回已连接的套接字,失败返回-1,超时返回-1并且errno = ETIMEDOUT
     * */
    int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
    {
        int ret = 0;
        if (wait_seconds > 0)
        {
            /*
             * 说明:accept和connect都会阻塞进程,accept的本质是从listen的队列中读一个连接,是一个读事件
             * 三次握手机制是由TCP/IP协议实现的,并不是由connect函数实现的,connect函数只是发起一个连接,
             * connect并非读写事件,所以只能设置connect非阻塞,而用select监测写事件(读事件必须由对方先发送报文,时间太长了)
             * 所以accept可以由select管理
             * 强调:服务端套接字是被动套接字,实际上只有读事件
             * */
            fd_set readfds;
            FD_ZERO(&readfds);
            FD_SET(fd, &readfds);
            struct timeval timeout;
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
            } while (ret == -1 && errno == EINTR);
            if (ret == -1)
            {
                return -1;
            } else if (ret == 0)
            {
                ret = -1;
                errno = ETIMEDOUT;
                return ret;
            }
            //成功无需处理,直接往下执行
        }
        //一旦检测出select有事件发生,表示有三次握手成功的客户端连接到来了
        //此时调用accept不会被阻塞
        if (addr != NULL)
        {
            socklen_t len = sizeof(struct sockaddr_in);
            ret = accept(fd, (struct sockaddr *) addr, &len);
        } else
        {
            ret = accept(fd, NULL, NULL);
        }
        return ret;
    }
    
    /**
     * activate_nonblock - 设置套接字非阻塞
     * @fd:文件描述符
     * 成功返回0,失败返回-1
     * */
    int activate_nonblock(int fd)
    {
        int ret = 0;
        int flags = fcntl(fd, F_GETFL);
        if (flags == -1)
            return -1;
        flags = flags | O_NONBLOCK;
        ret = fcntl(fd, F_SETFL, flags);
        if (ret == -1)
            return -1;
        return ret;
    }
    
    /**
     * deactivate_nonblock - 设置套接字阻塞
     * @fd:文件描述符
     * 成功返回0,失败返回-1
     * */
    int deactivate_nonblock(int fd)
    {
        int ret = 0;
        int flags = fcntl(fd, F_GETFL);
        if (flags == -1)
            return -1;
        flags = flags & (~O_NONBLOCK);
        ret = fcntl(fd, F_SETFL, flags);
        if (ret == -1)
            return -1;
        return ret;
    }
    
    /**
     * connect_timeout - 带超时的connect(方法中已执行connect)
     * @fd:文件描述符
     * @addr:地址结构体指针
     * @wait_seconds:等待超时秒数,如果为0表示不检测超时
     * 成功返回0.失败返回-1,超时返回-1并且errno = ETIMEDOUT
     * */
    int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
    {
        int ret = 0;
        //connect()函数是连接服务器,本来connect会阻塞,但是设置未阻塞之后,
        //客户端仍然会三次握手机制,如果三次握手失败,那么客户端一定无法向文件描述符中写入数据
        //如果连接成功,那么客户端就可以向文件描述符写入数据了,
        //所以交给select监管的文件描述符如果可以写,说明连接成功,不可以写说明连接失败
    
        //设置当前文件描述符未阻塞--设置非阻塞之后,
        //connect在网络中非常耗时,所以需要设置成非阻塞,如果有读事件,说明可能连接成功
        //这样有利于做超时限制
        if (wait_seconds > 0)
        {
            if (activate_nonblock(fd) == -1)
                return -1;
        }
        ret = connect(fd, (struct sockaddr *) addr, sizeof(struct sockaddr));
        if (ret == -1 && errno == EINPROGRESS)
        {
            fd_set writefds;
            FD_ZERO(&writefds);
            FD_SET(fd, &writefds);
            struct timeval timeout;
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
            } while (ret == -1 && errno == EINTR);
            //ret==-1 不需要处理,正好给ret赋值
            //select()报错,但是此时不能退出当前connect_timeout()函数
            //因为还需要取消文件描述符的非阻塞
            if (ret == 0)
            {
                errno = ETIMEDOUT;
                ret = -1;
            } else if (ret == 1)
            {
                //ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,
                //此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。
                int err = 0;
                socklen_t len = sizeof(err);
                ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
                if (ret == 0 && err != 0)
                {
                    errno = err;
                    ret = -1;
                }
                //说明套接字没有发生错误,成功
            }
        }
        if (wait_seconds > 0)
        {
            if (deactivate_nonblock(fd) == -1)
                return -1;
        }
        return ret;
    }
    socket共用代码
    //commsocket.h
    #include "sockhelp.h"
    
    #ifndef _vx2016
    #define _vx2016
    
    /*
     * 思考:select超时应该用在客户端,客户对时间有要求,
     * 客户端不一定支持select,并且客户端IO也不多,所以管理IO使用多进程
     * 服务器不需要使用select超时,但是需要select管理客户端连接和监听套接字
     * */
    
    //定义错误码
    #define OK 0
    #define Sck_BaseErr 3000
    #define Sck_MacErr (Sck_BaseErr+1)
    #define Sck_TimeoutErr (Sck_BaseErr+2)
    #define Sck_ParamErr (Sck_BaseErr+3)
    #define Sck_PipeClosed (Sck_BaseErr+4)
    
    #define MAXBUFSIZE 1020 //留出4个字节存放包体大小
    
    //定义粘包结构
    typedef struct _packet
    {
        int len; //报文长度
        char buf[MAXBUFSIZE]; //包体
    } Packet;
    
    //定义socket结构
    typedef struct _mysock
    {
        int fd;
    } Mysock;
    
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    /**
     * strsockerr - 错误码转成字符串
     * @err:错误码
     * 返回错误信息
     * */
    char * strsockerr(int err);
    
    /**
     * socket_send - 报文发送
     * @fd:文件描述符
     * @buf:写入缓冲区
     * @buflen:写入数据长度
     * 成功返回0,失败返回-1
     * */
    int socket_send(int fd, void *buf, int buflen);
    
    /**
     * socket_recv - 报文接收
     * @fd:文件描述符
     * @buf:接收缓冲区
     * @buflen:接收数据长度
     * 成功返回0,失败返回-1
     * */
    int socket_recv(int fd, void *buf, int *buflen);
    
    /**
     * InstallSignal - 安装信号
     * @signarr:信号数组
     * @len:信号数组的长度
     * 成功返回0,失败返回错误码
     * */
    int Install_Signal(int *signarr, int len,void (*handler)(int));
    
    #ifdef __cplusplus
    extern "C"
    }
    #endif
    
    #endif
    //commsocket.c -- socket上层方法实现
    #include "commsocket.h"
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/wait.h>
    #include <signal.h>
    
    /**
     * strsockerr - 错误码转成字符串
     * @err:错误码
     * 返回错误信息
     * */
    char * strsockerr(int err)
    {
        switch (err)
        {
        case OK:
            return "success!";
        case Sck_BaseErr:
            return "方法内部错误!";
        case Sck_MacErr:
            return "malloc内存错误!";
        case Sck_TimeoutErr:
            return "select 超时错误!";
        case Sck_ParamErr:
            return "方法参数列表错误!";
        case Sck_PipeClosed:
            return "对等方已经关闭连接!";
        default:
            return "未识别错误码!";
        }
    }
    
    /**
     * socket_send - 报文发送
     * @fd:文件描述符
     * @buf:写入缓冲区
     * @buflen:写入数据长度
     * 成功返回0,失败返回错误码
     * */
    int socket_send(int fd, void *buf, int buflen)
    {
        int ret = 0;
        Packet pack;
        memset(&pack, 0, sizeof(pack));
        //本地字节序转化成网络字节序
        pack.len = htonl(buflen);
        strncpy(pack.buf, buf, MAXBUFSIZE);
        ret = writen(fd, &pack, buflen + 4);
        if (ret == -1)
        {
            ret=Sck_BaseErr;
            perror("writen() err");
            return ret;
        } else if (ret == buflen)
        {
            ret = 0;
        }
        return ret;
    }
    
    /**
     * socket_recv - 报文接收
     * @fd:文件描述符
     * @buf:接收缓冲区
     * @buflen:接收数据长度
     * 成功返回0,失败返回错误码
     * */
    int socket_recv(int fd, void *buf, int *buflen)
    {
        int ret = 0;
        //定义包体长度
        int len = *buflen;
        int hostlen = 0;
        Packet pack;
        memset(&pack, 0, sizeof(pack));
        //获取报文字节数
        ret = readn(fd, &pack.len, 4);
        if (ret == -1)
        {
            perror("readn() err");
            return -1;
        } else if (ret < 4)
        {
            printf("peer is closed !
    ");
            return -1;
        }
        //网络字节转化成本地字节序
        hostlen = ntohl(pack.len);
        if (len < hostlen)
        {
            printf("socket_recv() 接收缓冲区太小!
    ");
            return -1;
        }
        ret = readn(fd, pack.buf, hostlen);
        if (ret == -1)
        {
            perror("readn() err");
            return -1;
        } else if (ret < hostlen)
        {
            printf("peer is closed !
    ");
            return -1;
        }
        *buflen = hostlen;
        strncpy(buf, pack.buf, hostlen);
        return ret;
    }
    
    /**
     * InstallSignal - 安装信号
     * @signarr:信号数组
     * @len:信号数组的长度
     * 成功返回0,失败返回错误码
     * */
    int Install_Signal(int *signarr, int len,void (*handler)(int))
    {
        int ret = 0;
        if (signarr == NULL)
        {
            ret = -1;
            printf("Install_Signal() param not correct !
    ");
            return ret;
        }
        int i = 0;
        for (i = 0; i < len; i++)
        {
            //安装信号
            if (signal(signarr[i], handler) == SIG_ERR)
            {
                ret = -1;
                printf("signal() failed !
    ");
                break;
            }
        }
        return ret;
    }
  • 相关阅读:
    Windbg命令学习11(.dump)
    Windbg命令学习13(ln和伪寄存器)
    Windbg命令学习15(bp bm bu bl bc ba断点)
    Windbg命令学习16(!gle和g和p)
    Windbg命令学习0 (.symfix和.cls和设置Log文件)
    API拦截方法一:PE简介
    Windbg命令学习12(.lastevent和!analyze)
    Windbg命令学习14(dv)
    Davinci DM6446开发攻略——LINUX GPIO驱动源码移植
    Centos 编译安装高版本Python方法
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/6181666.html
Copyright © 2011-2022 走看看