实习的时候要用上QT开发,之前没用过,再看我之前的博客,我发现我什么都用过一点,却什么都不精。不过之前春招告诉我,JAVA这条路真难走,竞争太大了。真不如安心学C/C++,正好这次实习QT也是用的C++的,正好重新学习一下。
这次实习采用小组合作形式,内容是模拟一个只能家居系统,我负责TCP通信这一块(PC客户端与硬件客户端通信,中间有一个服务器),之前其实没有怎么用过,其实也算新手上路,也遇到挺多问题的。
我不确定自己说的对不对(下同),但是我这次遇到的问题主要是服务器要对两个客户端进行区分,而我没有发现socket有明确的标记(也可能仅仅是我没发现)可以用来区分(ip地址是会随局域网变化而变化的,端口是人为设定的,也没发现可以人为设定一些标记)。所以我就只能用蠢方法,就是一旦客户端与服务器成功连接,那么客户端需要发送一个“信息”,告诉服务器自己是谁。从而进行区分。
还有一个问题是数据转接问题,此时先假设通过上面方法连接了两个客户端,一个为“A”,一个为“B”,首先“A”向服务器发送了一段信息,这段信息需要转发给“B”,注意这时“A“和”B”是两个不同的对象,他们之间的信息是不互通的,所以是不能直接将得到的信息转发给“B”,我首先想到的是“static”变量,但是我发现这里的“static”和Java里的不一样,他仅仅是在这个类中的所有该类的对象共享该变量(具体百度),并不能在别的类中也共享变量,所以这个方案行不通。第二个方案是用extern关键字,这个我之前没用过,用不通,也就放弃了。最后还是用Qt里很强大的信号槽机制去解决。
connection.h:
#ifndef CONNECTION_H
#define CONNECTION_H
#include <QObject>
#include <QTcpSocket>
class Connection : public QObject
{
Q_OBJECT
public:
explicit Connection(QTcpSocket *pSocket,QObject *parent = 0);
~Connection();
bool signal;//标记该socket连接的是"A"还是"B",true表示"A",false表示"B"
QTcpSocket *m_socket;//默认收发数据的socket
signals:
void SignalDisconnect();
void SignalReadSend(QByteArray array,bool sig);
void SignalRemoveThis();
public slots:
void slotDisconnect();
void slotReadData();
void slotVerifyReadData();
void readyToSend();
private:
QByteArray m_buffer;
};
#endif // CONNECTION_H
serverwidget.h
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include <QWidget>
#include <QTcpServer>
#include "connection.h"
namespace Ui {
class ServerWidget;
}
class ServerWidget : public QWidget
{
Q_OBJECT
public:
explicit ServerWidget(QWidget *parent = 0);
~ServerWidget();
signals:
void readSend();
private slots:
void slotNewConnect();
void on_pbNetConn_clicked();
void slotDisconnect();
void send();
void setByte(QByteArray arr, bool sig);
void removeSocket();
private:
Ui::ServerWidget *ui;
QTcpServer m_tcpServer;//定义一个服务器对象
QByteArray byte;
bool signal;//用于判断数据要发给哪一个客户端,true表示发给“A”客户端,否则“B”
QVector<Connection *> m_List;//存放当前连接中给的客户端
QHostAddress address;
};
#endif // SERVERWIDGET_H
connection.cpp
#include "connection.h"
#include <QDebug>
Connection::Connection(QTcpSocket *pSocket
, QObject *parent) : QObject(parent),m_socket(pSocket)
{
connect(m_socket,&QTcpSocket::readyRead,this,
&Connection::slotVerifyReadData);//首先接收一个身份信息,然后触发验证函数slotVerifyReadData
connect(m_socket,&QTcpSocket::disconnected,this,
&Connection::slotDisconnect);//当客户端断开连接时触发slotDisconnect发送信号源SignalDisconnect
}
Connection::~Connection()
{
if(m_socket->state()==QTcpSocket::ConnectedState)
m_socket->close();
}
//当该客户端断开连接时触发
void Connection::slotDisconnect()
{
qDebug()<<7;
emit SignalDisconnect();
}
//读取接收到的数据
void Connection::slotReadData()
{
qDebug()<<8;
if(m_socket->bytesAvailable()){
m_buffer=m_socket->readAll();
readyToSend();
}
}
//用于验证是否为合法客户端
void Connection::slotVerifyReadData()
{
qDebug()<<9;
QByteArray arr;
if(m_socket->bytesAvailable()){
arr=m_socket->readAll();
if(arr=="A"){//如果发送的信息是“A”,则判断它为"A"客户端
disconnect(m_socket,&QTcpSocket::readyRead,this,
&Connection::slotVerifyReadData);//解除绑定,防止干扰之后的获取数据
connect(m_socket,&QTcpSocket::readyRead,this,
&Connection::slotReadData);//绑定一个获取数据函数,当有数据发过来时触发
signal=true;//代表着“A”客户端
}
else if (arr=="B"){//如果发送的信息是“B”,则判断它为"B"客户端
disconnect(m_socket,&QTcpSocket::readyRead,this,
&Connection::slotVerifyReadData);//解除绑定,防止干扰之后的获取数据
connect(m_socket,&QTcpSocket::readyRead,
this,&Connection::slotReadData);//绑定一个获取数据函数,当有数据发过来时触发
signal=false;//代表着“B”客户端
}
else{//如果收到的信息不合法,那就发送信号源removeThis,将其移除
emit SignalRemoveThis();
}
}
}
//接收到数据后就触发,告诉服务器可以进行数据转发了
void Connection::readyToSend()
{
qDebug()<<10;
//当然可以在这里加一些条件,比如数据要包含一定过得帧头帧尾,才能进行发送
emit SignalReadSend(m_buffer,signal);
m_buffer.clear();
}
serverwidget.cpp
#include "serverwidget.h"
#include "ui_serverwidget.h"
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
connect(&m_tcpServer,&QTcpServer::newConnection,
this,&ServerWidget::slotNewConnect);//当有新客户端连接时触发
connect(this,&ServerWidget::readSend,this,&ServerWidget::send);//需要进行数据转发时触发
}
ServerWidget::~ServerWidget()
{
delete ui;
}
//开启服务器监听连接,这是的第一步
void ServerWidget::on_pbNetConn_clicked()
{ qDebug()<<1;
if(!m_tcpServer.isListening()){
if(m_tcpServer.listen(QHostAddress::Any
,ui->ldtPort->text().toInt())){
ui->pbNetConn->setText("关闭");
ui->ldtPort->setReadOnly(true);//设置为只读,不允许修改
}
}else{
m_tcpServer.close();
ui->pbNetConn->setText("开启");
ui->ldtPort->setReadOnly(false);
}
}
//用于移除非法客户端
void ServerWidget::removeSocket()
{
qDebug()<<2;
int a=m_List.size()-1;
while(1){
if(m_List.at(a)->m_socket->state()==QTcpSocket::ConnectedState){
m_List.at(a)->m_socket->close();
break;
}
else if(NULL == m_List.at(a))
a--;
}
}
//将新的连接添加到m_List中,表明当前服务器连接的客户端
void ServerWidget::slotNewConnect()
{ qDebug()<<3;
if(m_tcpServer.hasPendingConnections()){
QTcpSocket *newSocket=m_tcpServer.nextPendingConnection();//即将进行连接的客户端
Connection *conn=new Connection(newSocket,this);//每一个socket对象与一个connection对象绑定
m_List.append(conn);//存放到list中
connect(conn,&Connection::SignalRemoveThis,this,
&ServerWidget::removeSocket);//当客户端没有发来正确的身份验证信息,就触发removeSocket将它从list中移除
connect(conn,&Connection::SignalDisconnect,this,
&ServerWidget::slotDisconnect);//当客户端断开连接时触发slotDisconnect,将它从list中移除
connect(conn,&Connection::SignalReadSend,this,
&ServerWidget::setByte);//当接收到信息并且能够进行转发时触发,发给指定的客户端
address =newSocket->peerAddress();//存放ip地址
ui->textEdit->append(address.toString());//将ip地址显示在界面上
}
}
//客户端断开连接时触发
void ServerWidget::slotDisconnect()
{
qDebug()<<4;
for(int i=0;i<m_List.size();i++){
if(m_List.at(i) == (Connection *)sender()){
if(m_List.at(i)->signal==true)
ui->textEdit_2->append("A客户端断开连接");
else if(m_List.at(i)->signal==false)
ui->textEdit_2->append("B客户端断开连接");
else
ui->textEdit_2->append("无效客户端断开连接");
m_List.remove(i);
break;
}
}
}
void ServerWidget::send()
{
qDebug()<<5;
if(signal==false){//false表示数据是由"B"客户端发来的,所以要发给“A”
for(int i=0;i<m_List.size();i++){//因为存放时是非定点存放,所以要找到“A”客户端只能便利,当然我觉得这里肯定可以优化
if(m_List.at(i)->signal==true){
if(m_List.at(i)->m_socket->state()==QAbstractSocket::ConnectedState){
m_List.at(i)->m_socket->write(byte);
}
return;
}
}
}
else{//发给"B"
for(int i=0;i<m_List.size();i++){
if(m_List.at(i)->signal==false){
if(m_List.at(i)->m_socket->state()==QAbstractSocket::ConnectedState){
m_List.at(i)->m_socket->write(byte);
}
return;
}
}
}
}
void ServerWidget::setByte(QByteArray arr, bool sig)
{
qDebug()<<6;
byte=arr;
signal=sig;
emit readSend();
}
main.cpp
#include "serverwidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ServerWidget w;
w.show();
return a.exec();
}
注释很详细了,应该能看懂。提醒一下,记得在.pro文件里添加 network,并保存,才能使用tcp模块。
给一个效果图: