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类设计

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class TCPSocket  
  2. {  
  3. protected:  
  4.     TCPSocket();  
  5.     virtual ~TCPSocket();  
  6.   
  7.     bool create();  
  8.     bool bind(unsigned short int port, const char *ip = NULL) const;  
  9.     bool listen(int backlog = SOMAXCONN) const;  
  10.     bool accept(TCPSocket &clientSocket) const;  
  11.     bool connect(unsigned short int port, const char *ip) const;  
  12.   
  13.     /**注意: TCPSocket基类并没有send/receive方法**/  
  14.   
  15.     bool reuseaddr() const;  
  16.     bool isValid() const  
  17.     {  
  18.         return (m_sockfd != -1);  
  19.     }  
  20. public:  
  21.     bool close();  
  22.     int getfd() const  
  23.     {  
  24.         return m_sockfd;  
  25.     }  
  26.     //flag: true=SetNonBlock, false=SetBlock  
  27.     bool setNonBlocking(bool flag) const;  
  28.   
  29. protected:  
  30.     int m_sockfd;  
  31. };  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** TCP Client **/  
  2. class TCPClient : public TCPSocket  
  3. {  
  4. private:  
  5.     struct Packet  
  6.     {  
  7.         unsigned int    msgLen;     //数据部分的长度(网络字节序)  
  8.         char            text[1024]; //报文的数据部分  
  9.     };  
  10. public:  
  11.     TCPClient(unsigned short int port, const char *ip) throw(SocketException);  
  12.     TCPClient();  
  13.     TCPClient(int clientfd);  
  14.     ~TCPClient();  
  15.   
  16.     size_t send(const std::string& message) const throw(SocketException);  
  17.     size_t receive(std::string& message) const throw(SocketException);  
  18.     size_t read(void *buf, size_t count) throw(SocketException);  
  19.     void   write(const void *buf, size_t count) throw(SocketException);  
  20.     size_t write(const char *msg) throw(SocketException);  
  21. };  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** TCP Server **/  
  2. class TCPServer : public TCPSocket  
  3. {  
  4. public:  
  5.     TCPServer(unsigned short int port, const char *ip = NULL, int backlog = SOMAXCONN) throw(SocketException);  
  6.     ~TCPServer();  
  7.     void accept(TCPClient &client) const throw(SocketException);  
  8.     TCPClient accept() const throw(SocketException);  
  9. };  

