项目需求:
有很多分布各地的客户端硬件(设备是小米pad),需要做一个服务器,支持客户端数据联网上传到服务器,在服务器端进行数据管理和可视化
实现步骤:
通讯:
公网通讯使用的还是QT Socket通讯那一套东西,唯一的区别是原来是在局域网进行通讯,现在是联网通讯,区别是公网IP的差别;
实现思路:
在有公网IP的基础上,建立一个Socket服务器,建立一个接收一定数量的客户端队列,使用共同的端口号,这个队列主要是用来接收设备端的数据;建立另一个客户端队列,使用另外的端口号,这个队列主要是用来查询、可视化、修改的,相当于是有一个专用的exe用来接收和转发数据,可视化软件专门用来查询和可视化
没办法的事,正常来说是需要找一个前端和java后端来做,但是没有这方面的人,还只会用QT C++,所以间接实现吧
关键技术:
主要是建立socket服务,其他可视化部分就是工作量的事情了
步骤1:
首先,先查询下当前的wifi是不是有公网ip,办公室的网络是移动网络,登录路由器查询后,发现拨号连接和DCHP都是10*和100 开头的,在百度搜索框中,检索【IP查询】,把IP地址输入,看下情况;
如下:办公室的wifi是局域网,也就是说入户的网线已经是被公网转发过的了,没有暴露公网ip

步骤2:
如果没有公网IP无法进行下一步,看怎么申请下来,我跳过这步,用了阿里云的云服务器(试用几天看看),先验证功能;要确保有公网IP,如果后续转实体服务器,也需要有公网IP才行

步骤3:
建立socket服务器:
1.界面端,开启一个子线程,用来初始化服务端监听
监听的时候,重点来了,正常来说,一个公网IP不可能只连一个电脑,如上图,除了公网IP外,还有一个主私网IP,这个IP也是局域网,所以监听的时候直接监听的IP是0.0.0.0,端口号指定,一旦有公网IP有用户连接,会直接转发给私网
2.我的服务端访问量不高,所以我只内置了10个客户端的队列,也就是说超过10个无法连接,服务端也可以主动踢人
3.通讯数据交互,我选择json字符串,通用解析,只要定好规则就行
4.需要注意的是 ,如果多个客户端在同一个wifi局域网中,实际上服务端响应的ip是同一个ip,只是端口号有区别,所以踢人的时候,需要指定对象的时候,一定是IP+端口号,或者给每个客户端指定一个别名
5.收发信息,要区别是哪个客户端发送来信息了?我要给哪个客户端做信息反馈?这就需要按照ip和端口号进行查找了,此外,如果发的包一次性没发完,还需要进行包合并啥的(因为我是json字符串,每次收到信息都要检查json字符串的完整性,看是不是分包了)
关键代码如下:
cpp
// 在子线程中创建QTcpServer对象
m_tcpServer = new QTcpServer(this);
// 确定监听地址
QHostAddress listenAddress;
if (m_isuserealip && !m_ip.isEmpty())
{
listenAddress = QHostAddress(m_ip);
} else {
listenAddress = QHostAddress::Any;
}
// 启动服务器
if (m_tcpServer->listen(listenAddress, m_port))
{
m_running = true;
PUSH_LOG(INFO_LOG_LEVEL,"服务端开始监听:ip:%s port:%d",m_ip.toStdString().c_str(),m_port);
} else {
QString errorMsg = QString("服务器启动失败:%1").arg(m_tcpServer->errorString());
PUSH_LOG(ERROR_LOG_LEVEL,errorMsg.toStdString().c_str());
}
// 连接信号
connect(m_tcpServer, &QTcpServer::newConnection,
this, &ServerSocketCommunciateFunc::onNewConnection);
cpp
//超过预设数量,不连接
if (m_clients.size() >= m_ClientCount) {
QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
......
}
// 接受新连接
QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
if (!clientSocket) {
PUSH_LOG(ERROR_LOG_LEVEL,"客户端连接失败");
return;
}
// 添加到客户端列表
if (addClient(clientInfo))
{
PUSH_LOG(INFO_LOG_LEVEL,"新客户端已连入,ip: %s port: %d",clientInfo.peerAddress.toStdString().c_str(),clientInfo.peerPort);
// emit clientConnected(clientInfo);
}
cpp
// 客户端信息结构体
struct ClientInfo {
qintptr socketDescriptor; // 客户端socket描述符
QTcpSocket *socket; // 客户端socket指针
QString clientName; // 客户端名称
QString peerAddress; // 客户端地址
int peerPort; // 客户端端口
QByteArray buffer; // 数据缓冲区,用于处理分包
ClientInfo(){
socketDescriptor = -1;
socket = nullptr;
clientName = "";
peerAddress = "";
peerPort = 0;
buffer.clear();
}
};
/**
* @brief m_clients 所有已连接的客户端
*/
QList<ClientInfo> m_clients;
cpp
//有信息发过来了,我们要看是哪个socket发过来的
QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
if (!clientSocket)
{
return;
}
//查看下socket的描述符
qintptr socketDescriptor = clientSocket->socketDescriptor();
QString ipip = clientSocket->peerAddress().toString();
int port = clientSocket->peerPort();
// 查找客户端信息
int clientid_ = findClientIndexByDescriptor(ipip,port);
if (m_clients[clientid_].socketDescriptor == -1)
{
PUSH_LOG(WARN_LOG_LEVEL,"接收到未知客户端的数据,描述符为:%d",socketDescriptor);
return;
}
//开始解析数据
QByteArray responseData = clientSocket->readAll();
QString ss = QString::fromUtf8(responseData);//读取一次后,转为字符
m_clients[clientid_].buffer.append(responseData);//把数据追加
//如果是完整的json字符串,就需要开出任务响应了
贴一个截图吧,先把技术验证过了,后面再说工作量的事:
1.服务端简单一个日志窗口:
2.客户端1:window主机的调试助手
3.客户端2:手机【 码农宝】,Socket客户端工具
两个客户端都使用办公室wifi局域网,所以显示的ip都是一样的,就是端口号不一样,如果手机不连wifi,ip就不一样了
