版权声明:若无来源注明,Techie亮博客文章均为原创。 转载请以链接形式标明本文标题和地址:
本文标题:QTcpServer实现多客户端连接     本文地址:https://www.techieliang.com/2017/12/760/
文章目录

1. 介绍

QTcpServer使用请见:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端

QTcpServer类默认提供的只有无参数的newConnection的信号,这样虽然知道有人连接了,并且可以通过nextPendingConnection获取连接的socket,但并不便于管理,尤其是在连接断开以后无法判断具体那个断开了,因为QTcpSocket只提供了无参的disconnected信号。。。

这样就算在newConnection是存储一个list或者map,也无法在disconnected是知道具体是那一项断开连接,给不同的QTcpSocket的信号指向不同的槽。

实际上socket有自己的句柄,并通过下述函数在初步连接时就赋予了对应的socketDescriptor

  1. virtual void incomingConnection(qintptr socketDescriptor)

当有client连接时,首先是此方法被调用,可自行在此方法内建立QTcpSocket并将socketDescriptor值赋予socket,并在socket断开时告知此标识符

2. 范例

源码请见GitHub:QtOtherModuleExamples

tcp_server.h

  1. #ifndef TCP_SERVER_H
  2. #define TCP_SERVER_H
  3. #include <QTcpServer>
  4. namespace tcp_server_private {
  5. class TcpServerPrivate;
  6. }
  7. class QTcpSocket;
  8. /**
  9. * @brief Tcp多客户端服务器
  10. */
  11. class TcpServer : public QTcpServer {
  12. Q_OBJECT
  13. public:
  14. /**
  15. * @brief 构造函数
  16. * @param parent 父QObject
  17. */
  18. explicit TcpServer(QObject *parent = Q_NULLPTR);
  19. /**
  20. * @brief 析构函数
  21. * 非多线程模式行为:关闭所有连接后析构
  22. * 多线程模式行为:关闭所有连接及线程池后析构
  23. */
  24. ~TcpServer();
  25. signals:
  26. /**
  27. * @brief 客户端连入
  28. * @param 连接句柄
  29. * @param socket指针
  30. */
  31. void ClientConnected(qintptr, QTcpSocket*);//发送新用户连接信息
  32. /**
  33. * @brief socket已断开连接
  34. * 若需要在socket后析构后进行操作的可连接此信号
  35. * @param 连接句柄
  36. */
  37. void ClientDisconnected(qintptr);
  38. /**
  39. * @brief 主动断开连接信号
  40. * 若服务端想要主动断开与客户端连接将会发出此信号
  41. * 此信号发出这表明进行断开操作不表明断开成功,成功以SocketDisconnected信号为准
  42. * @param 连接句柄
  43. */
  44. void InitiativeDisConnectClient(qintptr);
  45. protected slots:
  46. /**
  47. * @brief 客户端已断开槽
  48. * 此槽与客户端的已断开信号连接
  49. * @param handle
  50. */
  51. void ClientDisconnectedSlot(qintptr handle);
  52. protected:
  53. /**
  54. * @brief 重写-有连接到来
  55. * 连接到来不一定连接,会根据maxPendingConnections决定是否连接
  56. * @param handle 连接句柄
  57. */
  58. virtual void incomingConnection(qintptr handle);
  59. private:
  60. tcp_server_private::TcpServerPrivate *private_;
  61. };
  62. #endif // TCP_SERVER_H

tcp_server.cpp

  1. #include "tcp_server.h"
  2. #include "tcp_server_private.h"
  3. //构造函数
  4. TcpServer::TcpServer(QObject *parent)
  5. : QTcpServer(parent),
  6. private_(new tcp_server_private::TcpServerPrivate) {
  7. }
  8. //析构函数
  9. TcpServer::~TcpServer() {
  10. for(tcp_server_private::TcpSocket *client : private_->clients.values()) {
  11. client->disconnectFromHost();
  12. auto handle = client->socketDescriptor();
  13. client->deleteLater();
  14. //告知其他调用者 当前socket断开,避免有需要在socket后执行的方法
  15. emit ClientDisconnected(handle);
  16. }
  17. if(private_)
  18. delete private_;
  19. this->close();
  20. }
  21. //重写-有连接到来
  22. void TcpServer::incomingConnection(qintptr handle) {
  23. //超出最大练级数量关闭连接
  24. if (private_->clients.size() > maxPendingConnections()) {
  25. QTcpSocket tcp;
  26. tcp.setSocketDescriptor(handle);
  27. tcp.disconnectFromHost();
  28. return;
  29. }
  30. auto client_socket = new tcp_server_private::TcpSocket(handle);
  31. Q_ASSERT(client_socket->socketDescriptor() == handle);
  32. //socket断开连接的信号与server槽连接
  33. connect(client_socket,
  34. &tcp_server_private::TcpSocket::ClientDisconnected,
  35. this,
  36. &TcpServer::ClientDisconnectedSlot);
  37. //主动断开连接的操作
  38. connect(this,
  39. &TcpServer::InitiativeDisConnectClient,
  40. client_socket,
  41. &tcp_server_private::TcpSocket::DisconnectSocket);
  42. //map记录
  43. private_->clients.insert(handle, client_socket);
  44. qDebug()<<handle<<"connected";
  45. emit ClientConnected(handle, client_socket);
  46. }
  47. //客户端已断开槽
  48. void TcpServer::ClientDisconnectedSlot(qintptr handle) {
  49. //map中移除
  50. private_->clients.remove(handle);
  51. qDebug()<<handle<<"disconnected";
  52. //发出信号
  53. emit ClientDisconnected(handle);
  54. }