TCPSocket/TCPClient/TCPServer类实现

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. TCPSocket::TCPSocket(): m_sockfd(-1) {}  
  2. TCPSocket::~TCPSocket()  
  3. {  
  4.     if (isValid())  
  5.         ::close(m_sockfd);  
  6. }  
  7.   
  8. bool TCPSocket::create()  
  9. {  
  10.     if (isValid())  
  11.         return false;  
  12.   
  13.     if ((m_sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1)  
  14.         return false;  
  15.     return true;  
  16. }  
  17.   
  18. bool TCPSocket::bind(unsigned short int port, const char *ip) const  
  19. {  
  20.     if (!isValid())  
  21.         return false;  
  22.   
  23.     struct sockaddr_in addr;  
  24.     addr.sin_family = AF_INET;  
  25.     addr.sin_port = htons(port);  
  26.     if (ip == NULL)  
  27.         addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  28.     else  
  29.         addr.sin_addr.s_addr = inet_addr(ip);  
  30.     if ( ::bind(m_sockfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1 )  
  31.         return false;  
  32.     return true;  
  33. }  
  34. bool TCPSocket::listen(int backlog) const  
  35. {  
  36.     if (!isValid())  
  37.         return false;  
  38.   
  39.     if ( ::listen(m_sockfd, backlog) == -1)  
  40.         return false;  
  41.     return true;  
  42. }  
  43. bool TCPSocket::accept(TCPSocket &clientSocket) const  
  44. {  
  45.     if (!isValid())  
  46.         return false;  
  47.   
  48.     clientSocket.m_sockfd =  
  49.         ::accept(this->m_sockfd, NULL, NULL);  
  50.     if (clientSocket.m_sockfd == -1)  
  51.         return false;  
  52.     return true;  
  53. }  
  54.   
  55. bool TCPSocket::connect(unsigned short int port, const char *ip) const  
  56. {  
  57.     if (!isValid())  
  58.         return false;  
  59.   
  60.     struct sockaddr_in addr;  
  61.     addr.sin_family = AF_INET;  
  62.     addr.sin_port = htons(port);  
  63.     addr.sin_addr.s_addr = inet_addr(ip);  
  64.     if ( ::connect(m_sockfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)  
  65.         return false;  
  66.     return true;  
  67. }  
  68.   
  69. bool TCPSocket::setNonBlocking(bool flag) const  
  70. {  
  71.     if (!isValid())  
  72.         return false;  
  73.     int opt = fcntl(m_sockfd, F_GETFL, 0);  
  74.     if (opt == -1)  
  75.         return false;  
  76.     if (flag)  
  77.         opt |= O_NONBLOCK;  
  78.     else  
  79.         opt &= ~O_NONBLOCK;  
  80.     if (fcntl(m_sockfd, F_SETFL, opt) == -1)  
  81.         return false;  
  82.     return true;  
  83. }  
  84. bool TCPSocket::reuseaddr() const  
  85. {  
  86.     if (!isValid())  
  87.         return false;  
  88.   
  89.     int on = 1;  
  90.     if (setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)  
  91.         return false;  
  92.     return true;  
  93. }  
  94. bool TCPSocket::close()  
  95. {  
  96.     if (!isValid())  
  97.         return false;  
  98.     ::close(m_sockfd);  
  99.     m_sockfd = -1;  
  100.     return true;  
  101. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** client TCP Socket **/  
  2. TCPClient::TCPClient(unsigned short int port, const char *ip)  
  3. throw(SocketException)  
  4. {  
  5.     if (create() == false)  
  6.         throw SocketException("tcp client create error");  
  7.     if (connect(port, ip) == false)  
  8.         throw SocketException("tcp client connect error");  
  9. }  
  10. TCPClient::TCPClient() {}  
  11. TCPClient::TCPClient(int clientfd)  
  12. {  
  13.     m_sockfd = clientfd;  
  14. }  
  15. TCPClient::~TCPClient() {}  
  16. /** client端特有的send/receive **/  
  17. static ssize_t readn(int fd, void *buf, size_t count);  
  18. static ssize_t writen(int fd, const void *buf, size_t count);  
  19.   
  20. //send  
  21. size_t TCPClient::send(const std::string& message)  
  22. const throw(SocketException)  
  23. {  
  24.     Packet buf;  
  25.     buf.msgLen = htonl(message.length());  
  26.     strcpy(buf.text, message.c_str());  
  27.     if (writen(m_sockfd, &buf, sizeof(buf.msgLen)+message.length()) == -1)  
  28.         throw SocketException("tcp client writen error");  
  29.     return message.length();  
  30. }  
  31. //receive  
  32. size_t TCPClient::receive(std::string& message)  
  33. const throw(SocketException)  
  34. {  
  35.     //首先读取头部  
  36.     Packet buf = {0, 0};  
  37.     size_t readBytes = readn(m_sockfd, &buf.msgLen, sizeof(buf.msgLen));  
  38.     if (readBytes == (size_t)-1)  
  39.         throw SocketException("tcp client readn error");  
  40.     else if (readBytes != sizeof(buf.msgLen))  
  41.         throw SocketException("peer connect closed");  
  42.   
  43.     //然后读取数据部分  
  44.     unsigned int lenHost = ntohl(buf.msgLen);  
  45.     readBytes = readn(m_sockfd, buf.text, lenHost);  
  46.     if (readBytes == (size_t)-1)  
  47.         throw SocketException("tcp client readn error");  
  48.     else if (readBytes != lenHost)  
  49.         throw SocketException("peer connect closed");  
  50.     message = buf.text;  
  51.     return message.length();  
  52. }  
  53. size_t TCPClient::read(void *buf, size_t count) throw(SocketException)  
  54. {  
  55.     ssize_t readBytes = ::read(m_sockfd, buf, count);  
  56.     if (readBytes == -1)  
  57.         throw SocketException("tcp client read error");  
  58.     return (size_t)readBytes;  
  59. }  
  60. void TCPClient::write(const void *buf, size_t count) throw(SocketException)  
  61. {  
  62.     if ( ::write(m_sockfd, buf, count) == -1 )  
  63.         throw SocketException("tcp client write error");  
  64. }  
  65. size_t TCPClient::write(const char *msg) throw(SocketException)  
  66. {  
  67.     if ( ::write(m_sockfd, msg, strlen(msg)) == -1 )  
  68.         throw SocketException("tcp client write error");  
  69.     return strlen(msg);  
  70. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** Server TCP Socket**/  
  2. TCPServer::TCPServer(unsigned short int port, const char *ip, int backlog)  
  3. throw(SocketException)  
  4. {  
  5.     if (create() == false)  
  6.         throw SocketException("tcp server create error");  
  7.     if (reuseaddr() == false)  
  8.         throw SocketException("tcp server reuseaddr error");  
  9.     if (bind(port, ip) == false)  
  10.         throw SocketException("tcp server bind error");  
  11.     if (listen(backlog) == false)  
  12.         throw SocketException("tcp server listen error");  
  13. }  
  14. TCPServer::~TCPServer() {}  
  15. void TCPServer::accept(TCPClient &client) const  
  16. throw(SocketException)  
  17. {  
  18.     //显式调用基类TCPSocket的accept  
  19.     if (TCPSocket::accept(client) == -1)  
  20.         throw SocketException("tcp server accept error");  
  21. }  
  22. TCPClient TCPServer::accept() const  
  23. throw(SocketException)  
  24. {  
  25.     TCPClient client;  
  26.     if (TCPSocket::accept(client) == -1)  
  27.         throw SocketException("tcp server accept error");  
  28.     return client;  
  29. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** readn/writen实现部分 **/  
  2. static ssize_t readn(int fd, void *buf, size_t count)  
  3. {  
  4.     size_t nLeft = count;  
  5.     ssize_t nRead = 0;  
  6.     char *pBuf = (char *)buf;  
  7.     while (nLeft > 0)  
  8.     {  
  9.         if ((nRead = read(fd, pBuf, nLeft)) < 0)  
  10.         {  
  11.             //如果读取操作是被信号打断了, 则说明还可以继续读  
  12.             if (errno == EINTR)  
  13.                 continue;  
  14.             //否则就是其他错误  
  15.             else  
  16.                 return -1;  
  17.         }  
  18.         //读取到末尾  
  19.         else if (nRead == 0)  
  20.             return count-nLeft;  
  21.   
  22.         //正常读取  
  23.         nLeft -= nRead;  
  24.         pBuf += nRead;  
  25.     }  
  26.     return count;  
  27. }  
  28. static ssize_t writen(int fd, const void *buf, size_t count)  
  29. {  
  30.     size_t nLeft = count;  
  31.     ssize_t nWritten = 0;  
  32.     char *pBuf = (char *)buf;  
  33.     while (nLeft > 0)  
  34.     {  
  35.         if ((nWritten = write(fd, pBuf, nLeft)) < 0)  
  36.         {  
  37.             //如果写入操作是被信号打断了, 则说明还可以继续写入  
  38.             if (errno == EINTR)  
  39.                 continue;  
  40.             //否则就是其他错误  
  41.             else  
  42.                 return -1;  
  43.         }  
  44.         //如果 ==0则说明是什么也没写入, 可以继续写  
  45.         else if (nWritten == 0)  
  46.             continue;  
  47.   
  48.         //正常写入  
  49.         nLeft -= nWritten;  
  50.         pBuf += nWritten;  
  51.     }  
  52.     return count;  
  53. }  

SocketException类

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //SocketException类的设计与实现  
  2. class SocketException  
  3. {  
  4. public:  
  5.     typedef std::string string;  
  6.     SocketException(const string &_msg = string())  
  7.         : msg(_msg) {}  
  8.     string what() const  
  9.     {  
  10.         if (errno == 0)  
  11.             return msg;  
  12.         //如果errno!=0, 则会加上错误描述  
  13.         return msg + ": " + strerror(errno);  
  14.     }  
  15.   
  16. private:  
  17.     string msg;  
  18. };  

Socket类测试(echo)

Server端测试代码

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void sigHandler(int signo)  
  2. {  
  3.     while (waitpid(-1, NULL, WNOHANG) > 0)  
  4.         ;  
  5. }  
  6.   
  7. int main()  
  8. {  
  9.     signal(SIGCHLD, sigHandler);  
  10.     signal(SIGPIPE, SIG_IGN);  
  11.     try  
  12.     {  
  13.         TCPServer server(8001);  
  14.         std::string msg;  
  15.         while (true)  
  16.         {  
  17.             TCPClient client = server.accept();  
  18.             pid_t pid = fork();  
  19.             if (pid == -1)  
  20.                 err_exit("fork error");  
  21.             else if (pid > 0)  
  22.                 client.close();  
  23.             else if (pid == 0)  
  24.             {  
  25.                 try  
  26.                 {  
  27.                     while (true)  
  28.                     {  
  29.                         client.receive(msg);  
  30.                         cout << msg << endl;  
  31.                         client.send(msg);  
  32.                     }  
  33.                 }  
  34.                 catch (const SocketException &e)  
  35.                 {  
  36.                     cerr << e.what() << endl;  
  37.                     exit(EXIT_FAILURE);  
  38.                 }  
  39.                 exit(EXIT_SUCCESS);  
  40.             }  
  41.         }  
  42.     }  
  43.     catch (const SocketException &e)  
  44.     {  
  45.         cerr << e.what() << endl;  
  46.         exit(EXIT_FAILURE);  
  47.     }  
  48. }  

Client端测试代码

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     signal(SIGPIPE, SIG_IGN);  
  4.     try  
  5.     {  
  6.         TCPClient client(8001, "127.0.0.1");  
  7.         std::string msg;  
  8.         while (getline(cin, msg))  
  9.         {  
  10.             client.send(msg);  
  11.             msg.clear();  
  12.             client.receive(msg);  
  13.             cout << msg << endl;  
  14.             msg.clear();  
  15.         }  
  16.     }  
  17.     catch (const SocketException &e)  
  18.     {  
  19.         cerr << e.what() << endl;  
  20.     }  
  21. }  

Makefile

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. .PHONY: clean all   
  2. CC = g++   
  3. CPPFLAGS = -Wall -g -pthread -std=c++11  
  4. BIN = serverclient  
  5. SOURCES = $(BIN.=.cpp)  
  6.   
  7. all: $(BIN)  
  8. $(BIN): $(SOURCES) Socket.cpp  
  9.   
  10. clean:  
  11.     -rm -rf $(BIN) bin/ obj/ core  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值