Linux Linux程序练习十九

题目:编写一个同步服务器模型
要求:
1)客户端A主机给服务器B主机发送报文,
2)B服务器主机收到报文以后同时分发给C1主机、C2主机;
3)C1主机和C2主机打印出客户端A的报文
bug总结:本来这道题目并不困难,就是向客户端连接池中的其他客户端发送数据,但是我这里出现了一个失误,我把接收到的数据直接发送了。
第一步:recv_packet(fd, &pack, &buflen, 0);第二步:send_packet(cltpool[i], &pack, buflen, 0);但是第二步中buflen的实际长度应该比buflen+4,导致发送的结构体不完整,接收失败,效果类似于没有接收到消息
核心代码
#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/select.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include <pthread.h>
#include "commsock.h"

#define MAXBUFSIZE 1020

//报文结构
typedef struct _packet
{
    int len;
    char buf[MAXBUFSIZE];
} Packet;

/**
 * readn - 读取固定大小的字节
 * @fd:文件描述符
 * @buf:接收缓冲区
 * @count:指定读取字节数
 * 成功返回count,失败返回-1,对等方连接关闭返回<count
 * */
int readn(int fd, void *buf, int count)
{
    int nread = 0;
    int lread = count;
    char *pbuf = (char *) buf;
    while (lread > 0)
    {
        do
        {
            nread = read(fd, pbuf, lread);
        } while (nread == -1 && errno == EINTR);
        if (nread == -1)
            return -1;
        else if (nread == 0)
            return count - lread;
        lread -= nread;
        pbuf += nread;
    }
    return count;
}

/**
 * writen - 写固定大小字节数
 * @fd:文件描述符
 * @buf:写入缓冲区
 * @count:指定写入字节数
 * 成功返回count,失败返回-1
 * */
int writen(int fd, void *buf, int count)
{
    int lwrite = count;
    int nwrite = 0;
    char *pbuf = (char *) buf;
    while (lwrite > 0)
    {
        do
        {
            nwrite = write(fd, pbuf, lwrite);
        } while (nwrite == -1 && errno == EINTR);
        if (nwrite == -1)
            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
        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);
        //ret==-1
        if (ret == 0)
        {
            errno = ETIMEDOUT;
            ret = -1;
        } else if (ret == 1)
        {
            ret = 0;
        }
    }
    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);
    //ret==-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);
    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;
    if (wait_seconds > 0)
    {
        if (activate_nonblock(fd) == -1)
            return -1;
    }
    ret = connect(fd, (struct sockaddr *) addr, sizeof(struct sockaddr_in));
    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;
        int nwrite = select(fd + 1, NULL, &writefds, NULL, &timeout);
        //nwrite==-1 此时ret==-1
        if (nwrite == 0)
            errno = ETIMEDOUT;
        else if (nwrite == 1)
        {
            int err = 0;
            socklen_t len = sizeof(err);
            ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
            if (ret == 0)
            {
                if (err != 0)
                {
                    errno = err;
                    ret = -1;
                }
            }
        }
    }
    if (wait_seconds > 0)
    {
        if (deactivate_nonblock(fd) == -1)
            return -1;
    }
    return ret;
}

/**
 * sock_init - 初始化SOCKET环境
 * @connid:连接套接字
 * 成功返回0,失败返回错误码
 * */
int sock_init(int *connid)
{
    int ret = 0;
    if (connid == NULL)
    {
        ret = SckParamErr;
        printf("cltsock_init() params not correct !\n");
        return ret;
    }
    //init
    ret = socket(AF_INET, SOCK_STREAM, 0);
    if (ret == -1)
    {
        ret = SckBaseErr;
        perror("socket() err");
        return ret;
    } else
    {
        *connid = ret;
        ret = 0;
    }
    return ret;
}

/**
 * connect_server - 连接服务器
 * @connid:连接套接字
 * @port:端口号
 * @ipaddr:IP地址
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回0,失败返回错误码
 * */
