QTCreatot做一个多线程服务器,通信有问题
QTCreator 做一个多线程TCP服务器,客户端以前别人就做好了,并定制了通信协议,现在我要用QT做一个服务器,要求能同时和多个客户机按通信协议通信。遇到了点问题,我调了很久都没解决,大家帮我看看。
通信协议是:
1.服务器向客户机发送命令(GET);
2.客户机发送START确认开始通信;
3.服务器发送AGAIN允许客户机发送;
4.接收客户机发送过来的信息并保存到缓冲区(QString)中;
5.检索缓冲区有没有客户机发送过来的结束命令(FINISH);
6.有:发送接收成功标志(SUCCESS)结束通信,存文件;
7.没有:转3;
如此往复直到检索到缓冲区中有客户机发过来的结束标志。
服务器要求能同时向多个客户机发送GET命令,并发的接收客户机发过来的信息,并在收到结束标志后将收到的信息存文件,信息不能发生串扰。
我的思路:
1.利用QT的多线程;
2.并不是为每个连接上的用户开启一个线程,而是为每个需要通信的客户端开启一个线程;
3.程序有GUI界面,每当有一个客户机连接到服务器以后在GUI上显示出来,并提供复选框,由用户勾选需要通信的客户端,并通过点击Get按钮向所选择的客户端发送GET命令,进行通信和信息处理;
问题是:
当我向多个客户发送GET的时候,程序偶尔会崩溃,偶尔成功但是保存的数据发生串扰(比如本来是客户1的数据却保存到了客户2的文件中)。我调了很久,没解决。
在线程的run()函数中:connect(sockfd, SIGNAL(readyRead()), this,SLOT(recvData()),Qt::DirectConnection);指定为Qt::DirectConnectio,那么recvData(),是否是在子线程之中执行?但是如果我指定为Qt::BlockingQueuedConnection程序通信依然存在问题。请问大家我的设计思想有没有问题,实现方法有没有问题。
主要代码如下,另附上源码包,http://download.csdn.net/detail/toney_ho/4058244
,大家可以下载下来调试运行一下,看是哪儿的问题,可能下载下来在QTCreator中容易看清楚点。
可能程序较长,大家耐心看一下共同进步。
- C/C++ code
//tcpserver.h#ifndef TCPSERVER_H#define TCPSERVER_H#include "tcpthread.h"#include <QTcpServer>class tcpserver : public QTcpServer{ Q_OBJECTpublic: explicit tcpserver(QObject *parent = 0); //QList<int> clientDescriptorList;signals: void newRow(int); void displayInfo(int,QString, int); void updateBar(int, qint64); void signal_send_command(int,int);public slots: void incomingConnection(int socketDescriptor); void slot_send_command(int,int);};#endif//tcpthread.h#ifndef TCPTHREAD_H#define TCPTHREAD_H#include <QThread>#include <QTcpSocket>#include <QtNetwork>#define SEND_AGIAN 2#define FOUND_FINISH 3#define SEND_SUCCESS 4#define SEND_GET 10#define FOUND_START 11#define TEST_OTHER 98#define DISPLAY_BUFFER 97#define RECV_FILE_SUCCESS 99#define SAVE_FILE_SUCCESS 100#define GET "GET"#define START "START"#define AGAIN "AGIAN"#define FINISH "FINISH"#define SUCCESS "SUCCESS"class QFile;class QTcpSocket;class TcpThread : public QThread{ Q_OBJECTpublic: TcpThread(int socketDescriptor,int command, QObject *parent = 0); void run(); QTcpSocket *sockfd; int socketDescriptor; int command;signals: void error(QTcpSocket::SocketError socketError); void displayInfo(int,QString, int); void updateBar(int, qint64);public slots: int processBuffer(QString ); void recvData();};#endif // TCPTHREAD_H//widget.h#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include "tcpthread.h"#include "tcpserver.h"class QDialogButtonBox;class QTcpSocket;namespace Ui { class Widget;}class Widget : public QWidget{ Q_OBJECTpublic: explicit Widget(QWidget *parent = 0); ~Widget(); TcpThread *thread ;private: Ui::Widget *ui; tcpserver tcpServer; QTime *currenttime;private slots: void on_pushButtonClearLog_clicked(); void on_pushButtonSaveLog_clicked(); void on_quitButton_clicked(); void on_pushButtonGET_clicked(); void on_OkButton_clicked(); void addNewRow(int); void displayInfo(int,QString, int); void updateBar(int, qint64);signals: void signal_send_command(int, int);};#endif // WIDGET_H//tcpserver.cpp#include "tcpserver.h"//构造函数tcpserver::tcpserver(QObject *parent) : QTcpServer(parent){}//重新定义了incomingConnection这个虚函数,//开辟一个新的tcpsocket线程,从TcpServer获得socketDescriptor,//并完成相应的信号连接void tcpserver::incomingConnection(int socketDescriptor){ qDebug() <<socketDescriptor; //clientDescriptorList.append(socketDescriptor); emit newRow(socketDescriptor); //display new client connect(this,SIGNAL(signal_send_command(int ,int)),this,SLOT(slot_send_command(int ,int)));}void tcpserver::slot_send_command(int sockDescriptor, int cmd){ TcpThread *thread = new TcpThread(sockDescriptor,cmd, this); thread->start(); connect(thread,SIGNAL(finished()),this,SLOT(deleteLater())); connect(thread,SIGNAL(displayInfo(int,QString,int)),this,SIGNAL(displayInfo(int,QString,int))); connect(thread,SIGNAL(updateBar(int,qint64)),this,SIGNAL(updateBar(int,qint64)));}//tcpthread.cpp#include "tcpthread.h"#include <QtGui>#include <QtNetwork>//构造函数完成简单的赋值/TcpThread::TcpThread(int socketDescriptor,int command, QObject *parent): QThread(parent),socketDescriptor(socketDescriptor),command(command){}void TcpThread::run(){ sockfd = new QTcpSocket; if (!sockfd->setSocketDescriptor(socketDescriptor)) { emit error(sockfd->error()); return; } sockfd -> write(GET); emit this->displayInfo(socketDescriptor,sockfd->peerAddress().toString(), SEND_GET); connect(sockfd, SIGNAL(readyRead()), this,SLOT(recvData()),Qt::DirectConnection); exec();}void TcpThread::recvData() //接收数据{ static qint64 bytesReceived = 0 ; //收到的总字节 static QString buffer = ""; //数据缓冲区 qint64 available = 0; if ( ( available = sockfd ->bytesAvailable() ) > 0 ) { bytesReceived += available; buffer.append(sockfd ->readAll()); //更新进度条 emit this->updateBar(socketDescriptor, bytesReceived); //display buffer emit this->displayInfo(socketDescriptor,buffer, DISPLAY_BUFFER); if ( processBuffer(buffer) == 1) { emit this->updateBar(socketDescriptor, 100); //更新进度条 goto GOTO_SEND_SUCCESS; } goto GOTO_SEND_AGAIN; GOTO_SEND_SUCCESS: //send SUCCESS sockfd -> write(SUCCESS); emit this-> displayInfo(socketDescriptor,sockfd->peerAddress().toString(), SEND_SUCCESS); return; GOTO_SEND_AGAIN: //send AGIAN emit this->displayInfo(socketDescriptor,sockfd->peerAddress().toString(), SEND_AGIAN); if (-1 == sockfd -> write(AGAIN)) { qDebug() << "write error"; return; } }}int TcpThread::processBuffer(QString buffer){ static int END_FLAG_FINISH = 0 ; static int positionSTART = 0; static int positionFINISH = 0; static QHostAddress fileName ; static QFile *localFile ; static QString fileBuffer = ""; //file buffer END_FLAG_FINISH = 0; if (-1 != (positionSTART = buffer.indexOf(START)) ) //found START { if (-1 != (positionFINISH = buffer.indexOf(FINISH))) //found FINISH { emit this->displayInfo(socketDescriptor,sockfd->peerAddress().toString(), FOUND_FINISH); END_FLAG_FINISH = 1; fileBuffer = buffer.mid(positionSTART+36,positionFINISH -positionSTART - 36); fileName = sockfd->peerAddress(); quint16 port = sockfd->peerPort(); localFile = new QFile(fileName.toString()+(tr(".%1").arg(port))); //用户端的IP地址作为保存文件名 if(!localFile->open(QFile::WriteOnly)) { qDebug() << "open file error!"; return -1; } QByteArray filetemp = fileBuffer.toLatin1(); if ( -1 == ( localFile->write(filetemp.data(),qstrlen(filetemp.data())))) { qDebug() <<"write file error!"; } localFile->close(); fileBuffer.clear(); emit this->displayInfo(socketDescriptor,sockfd->peerAddress().toString(), SAVE_FILE_SUCCESS); return END_FLAG_FINISH; } } return END_FLAG_FINISH;}
[解决办法]
这个 goto语句一般不用的,你得意思是不是把所有客户端的信息都存到一起了 然后在存起来?我觉得多线程的时候设定的缓存puff多个线程同时写的时候会崩溃,觉得可能是这个缓存没设计好
[解决办法]
参照一下官方的例子,
void FortuneServer::incomingConnection(int socketDescriptor)
{
QString fortune = fortunes.at(qrand() % fortunes.size());
FortuneThread *thread = new FortuneThread(socketDescriptor, fortune, this);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
官方的例子在examples\network\threadedfortuneserver
这里为每一个连接启动一个新的线程。
[解决办法]
是你的static变量不正确使用导致,你将buffer定义成thread类的成员变量,不要用static变量,就不会造成数据串扰的问题了。
已测试过。
如下方法
qint64 bytesReceived = 0 ;
QString strtemp;
strtemp.sprintf("%d",socketDescriptor);
qint64 available = 0;
available = sockfd->bytesAvailable();
if(available>0)
{
buffer.append(sockfd->readAll());
buffer.append(strtemp);
qDebug()<<buffer;
}
显示结果:
"15608156081560815608156081560815608156081560815608156081560815608156081560815608156081560815608156081560815608156081560815608156081560815608156081560815608"
"05548055480554805548055480554805548055480554805548055480554805548055480554805548055480554805548055480554805548055480554805548055480554805548055480554805548"
我发送的数据为一个客户端发0,一个客户端发1
个人建议,尽量不要使用自己不是很熟练的编程技巧。
[解决办法]
你定义一个类的成员变量bytesReceived ,然后在构造函数中将bytesReceived = 0。就如同你的socketDescriptor,command一样。
我明白你的意思是想要bytesReceived变量记录收到的记录长度,成员变量在类中作用域是整个类而不是只局限于recvData函数中。
以下内容摘自网上:
全局变量在类实例化一个对象以后,在每个对象内都会产生一个对象的变量;
而静态变量在类实例化一个对象以后,他是属于类的变量,并不会随着对象的增加而增加,任何方式改变了类的静态变量后,其他的对象访问到(通过一定方式)的静态变量都是改变以后的值。
所以说,静态变量只是类的变量!
经试验也证明,在实例化二次时(二个客户端连接),你的static buffer内容会串在一起。
可能我上一帖的语气欠妥,抱歉:)
[解决办法]
C++类的成员函数中使用静态变量的陷阱
C++类的成员函数中,使用static变量时,该变量是放在类存储区,而不是对象存储区,所以所有该类的对象共享该静态变量。
如以下代码:
#include <stdio.h>
#include <iostream>
using namespace std;
class Cs
{
public:
void t(void)
{
static i=0;
cout<<i++<<endl;
}
};
int main(int argc, char* argv[])
{
Cs c1;
Cs c2;
Cs c3;
c1.t();
c2.t();
c3.t();
return 0;
}
最后输出的数字不是0,而是2
[解决办法]
如何才能达到向某个或者某几个客户端发送命令的目的呢?
相当于要在界面中控制线程的工作状态,从这个思路来做,可以将thread类做成tcpserver类的public成员变量,然后在界面中tcpserver->thread->get()这样的方式来实现看看,当然也可以用类似信号的方式。