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的客户端和服务的的程序.但是直接 ...
随机推荐
- Node.js OS 模块
Node.js os 模块提供了一些基本的系统操作函数.我们可以通过以下方式引入该模块: var os = require("os") 方法 序号 方法 & 描述 1 os ...
- 为什么《Dive into Python》不值得推荐
2010 年 5 月 5 日更新:我翻译了一篇<<Dive Into Python>非死不可>作为对本文观点的进一步支持和对评论的回复,请见:http://blog.csdn. ...
- Linux下的有用命令
在之前的博客<Linux下常用命令与使用技巧>中,介绍了Linux的常用命令,在今天的博客中,给大家介绍其他的有用命令. 1.文本转换命令 在Linux下工作,我们不可避免地要和文件格式做 ...
- Zookeeper 客户端API调用示例(基本使用,增删改查znode数据,监听znode,其它案例,其它网络参考资料)
9.1 基本使用 org.apache.zookeeper.Zookeeper是客户端入口主类,负责建立与server的会话 它提供以下几类主要方法 : 功能 描述 create 在本地目录树中创建 ...
- MySQL备忘录
1 数据库概念(了解) 1.1 什么是数据库 数据库就是用来存储和管理数据的仓库! 数据库存储数据的优先: l 可存储大量数据: l 方便检索: l 保持数据的一致性.完整性: l 安全,可共享: l ...
- 安卓高级7 vitamio 视频框架 从raw文件下获取文件uri
vitamio免费的拥有多种解码器 而且容易操作 我们先来看看原生视频播放器的怎么使用 原生的: package qianfeng.com.videoviewdemo; import android. ...
- 从Dynamics CRM2011到Dynamics CRM2016的升级之路
CRM的产品更新特别快,特别是最近的几个版本,很多客户依旧停留在2011甚至是4.0,也经常会听到有人问2011能不能升级至最新版,2013能不能升级至最新版,本文将简单演示下从2011升级到2016 ...
- Android样式(style)和主题(theme)
样式和主题 样式是指为 View 或窗口指定外观和格式的属性集合.样式可以指定高度.填充.字体颜色.字号.背景色等许多属性. 样式是在与指定布局的 XML 不同的 XML 资源中进行定义. Andro ...
- FORM实现中打开图片,链接,文档(参考自itpub上一篇帖子,整理而来)
FORM实现中打开图片,链接,文档 参考自itpub上一篇帖子,整理而来 1.添加PL程序库D2kwutil.pll 2.主要实现程序 /*过程参数说明: v_application --打开文件的应 ...
- Afinal加载网络图片及下载文件使用方法
Afinal快速开发框架使用起来非常方便,下面将讲解如何利用Afinal加载网络图片及下载文件: 先看效果图: 注意:使用Afinal前需添加Afinal的jar,可以在这里下载:http://dow ...