int connect_server(int connid, int port, char *ipaddr,
        unsigned int wait_seconds)
{
    int ret = 0;
    if (connid < 0 || port < 0 || port > 65535 || ipaddr == NULL
            || wait_seconds < 0)
    {
        ret = SckParamErr;
        printf("cltsock_init() params not correct !\n");
        return ret;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ret = connect_timeout(connid, &addr, wait_seconds);
    if (ret == -1)
    {
        if (errno == ETIMEDOUT)
        {
            ret = SckTimeOut;
            printf("connect_timeout() time out !\n");
            return ret;
        }
        ret = SckBaseErr;
        perror("connect_timeout() err");
        return ret;
    }
    return ret;
}

/**
 * send_packet - 发送数据包
 * @fd:文件描述符
 * @pack:数据包
 * @buflen:数据包大小
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回0,失败返回错误码
 * */
int send_packet(int fd, Packet *pack, int buflen, unsigned int wait_seconds)
{
    int ret = 0;
    //可写检测
    ret = write_timeout(fd, wait_seconds);
    if (ret == -1)
    {
        if (errno == ETIMEDOUT)
        {
            ret = SckTimeOut;
            printf("write_timeout() time out !\n");
            return ret;
        }
        ret = SckBaseErr;
        perror("write_timeout() err");
        return ret;
    }
    //发送数据
    ret = writen(fd, pack, buflen);
    if (ret != buflen)
    {
        ret = SckBaseErr;
        perror("writen() err");
        return ret;
    } else
    {
        ret = 0;
    }
    return ret;
}

/**
 * send_packet - 接收数据包
 * @fd:文件描述符
 * @pack:数据包
 * @buflen:数据包大小
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 成功返回0,失败返回错误码
 * */
int recv_packet(int fd, Packet *pack, int *buflen, unsigned int wait_seconds)
{
    int ret = 0;
    //读超时检测
    ret = read_timeout(fd, wait_seconds);
    if (ret == -1)
    {
        if (errno == ETIMEDOUT)
        {
            ret = SckTimeOut;
            printf("read_timeout() time out !\n");
            return ret;
        }
        ret = SckBaseErr;
        perror("read_timeout() err");
        return ret;
    }
    //获取数据长度
    int len = 0;
    ret = readn(fd, &pack->len, 4);
    if (ret == -1)
    {
        ret = SckBaseErr;
        perror("readn() err");
        return ret;
    } else if (ret < 4)
    {
        ret = SckPeerClosed;
        printf("peer is closed !\n");
        return ret;
    }
    //网络字节序转化成本地字节序
    len = ntohl(pack->len);
    //获取包体
    ret = readn(fd, pack->buf, len);
    if (ret == -1)
    {
        ret = SckBaseErr;
        perror("readn() err");
        return ret;
    } else if (ret < len)
    {
        ret = SckPeerClosed;
        printf("peer is closed !\n");
        return ret;
    } else if (ret == len)
    {
        ret = 0;
    }
    *buflen = len;
    return ret;
}

/**
 * start_thread - 客户端线程回调函数
 * @arg:参数
 * */
void *start_thread(void *arg)
{
    if (arg == NULL)
    {
        printf("start_thread() params not correct !\n");
        return NULL;
    }
    int fd = (int) arg;
    int ret = 0;
    //接收信息并且打印
    Packet pack;
    int buflen = MAXBUFSIZE;
    while (1)
    {
        memset(&pack, 0, sizeof(pack));
        ret = recv_packet(fd, &pack, &buflen, 500);
        if (ret != 0)
        {
            printf("客户端线程退出了!\n");
            //退出当前进程
            pthread_exit(NULL);
        }
        //打印数据
        fputs(pack.buf, stdout);
        //fflush(stdout);
    }
    return NULL;
}

/**
 * run_clt - 运行客户端
 * @connid:连接套接字
 * @wait_seconds:等待超时秒数,如果为0表示不检测超时
 * 失败返回错误码
 * */
int run_clt(int connid, unsigned int wait_seconds)
{
    int ret = 0;
    //安装信号
    if (signal(SIGPIPE, handler) == SIG_ERR)
    {
        ret = SckBaseErr;
        printf("signal() failed !\n");
        return ret;
    }
    //开始多线程
    pthread_t thr1;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    //设置进程为可分离状态
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (pthread_create(&thr1, &attr, start_thread, connid) != 0)
    {
        ret = SckBaseErr;
        printf("pthread_create() failed !\n");
        return ret;
    }
    Packet pack;
    memset(&pack, 0, sizeof(pack));
    int buflen = 0;
    while (fgets(pack.buf, MAXBUFSIZE, stdin) != NULL)
    {
        //去除\n
        buflen = strlen(pack.buf);
        pack.len = htonl(buflen);
        //发送数据
        ret = send_packet(connid, &pack, buflen + 4, wait_seconds);
        if (ret != 0)
        {
            return ret;
        }
    }
    return ret;
}

/**
 * close_socket - 关闭连接
 * @fd:文件描述符
 * 成功返回0
 * */
int close_socket(int fd)
{
    int ret = 0;
    close(fd);
    return ret;
}

/*
 * clear_back - 退格键不回显
 * 成功返回0,失败返回错误码
 * */
int clear_back()
{
    int ret = 0;
    struct termios term;
    memset(&term, 0, sizeof(term));
    //获取当前系统设置
    if (tcgetattr(STDIN_FILENO, &term) == -1)
    {
        ret = SckBaseErr;
        perror("tcgetattr() err");
        return ret;
    }
    //修改系统设置
    term.c_cc[VERASE] = ‘\b‘;
    //立即生效
    if (tcsetattr(STDIN_FILENO, TCSANOW, &term) == -1)
    {
        ret = SckBaseErr;
        perror("tcsetattr() err");
        return ret;
    }
    return ret;
}

/**
 * listen_socket - 创建服务器监听套接字
 * @fd:套接字
 * @port:端口号
 * 成功返回0,失败返回错误码
 * */
int listen_socket(int fd, int port)
{
    int ret = 0;
    if (port < 0 || port < 0 || port > 65535)
    {
        ret = SckParamErr;
        printf("listen_socket() params not correct !\n");
        return ret;
    }
    //reuse addr
    int optval = 1;
    ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if (ret == -1)
    {
        ret = SckBaseErr;
        perror("setsockopt() err");
        return ret;
    }
    //bind
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr));
    if (ret == -1)
    {
        ret = SckBaseErr;
        perror("bind() err");
        return ret;
    }
    //listen
    ret = listen(fd, SOMAXCONN);
    if (ret == -1)
    {
        ret = SckBaseErr;
        perror("listen() err");
        return ret;
    }
    return ret;
}

