Socket编程实践(7) --Socket-Class封装(改进版v2)
2024-09-24 00:55:19 原文
本篇博客定义一套用于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
Socket编程实践(7) --Socket-Class封装(改进版v2)的更多相关文章
- Socket编程实践(2) Socket API 与 简单例程
在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...
- Socket编程实践(3) --Socket API
socket函数 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, ...
- Socket编程实践(2) --Socket编程导引
什么是Socket? Socket可以看成是用户进程与内核网络协议栈的接口(编程接口, 如下图所示), 其不仅可以用于本机进程间通信,可以用于网络上不同主机的进程间通信, 甚至还可以用于异构系统之间的 ...
- C# socket编程实践
C# socket编程实践——支持广播的简单socket服务器 在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# ...
- Socket编程实践(10) --select的限制与poll的使用
select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或 ...
- Socket编程实践(6) --TCP服务端注意事项
僵尸进程处理 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中添加 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法,解决僵尸进程 sign ...
- Socket编程实践(6) --TCPNotes服务器
僵尸进程过程 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中加入 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法.解决僵尸进程 sign ...
- Socket编程实践(1) 基本概念
1. 什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口.TCP/IP协议的底层部分已经被内核实现了,而应用层是用户需要实现的,这部分程序工作在用户空间.用户空间的程序需要通 ...
- C# socket编程实践——支持广播的简单socket服务器
在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# socket编程基本知识,写一个支持广播的简单server/clie ...
- C# Socket编程(4)初识Socket和数据流
经过前面基础知识作为背景,现在对Socket编程进行进一步的学习.在System.Net.Socket命名空间提供了Socket类,利用该类我们可以直接编写Socket的客户端和服务的的程序.但是直接 ...
随机推荐
- List Set Map比较
List按对象进入的顺序保存对象,不做排序或编辑操作. Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set,而不关心它的顺序–否则应该使用List). Map同 ...
- Oracle中表字段相关操作举例
--创建测试表 create or replace table student ( xh ), --学号 xm ), --姓名 sex ), --性别 birthday date, --日期 sal ...
- delphi 组件安装教程详解
学习安装组件的最好方法,就是自己编写一个组件并安装一遍,然后就真正明白其中的原理了. 本例,编写了两个BPL, dclSimpleEdit.bpl 与 SimpleLabel.bpl ,其中,dc ...
- Docker标准化开发测试和生产环境
对于大部分企业来说,搭建 PaaS 既没有那个精力,也没那个必要,用 Docker 做个人的 sandbox 用处又小了点. 可以用 Docker 来标准化开发.测试.生产环境. Docker 占用资 ...
- Rails做rspec测试时出现bcrypt错误的解决
在用rspec做测试的时候,出现了如下一句错误: You don't have bcrypt-ruby installed in your application. Please add it to ...
- AJAX编程实践
---------------------------------------------------------------------------------------------------- ...
- Gradle 1.12用户指南翻译——第五十二章. Maven 插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上 ...
- Python 描述符 data 和 non-data 两种类型
仅包含__get__的,是non-data descriptor, 如果实例__dict__包含同名变量, 则实例优先; 如果还包含__set__, 则是data descriptor, 优先于实例_ ...
- Python 通过继承实现标准对象的子类
idict是dict的子类,它的键值和属性是同步的,并且有强大的默认值机制. 例如,假设x是idict的一个实例,且x['a']['b']=12,则有x.a.b=12.反之亦然; 假设'c'不在x的键 ...
- Gazebo機器人仿真學習探索筆記(一)安裝與使用
Gazebo提供了多平臺的安裝和使用支持,大部分主流的linux,Mac以及Windows,這裏結合ROS以Ubuntu爲例進行介紹. 首先是參考資料:http://gazebosim.org/tut ...