远程服务端
1、作用
远程服务器在网络摄像头中起的是一个桥接的作用,将摄像头服务端与摄像头客户端连接起来。正如网络摄像头第一篇(原理)所介绍的一样,当摄像头服务端启动之后,会主动与远程服务端进行连接,这时候,远程服务器会将摄像头服务端的ip地址和端口进行保存起来。当摄像头客户端启动之后,远程服务器会将摄像头客户端的ip和地址发送给摄像头服务端。
2、服务器界面
2.1、界面
2.2、服务器放在局域网
2.3、服务器放在外网
3、NetServerTCP 实现
3.1、NetServerTCP 头文件
#ifndef NETSERVERTCP_H
#define NETSERVERTCP_H
#ifndef INETSERVERTCP_H
#include "network/inetservertcp.h"
#endif /*INETSERVERTCP_H*/
#include <thread>
BEGIN_DOX_NAMESPACE
FORWARD_DECLARE_CLASS(INetServerReactor)
class NetServerTCP
: public INetServerTCP
{
REGISTER_ACTUALIZE(NetServerTCP, INetServerTCP)
public:
/* @接口 默认构造函数
* @类名 [NetServerTCP]
* @邮箱 575814050@qq.com
* @时间 2020年3月14号
*/
NetServerTCP();
/* @接口 默认构造函数
* @类名 [NetServerTCP]
* @邮箱 575814050@qq.com
* @时间 2020年3月14号
*/
~NetServerTCP();
/* @接口
* @类名 [INetServer]
* @邮箱 575814050@qq.com
* @时间 2020年4月19日
*/
virtual void stopServer();
/* @接口 等待连接
* @参数 int 连接的个数
* @类名 [IFTPNetServer]
* @邮箱 575814050@qq.com
* @时间 2020年4月19日
*/
virtual bool waitConnect(int);
/* @接口 初始化端口
* @参数 int 默认端口21
* @参数 int 客户端的个数
* @类名 [INetProtocol]
* @邮箱 575814050@qq.com
* @时间 2020年4月19日
*/
virtual bool initIPAndPort(int);
/* @接口
* @类名 [INetServer]
* @邮箱 575814050@qq.com
* @时间 2020年4月19日
*/
virtual void startServer(RecvCallBackFunc);
/* @接口
* @类名 [INetServer]
* @邮箱 575814050@qq.com
* @时间 2020年4月21日
*/
virtual void setBufferSize(int bufSize = 40960);
/* @接口 发送消息
* @参数 QString 发送消息
* @类名 [INetServer]
* @邮箱 575814050@qq.com
* @时间 2020年4月20日
*/
virtual bool sendMessage(ulong, const QString &);
private:
/* @接口
* @类名 [NetServerTCP]
* @邮箱 575814050@qq.com
* @时间 2020年11月18日
*/
void initNetServerTCP();
private:
ulong m_sock; std::thread m_thread;
QList<Object<INetServerReactor>> m_reactors;
};
END_DOX_NAMESPACE
#endif /*NETSERVERTCP_H*/
3.2 、NetServerTCP源文件
#ifndef NETSERVERTCP_H
#include "netservertcp.h"
#endif /*NETSERVERTCP_H*/
#include "winsock2.h"
#include "network/inetmacro.h"
#include "manager/iappmanager.h"
#include "network/inetserverreactor.h"
#include <thread>
BEGIN_DOX_NAMESPACE
NetServerTCP::NetServerTCP()
: INetServerTCP()
{
WSADATA wsaData;
if(0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
{
DoxLoggerFL("Winsock Initialization failed");
ExitProcess(0);
}
m_sock = socket(PF_INET, SOCK_STREAM, 0);
initNetServerTCP();
}
NetServerTCP::~NetServerTCP()
{
closesocket(m_sock);
}
void NetServerTCP::stopServer()
{
}
void NetServerTCP::initNetServerTCP()
{
Object<IAppManager> iAppManager(NIL);
QStringList clss = iAppManager->classListFromIID(INetServerReactor::ClassGuid());
for(int idx = 0; idx < clss.length(); ++idx)
{
QString cls = clss[idx];
Object<INetServerReactor> iReactor(cls.toLocal8Bit().constData());
if(iReactor.valid()) m_reactors.append(iReactor);
}
}
bool NetServerTCP::waitConnect(int count)
{
if(SOCKET_ERROR == listen(m_sock, count))
{
DoxLoggerFL("监听套接字失败");
return false;
}
return true;
}
bool NetServerTCP::initIPAndPort(int port)
{
SOCKADDR_IN servAdr;
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(port);
if(SOCKET_ERROR == bind(m_sock, (SOCKADDR *)&servAdr, sizeof(servAdr)))
{
DoxLoggerFL("绑定端口失败");
return false;
}
return true;
}
void NetServerTCP::setBufferSize(int bufSize)
{
}
bool NetServerTCP::sendMessage(ulong, const QString &)
{
return false;
}
void NetServerTCP::startServer(RecvCallBackFunc backFunc)
{
auto acceptCB = [this]()
{
while(true)
{
SOCKADDR_IN clntAdr; int clntAdrSize = sizeof(clntAdr);
SOCKET hClntSock = accept(m_sock, (SOCKADDR *)&clntAdr, &clntAdrSize);
for(int idx = 0; idx < m_reactors.length(); ++idx)
m_reactors[idx]->acceptClient(hClntSock);
}
};
m_thread =std::thread(acceptCB);
}
END_DOX_NAMESPACE
3.3、解释
NetServerTCP(); 构造中初始化套接字的版本
initNetServerTCP(); 用来初始化服务器反应器(反应器说白了就是一组回调构成的类,每个回调都是一个接口)
initIPAndPort(); 初始化套接字地址以及绑定套接字
waitConnect(); 用来设定套接字监听的个数
startServer(); 让套接字处于监听状态,注意在这个函数里面引用了lambda表达式,这个表示式是一个线程函数。当套接字被客户端连接上时,accept就会返回,然后开始进入反应器结合,这个反应器开始调用acceptClient这个接口,这个接口是在其他的dll中实现,通过这种反应器技术,我们可以将其他dll中的函数直接在这里调用。
INetServerReactor 这是一个反应器接口类,里面定义了一组接口,只要实现类继承了这个接口并且被注册到框架中,那么每当客户端连接到服务器时,都会主动执行这个接口类中的acceptClient接口。
4、ClientsList 实现(实现了INetServerReactor反应器)
4.1、ClientsList 头文件
#ifndef CLIENTSLIST_H
#define CLIENTSLIST_H
#include "widget/iwidget.h"
#include "QtWidgets/QListView"
#include "network/inetserverreactor.h"
FORWARD_DECLARE_CLASS(QStandardItemModel)
BEGIN_DOX_NAMESPACE
class ClientsList
: public IWidget
, public QListView
, public INetServerReactor
{
REGISTER_ACTUALIZES(ClientsList, IWidget, INetServerReactor)
public:
/* @接口 默认构造函数
* @类名 [ClientsList]
* @作者 杨发荷
* @邮箱 575814050@qq.com
* @时间 2020年3月14号
*/
ClientsList();
/* @接口 默认构造函数
* @类名 [ClientsList]
* @作者 杨发荷
* @邮箱 575814050@qq.com
* @时间 2020年3月14号
*/
~ClientsList();
/* @接口
* @类名 [ClientsList]
* @作者 杨发荷
* @邮箱 575814050@qq.com
* @时间 2020年11月17日
*/
virtual ObjectPtr selfWidget();
/* @接口
* @类名 [ClientsList]
* @作者 杨发荷
* @邮箱 575814050@qq.com
* @时间 2020年11月18日
*/
virtual bool acceptClient(ulong);
/* @接口
* @类名 [ClientsList]
* @作者 杨发荷
* @邮箱 575814050@qq.com
* @时间 2020年11月18日
*/
virtual QString windowTitle() const;
private:
/* @接口
* @类名 [ClientsList]
* @作者 杨发荷
* @邮箱 575814050@qq.com
* @时间 2020年11月18日
*/
void initClientsList();
private:
QStandardItemModel *m_model;
};
END_DOX_NAMESPACE
#endif /*CLIENTSLIST_H*/
4.2、ClientList 源文件
#include "winsock2.h"
#include "clientslist.h"
#include "widget/imainwindow.h"
#include "QtWidgets/QMessageBox"
#include "QtGui/QStandardItemModel"
BEGIN_DOX_NAMESPACE
ClientsList::ClientsList()
: QListView(WIDGET_PARENT)
, IWidget(), INetServerReactor()
{
initClientsList();
}
ClientsList::~ClientsList()
{
}
ObjectPtr ClientsList::selfWidget()
{
return DOX_DYNAMIC_PTR(QWidget);
}
void ClientsList::initClientsList()
{
m_model = new QStandardItemModel;
QListView::setModel(m_model);
QListView::resize(200, 600);
Object<IMainWindow> iMainWindow(NIL);
iMainWindow->resize(int2(200, 600));
IWindowFlags wflags = iMainWindow->windowFlags();
iMainWindow->windowFlags(wflags & ~wfWindowMinMaxButtonsHint);
}
bool ClientsList::acceptClient(ulong sock)
{
SOCKADDR_IN address;
int nAddrLen = sizeof(address);
memset(&address, 0, nAddrLen);
//根据套接字获取地址信息
if(::getpeername(sock, (SOCKADDR *)&address, &nAddrLen) != 0) return false;
QString ip = ::inet_ntoa(address.sin_addr);
u_short port = ntohs(address.sin_port);
QString item = QS("%1:%2").arg(ip).arg(port);
m_model->appendRow(new QStandardItem(item));
return true;
}
QString ClientsList::windowTitle() const
{
return QS("客户端列表");
}
END_DOX_NAMESPACE
4.3、解释
ClientsList(); 构造函数
initClientsList(); 用来初始化QListView列表以及框架标题和大小
acceptClient(); 通过客户端套接字,获取客户端IP地址和端口号(这个接口也就是在3.2小节startServer中被调用)
5、调用
5.1、注册类
#include "clientslist.h"
#include "netservercmd.h"
#include "core/modulemacro.h"
#pragma comment (lib,"ws2_32.lib")
BEGIN_DOX_NAMESPACE
BEGIN_DEFINE_DOX_MODULE("NetServer")
XDEFINE_CLASSMAP_ENTRY_SINGLE(ClientsList)
FINISH_DEFINE_DOX_MODULE()
END_DOX_NAMESPACE
OUTCAPI bool attachDoxygen()
{
return dox::initApplication();
}
OUTCAPI bool detachDoxygen()
{
return true;
}
5.2、模块初始化
#include "netservercmd.h"
#include "winsock2.h"
#include "core/idoxcommand.h"
#include "network/inetmacro.h"
#include "widget/imainwindow.h"
#include "widget/idockwidget.h"
#include "network/inetmanager.h"
#include "network/inetservertcp.h"
BEGIN_DOX_NAMESPACE
void netServerUi()
{
Object<IMainWindow> iMainWindow(NIL);
Object<IWidget> iWidget("NetServer.ClientsList");
iMainWindow->setWidget(iWidget);
}
void startServer()
{
Object<INetManager> iNetManager(NIL);
Object<INetServer> iNetServer = iNetManager->netServer(ProtocolTCP);
iNetServer->initIPAndPort(1119); iNetServer->waitConnect(64);
iNetServer->startServer(NULL); iNetServer->retainObject();
}
bool initApplication()
{
Object<IDoxCommand> command(NIL); netServerUi();
command->registerCommand("NetServer", "startServer", startServer);
return true;
}
END_DOX_NAMESPACE
首先先将通过netServerUi接口将ClientsList加到主界面中从而显示2.1小节中的主界面,然后通过执行startserver这个命令,启动服务器服务。接下来就是等待摄像头端的连接。