/**
 * product_clt - 处理客户端信息
 * @fd:客户端A文件描述符
 * @cltpool:客户端套接字池
 * @maxindex:连接池最后一个元素的下标
 * 成功返回0,失败返回错误码
 * */
int product_clt(int fd, int *cltpool, int maxindex)
{
    int ret = 0;
    //接收客户端信息
    Packet pack;
    memset(&pack, 0, sizeof(pack));
    int buflen = 0;
    ret = recv_packet(fd, &pack, &buflen, 0);
    if (ret != 0)
        return ret;
    //转发给其他客户端
    int i = 0;
    for (i = 0; i <= maxindex; i++)
    {
        if (cltpool[i] != -1 && cltpool[i] != fd)
        {
            //发送信息
            ret = send_packet(cltpool[i], &pack, buflen+4, 0);
            if (ret != 0)
                return ret;
        }
    }
    return ret;
}

/**
 * handler - 信号捕捉函数
 * @sign:信号值
 * */
void handler(int sign)
{
    if (sign == SIGPIPE)
    {
        printf("accept SIGPIPE!\n");
    }
}

/**
 * select_socket - select机制管理客户端连接
 * @fd:文件描述符
 * 失败返回错误码
 * */
int select_socket(int fd)
{
    int ret = 0;
    //安装信号
    if (signal(SIGPIPE, handler) == SIG_ERR)
    {
        ret = SckBaseErr;
        printf("signal() failed !\n");
        return ret;
    }
    //定义客户端套接字临时变量
    int conn = 0;
    struct sockaddr_in peeraddr;
    socklen_t peerlen = 0;
    //已经处理的select事件
    int nread = 0;
    //创建客户端连接池
    int cltpool[FD_SETSIZE] = { 0 };
    //初始化连接池
    int i = 0;
    for (i = 0; i < FD_SETSIZE; i++)
    {
        cltpool[i] = -1;
    }
    //定义数组尾部元素下标
    int maxindex = 0;
    //定义最大的套接字(初始值是监听套接字)
    int maxfd = fd;
    //定义最新的套接字集合
    fd_set allsets;
    FD_ZERO(&allsets);
    //定义需要监听的套接字集合
    fd_set readfds;
    FD_ZERO(&readfds);
    //将监听套接字加入最新的套接字集合
    FD_SET(fd, &allsets);
    while (1)
    {
        //将最新的套接字集合赋值给需要监听的套接字集合
        readfds = allsets;
        do
        {
            nread = select(maxfd + 1, &readfds, NULL, NULL, NULL);
        } while (nread == -1 && errno == EINTR); //屏蔽信号
        if (nread == -1)
        {
            ret = SckBaseErr;
            perror("select() err");
            return ret;
        }
        //1.服务器监听套接字处理
        if (FD_ISSET(fd, &readfds))
        {
            //接收到客户端的连接
            memset(&peeraddr, 0, sizeof(peeraddr));
            peerlen = sizeof(peeraddr);
            conn = accept(fd, (struct sockaddr *) &peeraddr, &peerlen);
            if (conn == -1)
            {
                ret = SckBaseErr;
                perror("accept() err");
                return ret;
            }
            //将客户端连接添加到连接池
            for (i = 0; i < FD_SETSIZE; i++)
            {
                if (cltpool[i] == -1)
                {
                    if (i > maxindex)
                    {
                        maxindex = i;
                    }
                    cltpool[i] = conn;
                    break;
                }
            }
            if (i == FD_SETSIZE)
            {
                ret = SckBaseErr;
                close(conn);
                printf("客户端连接池已满!\n");
                return ret;
            }
            if (conn > maxfd)
                maxfd = conn;
            //将该客户端套接字加入到最新套接字集合
            FD_SET(conn, &allsets);
            printf("server accept from :%s\n", inet_ntoa(peeraddr.sin_addr));
            if (--nread <= 0)
                continue;
        }
        //处理客户端请求
        if (nread <= 0)
            continue;
        for (i = 0; i <= maxindex; i++)
        {
            if (cltpool[i] == -1)
                continue;
            if (FD_ISSET(cltpool[i], &readfds))
            {
                //处理客户端请求
                ret = product_clt(cltpool[i], cltpool, maxindex);
                if (ret != 0)
                {
                    //从最新的套接字集合中删除
                    FD_CLR(cltpool[i], &allsets);
                    //处理请求失败,关闭客户端连接
                    close(cltpool[i]);
                    //从客户端连接池中清除
                    cltpool[i] = -1;
                    break;
                }
                if (--nread <= 0)
                    break;
            }
        }
    }
    return ret;
}
时间: 2024-09-28 08:20:30

