本篇博客定義一套用於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