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
extern "C"
}
#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 !\n");
        return -1;
    }
    //网络字节转化成本地字节序
    hostlen = ntohl(pack.len);
    if (len < hostlen)
    {
        printf("socket_recv() 接收缓冲区太小!\n");
        return -1;
    }
    ret = readn(fd, pack.buf, hostlen);
    if (ret == -1)
    {
        perror("readn() err");
        return -1;
    } else if (ret < hostlen)
    {
        printf("peer is closed !\n");
        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 !\n");
        return ret;
    }
    int i = 0;
    for (i = 0; i < len; i++)
    {
        //安装信号
        if (signal(signarr[i], handler) == SIG_ERR)
        {
            ret = -1;
            printf("signal() failed !\n");
            break;
        }
    }
    return ret;
}
时间: 2024-10-20 10:10:36

Linux socket多进程服务器框架一的相关文章

Linux socket多进程服务器框架二

客户端未解决Bug:子进程或者父进程退出的时候,我无法做到两个进程都调用clt_socket_Destory()方式释放socket句柄, 但是进程退出后,相应的资源也会释放,有一定影响,但是不大,以后我想到办法再优化.重点:客户端connect服务器方法需要单独分离出来,方便用户自己断线重连. 客户端 //clthelp.h #include <stdio.h> #include "commsocket.h" #ifndef _vxclt #define _vxclt #

Linux socket多进程服务器框架三

在使用select管理服务器连接的时候: 注意1:select是可中断睡眠函数,需要屏蔽信号 注意2:必须获取select的返回值nread,每次处理完一个事件,nread需要-1 注意3:如果客户端的连接超过连接池的大小,需要关闭客户端连接 注意4:获取最大套接字的方法是每次有客户端连接过来时,在和maxfd比较,这样就不用每次select之前都遍历池,查找最大值 服务器 //serhelp.h #ifndef _vxser #define _vxser #ifdef __cplusplus

PHP socket 服务器框架集

1.Swoole:重新定义PHP PHP语言的高性能网络通信框架,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询.Swoole可以广泛应用于互联网.移动通信.企业软件.网络游戏.物联网.车联网.智能家庭等领域. 使用PHP+Swoole作为网络通信框架,可以使企业IT研发团队的效率大大提升,更加专注于开发创新产品. 2.workerman workerman是一个高性能的PHP

Skynet服务器框架(一) Linux下的安装和启动

根据云风博客的描述,Skynet 的核心功能就是解决一个问题: 把一个符合规范的 C 模块,从 动态库(so文件)中启动起来,绑定一个永不重复(即使模块退出)的数字id做为其 handle.模块 被称为 服务(Service),服务间可以自由发送消息. 每个 模块 可以向 Skynet 框架注册一个 callback 函数,用来接收发给它的消息: 每个服务都是被一个个 消息包 驱动,当没有包到来的时候,它们就会处于 挂起状态,此状态对 CPU 资源零消耗.如果需要自主逻辑,则可以利用 Skyne

linux编程实例--简单多进程服务器

主要利用fork事先创建若干个进程,并发处理多个客户端的连接,返回当前系统时间.具体代码如下: server.c # include <sys/types.h> # include <sys/socket.h> # include <netinet/in.h> # include <time.h> # include <string.h> # include <stdio.h> # include <signal.h> #

php 多进程workman服务器框架

今天搜php socket,发现了一个给力的php写socket的框架workman,有机会要用用. 好给力,原来那个小蝌蚪聊天室就是用这个开发的. 仿佛发现了新大陆. php 多进程workman服务器框架

【Linux学习】基本的多进程测试框架

题目: 编程一个基本多进程测试框架,提示用户输入进程数.和每个进程数运行圈数.进行多进程压力测试.要求父进程能监控所有子进程的退出,避免僵尸进程. #include <stdio.h> #include <unistd.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> void func(int, int);

跨平台网络通信与服务器框架 acl 3.2.0 发布

acl 3.2.0 版本发布了,acl 是 one advanced C/C++ library 的简称,主要包括网络通信库以及服务器框架库等功能,支持 Linux/Windows/Solaris/FreeBsd/MacOS 平台:整个 acl 项目主要包含三个函数库:lib_acl(纯C开发的基础库,主要包含网络通信及服务器编程框架以及其它丰富的功能).lib_protocol(包含 HTTP/PING/SMTP 通信协议的C语言实现).lib_fiber(网络协程库).lib_acl_cpp

LINUX环境并发服务器的三种实现模型

服务器设计技术有很多,按使用的协议来分有TCP服务器和UDP服务器.按处理方式来分有循环服务器和并发服务器. 1  循环服务器与并发服务器模型 在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求,对服务端的程序就提出了特殊的要求. 目前最常用的服务器模型有: ·循环服务器:服务器在同一时刻只能响应一个客户端的请求 ·并发服务器:服务器在同一时刻可以响应多个客户端的请求 1.1 UDP循环服务器的实现方法: UDP循环服务器每次从套接字上读取一个客户端的请求->处理->然后将