Linux Linux程序练习十九的相关文章

Linux学习笔记(十九)文件压缩

一.常见的压缩文件 Windows .rar .zip .7z Linux .zip,.gz,.bz2,.xz,.tar.gz,.tar.bz2,.tar.xz文件压缩可以节省内存,也可以节省传输速度 二.gzip首先创建了一个文件夹 /tmp/d6z/找了些比较大的文件写入1.txt例如find /etc/ -type f -name "*conf" -exec cat {} >> 1.txt \ :多执行几次 gzip 1.txt 就可以将文件1.txt压缩并且删除源文

鸟哥的Linux私房菜——第十九章:例行命令的建立

视频链接: 1. 什么是例行性命令 (分为两种,一种是周期性的,一种是突发性的)1.1 Linux 工作排程的种类: at, cron   ( at是突发的,cron是周期的)1.2 系统上常见的例行性命令有哪些? (log rotate,数据库rpm,locate数据库)2. 仅执行一次的工作排程: at, atq, atrm3. 循环执行的例行性命令: cron3.1 使用者的设定: crontab3.2 系统的设定: /etc/crontab at  仅进行一次的工程安排 这个现在好多都是

linux学习笔记-第十九课-LAMP之php 与 mysql 配置(三)

一.php 编译完的php,配置文件为空,我们需要将php的配置文件(php.ini)从解压的源码包中的php.ini-development(开发调试模板)和php.ini-production(生产运行模板)中复制一份到php的配置目录中,且名字改为php.ini 1 )disable_functions 配置 默认为空,修改为 disable_functions = eval,assert,popen,passthru,escapeshellarg,escapeshellcmd,passt

