您的位置:首页 > 理论基础 > 计算机网络

QTcpServer多线程实现

2016-08-20 16:58 363 查看
转自:http://www.dushibaiyu.com/2013/12/qtcpserver%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%AE%9E%E7%8E%B0.html

实现时分别继承QTcpServer和QTcpScoket实现出自己需要的类。

继承QTcpServer为每个客户端连接时分配线程,并接受处理tcpScoket的信号和槽、、还有发送信息,储存连接信息等。

继承QTcpScoket为处理通信数据和增加信号的参数,以便和tcpServer更好的配合。

首先是继承并重写QTcpServer的incomingConnection函数去自己实现tcpsocket连接的建立和分配。

其文档的默认描述为:

This virtual function is called by QTcpServer when a new connection is available.
The socketDescriptor argument is the native socket descriptor for the accepted connection.

The base implementation creates a QTcpSocket,sets the socket descriptor and
then stores the QTcpSocket in an internal list of pending connections. Finally newConnection() is emitted.

Reimplement this function to alter the server’s behavior when a connection is
available.

If this server is using QNetworkProxy then the socketDescriptor may not be usable
with native socket functions,and should only be used with QTcpSocket::setSocketDescriptor().

Note: If you want to handle an incoming connection as a new QTcpSocket object
in another thread you have to pass the socketDescriptor to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.

译文(谷歌翻译和自己简单的更正):

当QTcpServer有一个新的连接时这个虚函数被调用。该socketDescriptor参数是用于接受连接的本地套接字描述符。

该函数会创建一个QTcpSocket,并设置套接字描述符为socketDescriptor,然后存储QTcpSocket在挂起连接的内部清单。最后newConnection()被发射。

重新实现这个函数来改变服务器的行为,当一个连接可用。

如果该服务器使用QNetworkProxy那么socketDescriptor可能无法与原生socket函数使用,并且只能用QTcpSocket:: setSocketDescriptor()中使用。

注意:如果你想处理在另一个线程一个新的QTcpSocket对象传入连接,您必须将socketDescriptor传递给其他线程,并创建了QTcpSocket对象存在并使用其setSocketDescriptor()方法。

所以我们必须先重写这个函数:

下面先附上继承QTcpServer的自己的类声明,代码注释个人以为挺详细了:

01
//继承QTCPSERVER以实现多线程TCPscoket的服务器。
02
class
MyTcpServer
:
public
QTcpServer
03
{
04
Q_OBJECT
05
public
:
06
explicit
MyTcpServer(QObject
*parent =0);
07
~MyTcpServer();
08
signals:
09
void
connectClient(
const
int
,
const
QString
& ,
const
quint16
);
//发送新用户连接信息
10
void
readData(
const
int
,
const
QString
&,quint16,
const
QByteArray
&);
//发送获得用户发过来的数据
11
void
sockDisConnect(
const
int
,
const
QString
&,
const
quint16
);
//断开连接的用户信息
12
void
sentData(
const
QByteArray
&,
const
int
);
//向scoket发送消息
13
public
slots:
14
void
setData(
const
QByteArray
& data,
const
int
handle);
//想用户发送消息
15
void
readDataSlot(
const
int
,
const
QString
&,
const
quint16,
const
QByteArray
&);
//发送获得用户发过来的数据
16
void
sockDisConnectSlot(
int
handle,
QString ip,quint16 prot);
//断开连接的用户信息
17
18
protected
:
19
void
incomingConnection(qintptr
socketDescriptor);
//覆盖已获取多线程
20
private
:
21
QMap<
int
,myTcpSocket
*> * tcpClient;
22
};
接着是我们重写void QTcpServer::incomingConnection(qintptr socketDescriptor)的实现:

01
void
MyTcpServer::incomingConnection(qintptr
socketDescriptor)
02
{
03
myTcpSocket
* tcpTemp =
new
myTcpSocket(socketDescriptor);
04
QThread
*
thread
=
new
QThread(tcpTemp);
//把线程的父类设为连接的,防止内存泄漏
05
//可以信号连接信号的,我要捕捉线程ID就独立出来函数了,使用中还是直接连接信号效率应该有优势
06
connect(tcpTemp,&myTcpSocket::readData,
this
,&MyTcpServer::readDataSlot);
//接受到数据
07
connect(tcpTemp,&myTcpSocket::sockDisConnect,
this
,&MyTcpServer::sockDisConnectSlot);
//断开连接的处理,从列表移除,并释放断开的Tcpsocket
08
connect(
this
,&MyTcpServer::sentData,tcpTemp,&myTcpSocket::sentData);
//发送数据
09
connect(tcpTemp,&myTcpSocket::disconnected,
thread
,&QThread::quit);
//断开连接时线程退出
10
tcpTemp->moveToThread(
thread
);
//把tcp类移动到新的线程
11
thread
->start();
//线程开始运行
12
13
tcpClient->insert(socketDescriptor,tcpTemp);
//插入到连接信息中
14
qDebug()
<<
"incomingConnection
THREAD IS:"
<<QThread::currentThreadId();
15
//发送连接信号
16
emit
connectClient(tcpTemp->socketDescriptor(),tcpTemp->peerAddress().toString(),tcpTemp->peerPort());
17
18
}
用moveToThread来处理移动到新的线程。