private

  1. #ifndef TCP_SERVER_PRIVATE_H
  2. #define TCP_SERVER_PRIVATE_H
  3. #include <QTcpSocket>
  4. namespace tcp_server_private {
  5. class TcpSocket : public QTcpSocket {
  6. Q_OBJECT
  7. public:
  8. /**
  9. * @brief 构造函数
  10. * @param socketDescriptor 连接句柄
  11. * @param parent 父QObject
  12. */
  13. TcpSocket(qintptr handle, QObject *parent = 0);
  14. signals:
  15. /*
  16. * 已断开连接信号
  17. */
  18. void ClientDisconnected(qintptr);
  19. public slots:
  20. /**
  21. * @brief 断开连接
  22. * @param handle 连接句柄
  23. */
  24. void DisconnectSocket(qintptr handle);
  25. private:
  26. qintptr handle_;
  27. };
  28. /**
  29. * @brief Tcp多客户端服务器私有类
  30. */
  31. class TcpServerPrivate {
  32. public:
  33. QMap<int, TcpSocket *> clients; ///所有连接的map
  34. };
  35. }//tcp_server_private
  36. #endif // TCP_SERVER_PRIVATE_H
  37. //cpp
  38. #include "tcp_server_private.h"
  39. namespace tcp_server_private {
  40. //构造函数
  41. TcpSocket::TcpSocket(qintptr handle, QObject *parent)
  42. : QTcpSocket(parent), handle_(handle) {
  43. this->setSocketDescriptor(handle_);
  44. //断开连接消息
  45. connect(this,&TcpSocket::disconnected,
  46. [&](){
  47. this->deleteLater();
  48. emit this->ClientDisconnected(handle_);
  49. });
  50. }
  51. //主动断开连接槽
  52. void TcpSocket::DisconnectSocket(qintptr handle) {
  53. if (handle == handle_)
  54. disconnectFromHost();
  55. }
  56. }
  • incomingConnection首先判断是否超出最大连接数量,超出就断开新链接,最大连接数量在maxPendingConnections方法获取,而当前已连接client在自定义server的TcpServerPrivate::clients这个QMap记录,此map记录了socketDescriptor和socket指针的映射关系
  • 若未达最大连接数量,则连接创建新的tcpsocket,并将socketDescriptor赋值到socket对象,最后发出ClientConnected信号,此信号带有新链接的socket的指针,可以用于自定义收发信号槽。
  • socket对象的disconnected信号直接connect到了lambda表达式以发出新的信号,新信号ClientDisconnected,并带有socketDescriptor句柄标识符,从而避免了socket断开连接后不知道具体哪个断开
  • socket在创建时均吧ClientDisconnected信号与server的ClientDisconnectedSlot槽连接,当有连接断开时会动态维护map记录
  • server析构时以disconnectFromHost方法主动断开和所有客户端连接,若需要等客户端先断开可以用waitForDisconnected

上述范例将TcpSocket定义在tcp_server_private命名空间不对外可见,且TcpServer返回值也是QTcpSocket,若需要继承QTcpSocket做更多自定义需要将TcpSocket移出

转载请以链接形式标明本文标题和地址:Techie亮博客 » QTcpServer实现多客户端连接

