本文主要解决的问题是:
一、一个服务端怎么处理多客户端的连接?
二、一个服务端怎么开启多个线程来处理所有客户端发来的消息?(换汤不换药,可以看这篇博客Qt封装一个类管理moveToThread( )正确的开启多线程、安全的退出线程的实例不管是开启多个客户端还是一个服务端开启多个线程都可以用此方法)这里我们还需要考虑弄一个全局的缓存区暂时来存放从客户端收到的消息,因为有时候服务端对消息的处理速度慢时,可能会造成数据丢失这些原因。
一些用到的函数等解释:
void incomingConnection ( int socketDescriptor ) | 当有新连接可用时,QTcpServer将调用此虚拟函数。注意:如果希望将传入连接作为另一个线程中的新QTcpSocket对象处理,则必须将socketDescriptor传递给另一个线程,在那里创建QTcpSocket对象并使用其setSocketDescriptor()方法。 |
一、一个服务端怎么处理多客户端的连接?
首先要知道在QT中有QTcpServer来进行TCP通信,每个客户端连接到服务端时,这个QTcpSever会给他们分配一个socket。
伪代码:
/************
1 创建一个QTcpSever
2 监听端口
3 QTcpSever有客户端连接上来是,会触发newConnection信号,或者incomingConnection虚函数
4 创建一个QList来存储QTcpSocket,后续需要用到每个客户端对应的socket时,在这个容器里取出就行了。
************/
1 在这里我用到了重写虚函数incomingConnection继承QTcpServer的方法来实现一个服务端如何处理多个客户端的连接,
2 当然也可以不用这种方法,直接在触发readReady信号时遍历容器即可,可以看本篇博客QT TCP服务端处理多个客户端发来的消息
2的这种方法比较容易看懂,但是1也很简单,现在我们来看1的方法怎么实现吧。
在这里我把QTcpSever和QTcpSocket的处理封装成两个类,比较有MVC的那种分层管理的想法;当然你也可以不用封装成两个类,封装成一个也行。
调用代码示例:如此我们便开启了一个可以处理多个客户端发来的消息的服务器了
SeverByTcp m_tcpSever = new SeverByTcp(this);
//开启tcp监听
if(!m_tcpSever->startSeverListen(sTcpPort))
{
return false;
}
下面时示例的详细代码,每一步都有说明。
A.继承QTcpServer的类SeverByTcp
.h
#ifndef SEVERBYTCP_H
#define SEVERBYTCP_H
#include
#include
#include
#include "tcpclientsocket.h"//这个类是自己继承QTcpSocket的,下个示例就会补充
class SeverByTcp : public QTcpServer
{
Q_OBJECT
public:
explicit SeverByTcp(QObject *parent = 0);
//开启监听
bool startSeverListen(const QString sHostPort);
// 当有新连接可用时,QTcpServer将调用此虚拟函数
void incomingConnection(int socketDescriptor);
signals:
public slots:
//处理服务器收到tcpsocket发过来的信息
void slotUpdateServer(QString, int);
//告诉服务器有客户端断开连接
void slotClientDisconnected(int);
private:
// 连接到服务器的客户端链表
QList m_tcpClientSocketList;
};
#endif // SEVERBYTCP_H
.cpp
#include "severbytcp.h"
SeverByTcp::SeverByTcp(QObject *parent) : QTcpServer(parent)
{
}
bool SeverByTcp::startSeverListen(const QString sHostPort)
{
//监听端口
if(listen(QHostAddress::Any, sHostPort.toInt()))
{
return true;
}
return false;
}
void SeverByTcp::incomingConnection(int socketDescriptor)
{
//只要有新的连接就生成一个新的通信套接字
TcpClientSocket *tcpClientSocket = new TcpClientSocket(this);
//将新创建的通信套接字描述符指定为参数socketdescriptor
tcpClientSocket->setSocketDescriptor(socketDescriptor);
//将这个套接字加入客户端套接字列表中
m_tcpClientSocketList.append(tcpClientSocket);
//接收到tcpclientsocket发送过来信息
connect(tcpClientSocket,SIGNAL(sigUpdateSever(QString,int)),this,SLOT(slotUpdateServer(QString, int)));
//处理客户端掉线
connect(tcpClientSocket,SIGNAL(sigClientDisconnect(int)),this,SLOT(slotClientDisconnected(int)));
}
void SeverByTcp::slotUpdateServer(QString sMsg, int iLength)
{
//这里的sMsg就是服务端从客户端收到的消息了
//可以在这里进行对消息的处理,你可以按照自己的需求来设计
}
void SeverByTcp::slotClientDisconnected(int iSocketDescriptor)
{
//如果有客户端断开连接, 就将列表中的套接字删除
for(int i = 0; i < m_tcpClientSocketList.count(); i++)
{
QTcpSocket *item = m_tcpClientSocketList.value(i);
if(item->socketDescriptor() == iSocketDescriptor)
{
m_tcpClientSocketList.removeAt(i);
return;
}
}
return;
}
B.继承QTcpSocket的类TcpClientSocket
.h
#ifndef TCPCLIENTSOCKET_H
#define TCPCLIENTSOCKET_H
#include
#include
class TcpClientSocket : public QTcpSocket
{
Q_OBJECT
public:
explicit TcpClientSocket(QObject *parent = 0);
signals:
// 通知服务器 客户端收到了消息
void sigUpdateSever(QString,int);
// 通知服务器 客户端掉线
void sigClientDisconnect(int);
public slots:
// 处理readyRead信号读取数据
void slotReceiveData();
// 用来处理客户端断开连接触发disconnected信号
void slotClientDisconnected();
private:
};
#endif // TCPCLIENTSOCKET_H
.cpp
#include "tcpclientsocket.h"
TcpClientSocket::TcpClientSocket(QObject *parent)
{
//客户端有消息传进来时会触发信号readyRead()
connect(this,SIGNAL(readyRead()),this,SLOT(slotReceiveData()));
//客户端断开连接时会触发信号disconnected()
connect(this,SIGNAL(disconnected()),this,SLOT(slotClientDisconnected()));
}
void TcpClientSocket::slotReceiveData()
{
int iMaxLength = 1024;
//QByteArray baArray = readAll();//把socket中的消息读出
QByteArray baArray = read(iMaxLength);设置长度大小是为了考虑粘包的问题,这里每次读出固定的长度即可
QString sMsg = baArray;
//通知QTcpSever socket收到客户端发来的消息了
emit sigUpdateSever(sMsg, iMaxLength);
}
void TcpClientSocket::slotClientDisconnected()
{
//通知服务器有客户端掉线
emit sigClientDisconnect(this->socketDescriptor());
}
2022/5/16补充:
姐妹们,上面那个一个服务端接收多个客户端的消息是没问题。but如果客户端发消息的时候,比如我一次for循环发多条的话,接收端那边显示的包数就和客户端那边对应不起来。
比如有两个客户端同时发消息,而每个客户端每次for循环发5条,那那么服务端那边只会显示收到2个包而不是10个!!
原因我也不知道(2022/5/23我觉得大概是这个原因注:write()函数和被触发的读的readyRead()信号并不是一一对应的,比如可能你客户端write了三次,服务端那边的readyRead可能才被触发一次.所以我们在读socket数据的时候要全部读完而不是等到readyRead被触发的时候才去读完全部的数据),下面这种方法可以解决:
在继承QTcpSocket的类接收消息那里加上一个循环即可,一定是在继承qtcpsocket类里面加,在继承QtcpServer那里不行,我试了。
2022/5/26补充:
服务端怎么发消息给客户端,两种情况:
1—— 如果你想发给固定的客户端,首先客户端那边就要绑定固定的ip和端口号了,接收端发的时候直接往这个socket里面发消息就好了,怎么发?建议看一下这篇博客(QT TCP简单的通信示例),里面有讲客户端怎么发消息服务端,同理,照猫画虎,服务端发消息给客户端也是这样发的,直接write就好了。
2——我们不想发给固定的客户端,只是想在多客户端的情况下,哪个客户端向我服务端发了消息,我就回给他。
在这里服务端接收消息的时候,会自动读取发送端的端口和ip地址。
我给大家思考了可能有的这几种情况:
1) 如果是服务端收到客户端每一条消息都要回应的话,发消息的那边又是多个客户端又是一次发不知一条消息的话。那肯定是在我5/16增加的内容那里每次都write回去。
2) 如果你是有界面的话,我们可以在SeverByTcp类里面增加个函数直接调用或者槽函数等等方式实现都可以。
void SeverByTcp::sendMsg()
{
for(int i=0;iwrite();
//可以获取这个socket对应的发送端的ip和端口,有用的话可以看看,比如识别哪个客户端发来的消息
int port = m_tcpClientSocketList.value(i)->peerPort();
QString sIp= m_tcpClientSocketList.value(i)->peerAddress().toString();
}
}
在TcpClientSocket里面也可以发消息,如下,
注:客户端那边要接收到服务端发来的消息,肯定是要connect readyRead信号的
//接收到服务器的信息就会触发readyRead信号
connect(m_tcpSocket,SIGNAL(readyRead()),this,SLOT(slotClientReadData()));
说一下,你们可以看到我上面服务端读取的时候,是有固定一个大小1024读取的。我这有做的原因是为了避免tcp粘包处理,我的客户端那边发消息的时候也是固定1024大小发的。当然这只是个减少tcp粘包问题的一个小投机方法,比较正确的方法应该是协议头+协议体方式那样去处理tcp粘包问题,但是那样太麻烦了,我这里也不需要,感兴趣的小伙伴可以自己去看看。
给大家展示一下,我做的示例,目前测了没啥问题,感兴趣的小伙伴可以下载源码看看(tcp/udp多客户端通信-C++文档类资源-CSDN文库)有设置4.9元的下载费,里面有的内容:
1 实现TCP/UDP处理多客户端发消息的处理,当然重点是tcp,这里面包含了我上面说的那两种方式
2 可以实现定时发送,发包数等
3 界面实时显示各种情况
4 xml解析与生成的方式
5 可以切换tcp/udp通信方式
……
大概就是视频这样,测试开启100个线程也没啥问题,嘎嘎嘎当然有不足,但整体也还可以。你们看到的那个字体显示不完全的原因是因为我展示的平台不一样,你们可以自己调一下就OK了。
不售后哦,我想大家都想通过自己的能力来提升自己吧,不要想着有人会帮你做完everthing,但是我看到能不是很浪费时间的话我会解答的
tcp