版权声明:若无来源注明,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. python列表的通用操作

    #'+'和'*'#+可以将两个列表拼接为一个列表my_list = [1,2,3]+[4,5,6]#*可以将列表重复指定的次数my_list = [1,2,3]*5 print(my_list) #创 ...

  2. 图 ADT接口 遍历运算 常规运算 邻接矩阵实现

    Graph.h   (图的结构, 遍历, 常规操作接口) /*定义图的最大定点数, 它要大于等于具体图的顶点树n*/ #define MaxVertexNum 12 /*定义图的最大边数,它要大于等于 ...

  3. Java动态代理代码快速上手

    动态代理的两个核心的点是:代理的行为 和 代理机构. 举个例子,上大学的时候,很多同学吃午饭的时候都是叫别人带饭,有一个人H特别热心肠,想了一个办法,他在门口挂了个公示牌,每天有谁想要找人带饭就写公告 ...

  4. 车道线识别/Opencv/传统方法

    车道检测(Advanced Lane Finding Project) 实现步骤: 使用提供的一组棋盘格图片计算相机校正矩阵(camera calibration matrix)和失真系数(disto ...

  5. 2016-2017-2 20155302 实验四 Android 开发基础

    2016-2017-2 20155302 实验四 Android 开发基础 实验内容 1.下载和安装Android Studio 2.学会使用Android Studio进行简单的Android开发 ...

  6. sql语句-5-联接组合查询

  7. day 2 异常传递 ,抛出

    1.异常的传递 def test1(): print("---test1--") print(num) print('---test1 over---') def test2(): ...

  8. 【CF960G】Bandit Blues

    [CF960G]Bandit Blues 题面 洛谷 题解 思路和这道题一模一样,这里仅仅阐述优化的方法. 看看答案是什么: \[ Ans=C(a+b-2,a-1)\centerdot s(n-1,a ...

  9. thinkphp5 开启多语言

    一.配置点击打开链接1.开启语言包功能'lang_switch_on' => true,2.支持的语言列表'lang_list' => ['zh-cn','en-us'],二.语言定义(默 ...

  10. Spring学习(十二)-----Spring @PostConstruct和@PreDestroy实例

    实现 初始化方法和销毁方法3种方式: 实现标识接口 InitializingBean,DisposableBean(不推荐使用,耦合性太高) 设置bean属性 Init-method destroy- ...