QTcpServer实现多客户端连接的更多相关文章

  1. 高版本->低版本迁移,低版本客户端连接高版本数据库EXP导出报错EXP-00008,ORA-01455,EXP-00000

    生产环境: 源数据库:RHEL + Oracle 11.2.0.3 目标数据库:HP-UX + Oracle 10.2.0.4   需求:迁移部分表  11.2.0.3-->10.2.0.4,若 ...

  2. Redis客户端连接池

    使用场景 对于一些大对象,或者初始化过程较长的可复用的对象,我们如果每次都new对象出来,那么意味着会耗费大量的时间. 我们可以将这些对象缓存起来,当接口调用完毕后,不是销毁对象,当下次使用的时候,直 ...

  3. 【RabbitMQ】CentOS安装RabbitMQ,及简单的Java客户端连接

    在CentOS安装 因Rabbit MQ使用Erlang,所以需要先安装Erlang,安装过程中可能会遇到种种问题,可参考CentOS 6.5安装Erlang/OTP 17.0.然后就可以安装MQ了. ...

  4. redis客户端连接异常

    本文参考:http://mdba.cn/2015/04/02/redistwemproxy-%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5%e5%bc%82 ...

  5. Mysql从客户端连接服务器连不上的问题

    Mysql从客户端连接服务器连不上的问题   公司要用Mysql做一个测试,开始在自己的本地建一个Mysql数据库自己本地的程序再连上去,没有遇到过连接不上的问题.这次数据库在服务器上,从本地客户端连 ...

  6. Mark一下,一上午就这么过去了,关于客户端连接oracle10G的问题

    Mark一下,一上午就这么过去了,关于客户端连接oracle10G的问题 正常的客户端PLSQL和Navicat都可以正常连接Oracle(局域网内),但代码生成器和VS2015死活连不上,在网上找了 ...

  7. atitit.客户端连接oracle数据库的方式总结

    客户端连接oracle数据库的方式总结 目录 Java程序连接一般使用jar驱动连接..... 桌面GUI一般采取c语言驱动oci.dll 直接连接... 间接连接(需要配置tns及其envi var ...

  8. 今天遇到的一个问题(windows的ssh客户端连接不到虚拟机Ubuntu)

    今天比较郁闷,想用windows上的ssh客户端连接虚拟机中的Ubuntu. 但是死活连不上,之前是能脸上的,所以比较郁闷. 我首先在windows上ping Ubuntu的ip地址,竟然发不了数据包 ...

  9. Redis基础知识之————如何处理客户端连接

    redis 连接建立 Redis Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作: 首先,客户端 ...

随机推荐

  1. 类似register uint32_t __regPriMask __ASM("primask");的代码分析

    代码: #define __ASM __asm /*!< asm keyword for ARM Compiler */ #define __INLINE __inline /*!< in ...

  2. 单线程+多线程下载doutula.com图片

    现在是2018年8月11日11:26:42,我挖的eth又降价了..... 单线程 # -*- coding:utf-8 -*- import re import os import urllib i ...

  3. python 验证码识别初探

    使用 pytesser 与 pytesseract 识别验证码 前置 :  首先需要安装  tesserract tesserract windows 安装包及中文 https://pan.baidu ...

  4. 20155308 实验四 Android开发基础

    20155308 实验四 Android开发基础 实验内容 基于Android Studio开发简单的Android应用并部署测试; 了解Android.组件.布局管理器的使用: 掌握Android中 ...

  5. 20155333 2016-2017-2《Java程序设计》第二周学习总结

    20155333 2016-2017-2<Java程序设计>第二周学习总结 教材学习内容总结 1. Java 类型系统:基本类型和类类型(参考类型) 2. 基本类型: 整数:short整数 ...

  6. [arc076F]Exhausted?

    Description 传送门 Solution 额外的椅子可以放置在任意实数位置,所以该问题其实就问最多能够有多少人坐下.由于每个人的需求有<=l和>=r两个限制,并不是很好下手,我们先 ...

  7. 【LG5019】[NOIP2018]道路铺设

    [LG5019][NOIP2018]道路铺设 题面 洛谷 题解 \(NOIP\) 抄 \(NOIP\)差评 设当前做到了位置\(i\) 且\(h_i\) \(-\) \(h_i\)\(_+\)\(_1 ...

  8. LVS入门篇(一)之ARP协议

    1.概念 地址解析协议,即ARP(AddressResolutionProtocol),是根据IP地址获取物理MAC地址的一个TCP/IP协议.主机发送信息时将包含目标IP地址的ARP请求广播到网络上 ...

  9. centos7下mysql 开启远程登录

    安装mysql 安装mysql就不做过多的介绍了,相信小伙伴们都可以很轻松的安装 进入mysql命令行 mysql -u用户名 -p密码 新建远程登录的用户 grant all on *.* to a ...

  10. Django——test文件编写接口测试

    用自己建立的小网页来做接口测试,在Django的tests.py写下如下 test_login_page为用get方式登录login路径,根据回复验证是否查看到页面 test_login_action ...