其他的非重要的函数就不一一列出,但是别忘记在断开连接的槽中释放连接:

01
void
MyTcpServer::setData(
const
QByteArray
&data,
const
int
handle)
02
{
03
emit
sentData(data,handle);
04
}
05
06
void
MyTcpServer::sockDisConnectSlot(
int
handle,
QString ip,quint16 prot)
07
{
08
qDebug()
<<
"MyTcpServer::sockDisConnectSlot
thread is:"
<<
QThread::currentThreadId();
09
10
myTcpSocket
* tcp =tcpClient->value(handle);
11
tcpClient->
remove
(handle);
//连接管理中移除断开连接的socket
12
delete
tcp;
//释放断开连接的资源、、子对象线程也会释放
13
emit
sockDisConnect(handle,ip,prot);
14
}
其次就是继承的QTcpSocket的声明,直接上代码把:

01
//继承QTcpSocket以处理接收到的数据
02
class
myTcpSocket
:
public
QTcpSocket
03
{
04
Q_OBJECT
05
public
:
06
explicit
myTcpSocket(qintptr
socketDescriptor,QObject *parent =0);
07
08
signals:
09
void
readData(
const
int
,
const
QString
&,
const
quint16,
const
QByteArray
&);
//发送获得用户发过来的数据
10
void
sockDisConnect(
const
int
,
const
QString
&,
const
quint16
);
//断开连接的用户信息
11
public
slots:
12
void
thisReadData();
//处理接收到的数据
13
void
sentData(
const
QByteArray
& ,
const
int
);
//发送信号的槽
14
private
:
15
qintptr
socketID;
//保存id,==
this->socketDescriptor();但是this->socketDescriptor()客户端断开会被释放,
16
//断开信号用this->socketDescriptor()得不到正确值
17
};
这个实现其实更简单了、、、就把主要的信号槽连接部分附上把,还有发送数据需要注意下,我是用的Qt,其中信号槽用的新语法,而且配合的C++11的lambda表达式,你如果不清楚C++11,你最好先去看下C++11的文档。

01
myTcpSocket::myTcpSocket(qintptr
socketDescriptor,QObject *parent) :
02
QTcpSocket(parent),socketID(socketDescriptor)
03
{
04
this
->setSocketDescriptor(socketDescriptor);
05
//转换系统信号到我们需要发送的数据、、直接用lambda表达式了,qdebug是输出线程信息
06
connect(
this
,&myTcpSocket::readyRead,
this
,&myTcpSocket::thisReadData);
//连接到收到数据的处理函数
07
connect(
this
,&myTcpSocket::readyRead,
//转换收到的信息,发送信号
08
[
this
](){
09
qDebug()
<<
"myTcpSocket::myTcpSocket
lambda readData thread is:"
<<
QThread::currentThreadId(); emit readData(socketID,
this
->peerAddress().toString(),
this
->peerPort()
,
this
->readAll());
//发送用户发过来的数据
10
});
11
connect(
this
,&myTcpSocket::disconnected,
//断开连接的信号转换
12
[
this
](){
13
qDebug()
<<
"myTcpSocket::myTcpSocket
lambda sockDisConnect thread is:"
<<
QThread::currentThreadId(); emit sockDisConnect(socketID,
this
->peerAddress().toString(),
this
->peerPort());
//发送断开连接的用户信息
14
});
15
16
qDebug()
<<
this
->socketDescriptor()
<<
"
"
<<
this
->peerAddress().toString()
17
<<
"
"
<<
this
->peerPort()
<<
"myTcpSocket::myTcpSocket
thread is "
<<QThread::currentThreadId();
18
}
19
20
void
myTcpSocket::sentData(
const
QByteArray
&data,
const
int
id)
21
{
22
//如果是服务器判断好,直接调用write会出现跨线程的现象,所以服务器用广播,每个连接判断是否是自己要发送的信息。
23
if
(id
==socketID)
//判断是否是此socket的信息
24
{
25
qDebug()
<<
"myTcpSocket::sentData"
<<
QThread::currentThreadId();
26
write(data);
27
}
28
}
整篇代码中出现了n个qDebug() <<,这个是我为了查看运行所在的线程所设,实际应用中这些都没用的、、你自己删除把、、自己测试的例子和源码我还是保留了、、毕竟时间长了都忘得,留着那天一看就一目了然的、、

这个每个连接分配一个线程,连接太多很耗资源的、、您可以自己更改下,把多个连接移到一个线程,但是那样,你需要保存线程信息,更要小心线程的分配和释放时、、可以自己做下、、我也欢迎大家来探讨、、

最新更新:添加线程管理类(应该算个线程池),单例类。可预先设置线程数或者每个线程处理多少连接。原来的代码主要变动在新建断开连接处更新了、、详细请见代码。

全部的代码测试例子,服务端和客户端,我传到了我的github上了,附上项目地址:https://github.com/dushibaiyu/QtTcpThreadServer
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: