QT + Socket 客户端/服务端 公网通讯

项目需求:

有很多分布各地的客户端硬件(设备是小米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就不一样了

相关推荐
是main不是漫1 小时前
【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基
c语言·开发语言·c++
小七在进步11 小时前
string的使用(1)
c++·visual studio
lzx_00212 小时前
Linux 进程(三) 从进程状态到僵尸进程、孤儿进程
linux·服务器
白杨尚青12 小时前
C++进阶篇(六):map 和 set 的模拟实现——用红黑树封装 mymap/myset(迭代器 + operator[])
开发语言·c++·迭代器·stl·set·map·红黑树
kloc15 小时前
聊天室项目入口网关子服务的实现
linux·c++
csdn_aspnet18 小时前
C++ 3个有序点的方向(Orientation of 3 ordered points)
c++·算法
星恒随风19 小时前
MySQL 从零入门(四):表的增删改查与基础查询详解
linux·服务器·数据库·笔记·mysql
小七在进步19 小时前
string的使用(2)
c++
青少儿编程课堂20 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:折半搜索优化子集枚举(Meet in the Middle)解析
c++·python·算法·bfs·信息学竞赛
wo_jack1 天前
C++ 入门笔记(十七):list 入门 —— 为什么 list 不能 sort?
数据结构·c++·面试·stl·list