Linux常用命令(二十九) - date

在linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便. 1.命令格式: date [参数]... [+格式] 2.命令功能: date 可以用来显示或设定系统的日期与时间. 3.命令参数: 必要参数: %H 小时(以00-23来表示). %I 小时(以01-12来表示). %K 小时(以0-23来表示). %l 小时(以0-12来表示). %M 分钟(以00-59来表示). %P AM

linux学习笔记-第十九课-LAMP之网站搭建(二)

一.网站搭建前提 搭建好LAMP运行环境 下载网站程序,这里以Discuz X 3.2 作为示例 Discuz 程序下载地址:    简体中文GBK http://download.comsenz.com/DiscuzX/3.2/Discuz_X3.2_SC_GBK.zip    繁体中文BIG5 http://download.comsenz.com/DiscuzX/3.2/Discuz_X3.2_TC_BIG5.zip    简体UTF-8 http://download.comsenz.c

Linux学习笔记&lt;二十九&gt;——http服务

基础概念: HTTP:Hyper Text Transfer Protocol 超文本传输协议 versions: HTTP/0.9:只接收GET一种请求方法,只支持纯文本 HTTP/1.0:支持PUT.POST.DELETE和HEAD,支持MINE HTTP/1.1:在HTTP/1.0的基础上,增加了缓存功能,支持长连接,支持管道方式同时                  发送多个请求 HTTP请求方法:获取资源的方法 HTTP/0.9:GET HTTP/1.0:PUT(修改服务器上的内容),

Linux C编程之十九(1) libevent基本概念

一.libevent是干什么的 1. 开源的库, 提高开发效率 封装了socket通信 封装了IO多路转接 2. 精简, 专注于网络, 性能高 3. 事件驱动 二.libevent库的安装 1. 官方网站: http://libevent.org 2. 源码包下载: 1.4.x -- 适合源码学习 2.x 3. 源码包的安装 (1)./configure --prefix == /usr/xxxxx 检测安装环境 生成makefile (2)make 编译源代码 生成一些库 (a. 动态, 静态

linux学习笔记-第十九课-LAMP之 mysql (四)

mysql日常操作指令 1 )mysql管理员密码的更改,mysql安装完毕后,管理员root的密码默认为空,需要进行修改 格式 :mysqladmin -u root password '新密码' 示例 : [[email protected] ~]# mysqladmin -u root password '123456' [[email protected] ~]# mysql -u root -p # 这时候就需要使用密码登陆mysql Enter password:          

Linux学习(二十九)iptables(三)nat表的应用

需求 A机器可以访问外网,B机器和A机器处于同一个内网,现在要让B机器通过A机器访问外网. 步骤 1.为虚拟机添加一块网卡. 如果没有区段名称的话,点击'LAN区段(S)...'按钮,新建一个. 2.ifconfig -a命令可以看到刚添加进来的尚未启用的网卡: [[email protected] ~]# ifconfig -a eth1 Link encap:Ethernet HWaddr 00:0C:29:AC:CC:56 inet addr:192.168.182.130 Bcast:1