zoukankan      html  css  js  c++  java
  • Socket编程实践(7) --Socket-Class封装(改进版v2)

        本篇博客定义一套用于TCP通信比较实用/好用Socket类库(运用C++封装的思想,将socket API尽量封装的好用与实用), 从开发出Socket库的第一个版本以来, 作者不知道做了多少改进, 每次有新的/好的想法尽量实现到该库当中来; 而且我还使用该库开发出作者第一个真正意义上的基于Linux的Server程序[MyHttpd, 在后续的博客当中, 我一定会将MyHttpd的实现原理与实现代码更新到这篇博客所属的专栏中, 希望读者朋友不吝赐教];

        可能在以后的学习和工作中, 作者还可能会加入新的功能和修复发现的BUG, 因此, 如果有读者喜欢这个Socket库, 请持续关注这篇博客, 我会把最新的更新信息都发布到这篇博客当中, 当然, 如果有读者朋友发现了这个Socket库的BUG, 还希望读者朋友不吝赐教, 谢谢您的关注;

     

    实现中的几个注意点:

        1)TCPSocket类几个成员函数的访问权限为protected, 使Socket类可以进行继承,但不允许私自使用;

        2)TCPClient类的send/receive方法使用了著名的writen/readn(来源UNP)实现, 解决了TCP的粘包问题.

        3)TCPServer端添加了地址复用, 可以方便TCP服务器重启;

        4)添加了异常类,让我们在编写易出错的代码时,可以解放思想,不用一直考虑该函数调用出错会发生什么情况!

        5)TCPSocket类中添加了getfd接口, 如果有这三个类完成不了的功能, 则可以将socket获取出来, 使用Linux的系统调用完成相应的功能;

        6)TCPClient中有好几个发送/接受的接口, 其中, 使用send发送的数据一定要使用receive来接收, 因为作者使用的是自定义应用层协议来解决的TCP粘包问题, 请读者朋友注意;


    由于实现思想较简单, 因此在代码中并未添加大量的注释, 请读者耐心读下去, 在博文结尾处, 会有该库的测试使用示例与Makefile文件, 并将整个文件夹(完整的项目实现源代码)放到了CSDN的下载资源(不需要下载分的O(∩_∩)O~)中, 下载链接见下:

    http://download.csdn.net/detail/hanqing280441589/8486489

    Socket类

    TCPSocket/TCPClient/TCPServer类设计

    class TCPSocket
    {
    protected:
        TCPSocket();
        virtual ~TCPSocket();
    
        bool create();
        bool bind(unsigned short int port, const char *ip = NULL) const;
        bool listen(int backlog = SOMAXCONN) const;
        bool accept(TCPSocket &clientSocket) const;
        bool connect(unsigned short int port, const char *ip) const;
    
        /**注意: TCPSocket基类并没有send/receive方法**/
    
        bool reuseaddr() const;
        bool isValid() const
        {
            return (m_sockfd != -1);
        }
    public:
        bool close();
        int getfd() const
        {
            return m_sockfd;
        }
        //flag: true=SetNonBlock, false=SetBlock
        bool setNonBlocking(bool flag) const;
    
    protected:
        int m_sockfd;
    };
    /** TCP Client **/
    class TCPClient : public TCPSocket
    {
    private:
        struct Packet
        {
            unsigned int    msgLen;     //数据部分的长度(网络字节序)
            char            text[1024]; //报文的数据部分
        };
    public:
        TCPClient(unsigned short int port, const char *ip) throw(SocketException);
        TCPClient();
        TCPClient(int clientfd);
        ~TCPClient();
    
        size_t send(const std::string& message) const throw(SocketException);
        size_t receive(std::string& message) const throw(SocketException);
        size_t read(void *buf, size_t count) throw(SocketException);
        void   write(const void *buf, size_t count) throw(SocketException);
        size_t write(const char *msg) throw(SocketException);
    };
    /** TCP Server **/
    class TCPServer : public TCPSocket
    {
    public:
        TCPServer(unsigned short int port, const char *ip = NULL, int backlog = SOMAXCONN) throw(SocketException);
        ~TCPServer();
        void accept(TCPClient &client) const throw(SocketException);
        TCPClient accept() const throw(SocketException);
    };

    TCPSocket/TCPClient/TCPServer类实现

    TCPSocket::TCPSocket(): m_sockfd(-1) {}
    TCPSocket::~TCPSocket()
    {
        if (isValid())
            ::close(m_sockfd);
    }
    
    bool TCPSocket::create()
    {
        if (isValid())
            return false;
    
        if ((m_sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1)
            return false;
        return true;
    }
    
    bool TCPSocket::bind(unsigned short int port, const char *ip) const
    {
        if (!isValid())
            return false;
    
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        if (ip == NULL)
            addr.sin_addr.s_addr = htonl(INADDR_ANY);
        else
            addr.sin_addr.s_addr = inet_addr(ip);
        if ( ::bind(m_sockfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1 )
            return false;
        return true;
    }
    bool TCPSocket::listen(int backlog) const
    {
        if (!isValid())
            return false;
    
        if ( ::listen(m_sockfd, backlog) == -1)
            return false;
        return true;
    }
    bool TCPSocket::accept(TCPSocket &clientSocket) const
    {
        if (!isValid())
            return false;
    
        clientSocket.m_sockfd =
            ::accept(this->m_sockfd, NULL, NULL);
        if (clientSocket.m_sockfd == -1)
            return false;
        return true;
    }
    
    bool TCPSocket::connect(unsigned short int port, const char *ip) const
    {
        if (!isValid())
            return false;
    
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip);
        if ( ::connect(m_sockfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)
            return false;
        return true;
    }
    
    bool TCPSocket::setNonBlocking(bool flag) const
    {
        if (!isValid())
            return false;
        int opt = fcntl(m_sockfd, F_GETFL, 0);
        if (opt == -1)
            return false;
        if (flag)
            opt |= O_NONBLOCK;
        else
            opt &= ~O_NONBLOCK;
        if (fcntl(m_sockfd, F_SETFL, opt) == -1)
            return false;
        return true;
    }
    bool TCPSocket::reuseaddr() const
    {
        if (!isValid())
            return false;
    
        int on = 1;
        if (setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
            return false;
        return true;
    }
    bool TCPSocket::close()
    {
        if (!isValid())
            return false;
        ::close(m_sockfd);
        m_sockfd = -1;
        return true;
    }
    /** client TCP Socket **/
    TCPClient::TCPClient(unsigned short int port, const char *ip)
    throw(SocketException)
    {
        if (create() == false)
            throw SocketException("tcp client create error");
        if (connect(port, ip) == false)
            throw SocketException("tcp client connect error");
    }
    TCPClient::TCPClient() {}
    TCPClient::TCPClient(int clientfd)
    {
        m_sockfd = clientfd;
    }
    TCPClient::~TCPClient() {}
    /** client端特有的send/receive **/
    static ssize_t readn(int fd, void *buf, size_t count);
    static ssize_t writen(int fd, const void *buf, size_t count);
    
    //send
    size_t TCPClient::send(const std::string& message)
    const throw(SocketException)
    {
        Packet buf;
        buf.msgLen = htonl(message.length());
        strcpy(buf.text, message.c_str());
        if (writen(m_sockfd, &buf, sizeof(buf.msgLen)+message.length()) == -1)
            throw SocketException("tcp client writen error");
        return message.length();
    }
    //receive
    size_t TCPClient::receive(std::string& message)
    const throw(SocketException)
    {
        //首先读取头部
        Packet buf = {0, 0};
        size_t readBytes = readn(m_sockfd, &buf.msgLen, sizeof(buf.msgLen));
        if (readBytes == (size_t)-1)
            throw SocketException("tcp client readn error");
        else if (readBytes != sizeof(buf.msgLen))
            throw SocketException("peer connect closed");
    
        //然后读取数据部分
        unsigned int lenHost = ntohl(buf.msgLen);
        readBytes = readn(m_sockfd, buf.text, lenHost);
        if (readBytes == (size_t)-1)
            throw SocketException("tcp client readn error");
        else if (readBytes != lenHost)
            throw SocketException("peer connect closed");
        message = buf.text;
        return message.length();
    }
    size_t TCPClient::read(void *buf, size_t count) throw(SocketException)
    {
        ssize_t readBytes = ::read(m_sockfd, buf, count);
        if (readBytes == -1)
            throw SocketException("tcp client read error");
        return (size_t)readBytes;
    }
    void TCPClient::write(const void *buf, size_t count) throw(SocketException)
    {
        if ( ::write(m_sockfd, buf, count) == -1 )
            throw SocketException("tcp client write error");
    }
    size_t TCPClient::write(const char *msg) throw(SocketException)
    {
        if ( ::write(m_sockfd, msg, strlen(msg)) == -1 )
            throw SocketException("tcp client write error");
        return strlen(msg);
    }
    /** Server TCP Socket**/
    TCPServer::TCPServer(unsigned short int port, const char *ip, int backlog)
    throw(SocketException)
    {
        if (create() == false)
            throw SocketException("tcp server create error");
        if (reuseaddr() == false)
            throw SocketException("tcp server reuseaddr error");
        if (bind(port, ip) == false)
            throw SocketException("tcp server bind error");
        if (listen(backlog) == false)
            throw SocketException("tcp server listen error");
    }
    TCPServer::~TCPServer() {}
    void TCPServer::accept(TCPClient &client) const
    throw(SocketException)
    {
        //显式调用基类TCPSocket的accept
        if (TCPSocket::accept(client) == -1)
            throw SocketException("tcp server accept error");
    }
    TCPClient TCPServer::accept() const
    throw(SocketException)
    {
        TCPClient client;
        if (TCPSocket::accept(client) == -1)
            throw SocketException("tcp server accept error");
        return client;
    }
    /** readn/writen实现部分 **/
    static ssize_t readn(int fd, void *buf, size_t count)
    {
        size_t nLeft = count;
        ssize_t nRead = 0;
        char *pBuf = (char *)buf;
        while (nLeft > 0)
        {
            if ((nRead = read(fd, pBuf, nLeft)) < 0)
            {
                //如果读取操作是被信号打断了, 则说明还可以继续读
                if (errno == EINTR)
                    continue;
                //否则就是其他错误
                else
                    return -1;
            }
            //读取到末尾
            else if (nRead == 0)
                return count-nLeft;
    
            //正常读取
            nLeft -= nRead;
            pBuf += nRead;
        }
        return count;
    }
    static ssize_t writen(int fd, const void *buf, size_t count)
    {
        size_t nLeft = count;
        ssize_t nWritten = 0;
        char *pBuf = (char *)buf;
        while (nLeft > 0)
        {
            if ((nWritten = write(fd, pBuf, nLeft)) < 0)
            {
                //如果写入操作是被信号打断了, 则说明还可以继续写入
                if (errno == EINTR)
                    continue;
                //否则就是其他错误
                else
                    return -1;
            }
            //如果 ==0则说明是什么也没写入, 可以继续写
            else if (nWritten == 0)
                continue;
    
            //正常写入
            nLeft -= nWritten;
            pBuf += nWritten;
        }
        return count;
    }

    SocketException类

    //SocketException类的设计与实现
    class SocketException
    {
    public:
        typedef std::string string;
        SocketException(const string &_msg = string())
            : msg(_msg) {}
        string what() const
        {
            if (errno == 0)
                return msg;
            //如果errno!=0, 则会加上错误描述
            return msg + ": " + strerror(errno);
        }
    
    private:
        string msg;
    };

    Socket类测试(echo)

    Server端测试代码

    void sigHandler(int signo)
    {
        while (waitpid(-1, NULL, WNOHANG) > 0)
            ;
    }
    
    int main()
    {
        signal(SIGCHLD, sigHandler);
        signal(SIGPIPE, SIG_IGN);
        try
        {
            TCPServer server(8001);
            std::string msg;
            while (true)
            {
                TCPClient client = server.accept();
                pid_t pid = fork();
                if (pid == -1)
                    err_exit("fork error");
                else if (pid > 0)
                    client.close();
                else if (pid == 0)
                {
                    try
                    {
                        while (true)
                        {
                            client.receive(msg);
                            cout << msg << endl;
                            client.send(msg);
                        }
                    }
                    catch (const SocketException &e)
                    {
                        cerr << e.what() << endl;
                        exit(EXIT_FAILURE);
                    }
                    exit(EXIT_SUCCESS);
                }
            }
        }
        catch (const SocketException &e)
        {
            cerr << e.what() << endl;
            exit(EXIT_FAILURE);
        }
    }

    Client端测试代码

    int main()
    {
        signal(SIGPIPE, SIG_IGN);
        try
        {
            TCPClient client(8001, "127.0.0.1");
            std::string msg;
            while (getline(cin, msg))
            {
                client.send(msg);
                msg.clear();
                client.receive(msg);
                cout << msg << endl;
                msg.clear();
            }
        }
        catch (const SocketException &e)
        {
            cerr << e.what() << endl;
        }
    }

    Makefile

    .PHONY: clean all 
    CC = g++ 
    CPPFLAGS = -Wall -g -pthread -std=c++11
    BIN = serverclient
    SOURCES = $(BIN.=.cpp)
    
    all: $(BIN)
    $(BIN): $(SOURCES) Socket.cpp
    
    clean:
        -rm -rf $(BIN) bin/ obj/ core


  • 相关阅读:
    (二)php的常量和变量
    关于标签系统的一点想法。
    Linux运维工程师中级面试题
    Linux C 编程内存泄露检测工具(一):mtrace
    掌握sudo的使用
    Scala极速入门
    处理千万级以上的数据提高查询速度的方法
    linux svn服务器搭建、客户端操作、备份与恢复
    select/poll/epoll 对比
    汇编指令和标志寄存器
  • 原文地址:https://www.cnblogs.com/itrena/p/5926943.html
Copyright © 2011-2022 走看看