zoukankan      html  css  js  c++  java
  • Linux Linux程序练习十八

    题目:编写一个TCP服务器和客户端,基于TCP的并发远程shell 
    要求实现:
    1)对于所有收到的客户端消息,作为命令行进行执行,
    并且将命令行的输出结果返回给客户端
    2)要求使用并发结构
    3)实现关键代码
        子进程执行命令
        numbytes = read(connfd, buf, 128); 
        buf[numbytes] = ''; 
        sprintf(cmd, "%s > /tmp/cmd.txt", buf); 
        system(cmd);
        fp = fopen("/tmp/cmd.txt", "r");
        numbytes = fread(cmd, 1, 1024*2, fp);
        cmd[numbytes] = '';
        fclose(fp);
    核心代码展示
    #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 "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 !
    ");
            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 !
    ");
            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 !
    ");
                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 !
    ");
                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 !
    ");
                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 !
    ");
            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 !
    ");
            return ret;
        } else if (ret == len)
            ret = 0;
        *buflen = len;
        return ret;
    }
    
    /**
     * product_ser - 处理服务器消息
     * @fd:文件描述符
     * @wait_seconds:等待超时秒数,如果为0表示不检测超时
     * 成功返回0,失败返回错误码
     * */
    int product_ser(int fd, unsigned int wait_seconds)
    {
        int ret = 0;
        Packet pack;
        int buflen = 0;
        while (1)
        {
            memset(&pack, 0, sizeof(pack));
            ret = recv_packet(fd, &pack, &buflen, wait_seconds);
            if (ret != 0)
            {
                return ret;
            }
            //已经完全接收服务器所有数据
            if (buflen == 3 && strncmp(pack.buf, "end", 3) == 0)
            {
                break;
            }
            //printf("数据包长度是%d;%s
    ",buflen,pack.buf);
            fputs(pack.buf, stdout);
        }
        return ret;
    }
    
    /**
     * 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 !
    ");
            return ret;
        }
        Packet pack;
        memset(&pack, 0, sizeof(pack));
        int buflen = 0;
        write(STDIN_FILENO,"请输入shell命令:",sizeof("请输入shell命令:"));
        while (fgets(pack.buf, MAXBUFSIZE, stdin) != NULL)
        {
            //去除
    
            buflen = strlen(pack.buf) - 1;
            pack.len = htonl(buflen);
            //发送数据
            ret = send_packet(connid, &pack, buflen + 4, wait_seconds);
            if (ret != 0)
            {
                return ret;
            }
            memset(&pack, 0, sizeof(pack));
            //接收服务器数据
            ret = product_ser(connid, wait_seconds);
            if (ret != 0)
                return ret;
            write(STDIN_FILENO,"请输入shell命令:",sizeof("请输入shell命令:"));
        }
        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] = '';
        //立即生效
        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 !
    ");
            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:文件描述符
     * 成功返回0,失败返回错误码
     * */
    int product_clt(int fd)
    {
        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;
        //重新拼接shell脚本
        char *path = "/home/test/1/cmdres.txt";
        char tempbuf[1024] = { 0 };
        if (buflen > 1000)
        {
            ret = SckBaseErr;
            printf("用户输入数据过长,服务器无法接收!
    ");
            return ret;
        }
        sprintf(tempbuf, "%s > %s", pack.buf, path);
        //执行shell脚本
        system(tempbuf);
        //打开文件
        FILE *pfr = NULL;
        pfr = fopen(path, "r");
        if (pfr == NULL)
        {
            ret = SckBaseErr;
            perror("fopen() err");
            return ret;
        }
        memset(&pack, 0, sizeof(pack));
        //读文件
        while (fgets(pack.buf, MAXBUFSIZE, pfr) != NULL)
        {
            //每读取一次,发送一个数据包
            buflen = strlen(pack.buf);
            pack.len = htonl(buflen);
            ret = send_packet(fd, &pack, buflen + 4, 100);
            if (ret != 0)
            {
                printf("发送数据失败!");
                break;
            }
            memset(&pack, 0, sizeof(pack));
        }
        //关闭文件流
        if (pfr != NULL)
        {
            fclose(pfr);
            pfr = NULL;
        }
        //文件读取完毕之后,发送一个0数据包,告诉客户端数据已经发送完毕
        memset(&pack, 0, sizeof(pack));
        strcpy(pack.buf, "end");
        buflen = strlen(pack.buf);
        pack.len = htonl(buflen);
        ret = send_packet(fd, &pack, buflen + 4, 100);
        if(ret!=0)
        {
            return ret;
        }
        //删除临时文件
        system("rm ../1/*");
        return ret;
    }
    
    /**
     * handler - 信号捕捉函数
     * @sign:信号值
     * */
    void handler(int sign)
    {
        if (sign == SIGPIPE)
        {
            printf("accept SIGPIPE!
    ");
        }
    }
    
    /**
     * select_socket - select机制管理客户端连接
     * @fd:文件描述符
     * 失败返回错误码
     * */
    int select_socket(int fd)
    {
        int ret = 0;
        //安装信号
        if (signal(SIGPIPE, handler) == SIG_ERR)
        {
            ret = SckBaseErr;
            printf("signal() failed !
    ");
            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("客户端连接池已满!
    ");
                    return ret;
                }
                if (conn > maxfd)
                    maxfd = conn;
                //将该客户端套接字加入到最新套接字集合
                FD_SET(conn, &allsets);
                printf("server accept from :%s
    ",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]);
                    if (ret != 0)
                    {
                        //从最新的套接字集合中删除
                        FD_CLR(cltpool[i], &allsets);
                        //处理请求失败,关闭客户端连接
                        close(cltpool[i]);
                        //从客户端连接池中清除
                        cltpool[i] = -1;
                        break;
                    }
                    if (--nread <= 0)
                        break;
                }
            }
        }
        return ret;
    }
    View Code
  • 相关阅读:
    Graph neural networks: A review of methods and applications文献阅读
    IMBD数据集处理
    GNN知识整理(二)
    GNN认识整理(一)
    Linux中python中的#!/usr/bin/python
    Linux下运行g++
    itextpdf7自写算法的表格展示 制表符
    itext7 List序号 有序列表 解决中文不显示
    java使用itextpdf7实现导出pdf表格;java使用itextpdf7实现pdf加水印
    csv导出导入工具类 commons-csv导出
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/6187032.html
Copyright © 2011-2022 走看看