在汇文培训老师给讲了这个例子。讲的挺好的
Qt编写聊天服务器与客户端主要用到下面两个类:
- QTcpSocket --- 处理连接的
- QTcpServer --- 处理服务器,对接入进行响应,创建每个链接的QTcpSocket实例
编写网络程序需要在 .pro 文件中加上 network,如下
QT += core gui network
1.客户端的编写
客户端需要做的事:
- 获取服务器的主机ip和端口(port)
- 链接主机(connectToHost)
- 链接状态下等待一些信号(signal)的产生并作出相应的回应(slot)
主要等待的信号由一下几个:
void connected() void disconnected() void error(QAbstractSocket::SocketError socketError) void readyRead()
并为这几个信号写相应的槽函数对该情况作出处理
以下为我的具体实现代码:
我的ui界面是这样的:
1 #ifndef TCPCLIENT_H 2 #define TCPCLIENT_H 3 4 #include <QWidget> 5 #include <QMessageBox> 6 #include <QTcpSocket> 7 #include <QDebug> 8 #include <QHostAddress> 9 #include <QTextStream> 10 11 namespace Ui { 12 class TcpClient; 13 } 14 15 class TcpClient : public QWidget 16 { 17 Q_OBJECT 18 19 public: 20 explicit TcpClient(QWidget *parent = 0); 21 ~TcpClient(); 22 23 private slots: 24 void on_connectPushButton_clicked(); //connect按键槽函数 25 26 void slotConnect(); 27 void slotErr(QAbstractSocket::SocketError err); 28 void slotDisconnect(); 29 void slotReadData(); 30 31 void on_sendPushButton_clicked(); //send按键槽函数 32 void on_sendLineEdit_returnPressed(); //sendLineEdit的回车槽 33 34 private: 35 Ui::TcpClient *ui; 36 QTcpSocket *socket; //客户端的文件描述符 37 bool isConnect; //connect按键的状态变换值 38 }; 39 40 #endif // TCPCLIENT_H
1 #include "tcpclient.h" 2 #include "ui_tcpclient.h" 3 4 TcpClient::TcpClient(QWidget *parent) : 5 QWidget(parent), 6 ui(new Ui::TcpClient) 7 { 8 ui->setupUi(this); 9 isConnect = true; 10 } 11 12 TcpClient::~TcpClient() 13 { 14 delete ui; 15 } 16 17 void TcpClient::on_connectPushButton_clicked() 18 { 19 QString hostip; 20 quint16 port; 21 if(isConnect) 22 { 23 /*判断ip和端口是否填写,并获取该ip和端口*/ 24 if(ui->ipLineEdit->text().isEmpty()) 25 { 26 QMessageBox::information(this,"ip","The ip is empty,please input a ip"); 27 return ; 28 } 29 hostip = ui->ipLineEdit->text(); 30 if(ui->portLineEdit->text().isEmpty()) 31 { 32 QMessageBox::information(this,"port","The port is empty,please input a port"); 33 return ; 34 } 35 port = ui->portLineEdit->text().toShort(); 36 37 /*连接服务器,并在这之前连接几个必要的信号*/ 38 socket = new QTcpSocket; 39 //已连接信号 40 connect(socket,SIGNAL(connected()),this,SLOT(slotConnect())); 41 //连接出现错误信号 42 connect(socket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(slotErr(QAbstractSocket::SocketError))); 43 //连接断开信号 44 connect(socket,SIGNAL(disconnected()),this,SLOT(slotDisconnect())); 45 //准备读取数据信号 46 connect(socket,SIGNAL(readyRead()),this,SLOT(slotReadData())); 47 socket->connectToHost(QHostAddress(hostip),port); 48 } 49 else 50 { 51 socket->disconnectFromHost(); 52 ui->connectPushButton->setText("connect"); 53 isConnect = true; 54 } 55 } 56 57 //槽函数定义 58 void TcpClient::slotConnect() 59 { 60 QMessageBox::information(this,"connect","host connect sucess"); 61 ui->ipLineEdit->setEnabled(false); 62 ui->portLineEdit->setEnabled(false); 63 ui->connectPushButton->setText("disconnect"); 64 isConnect = false; 65 } 66 67 void TcpClient::slotErr(QAbstractSocket::SocketError err) 68 { 69 qDebug("error is %d",err); 70 ui->connectPushButton->setText("connect"); 71 } 72 73 void TcpClient::slotDisconnect() 74 { 75 QMessageBox::information(this,"disconnect","host disconnect sucess"); 76 ui->ipLineEdit->setEnabled(true); 77 ui->portLineEdit->setEnabled(true); 78 ui->connectPushButton->setText("connect"); 79 isConnect = true; 80 } 81 82 void TcpClient::slotReadData() 83 { 84 qDebug()<<"have data"; 85 if(socket->bytesAvailable()>0) 86 { 87 QString msg; 88 QByteArray ba; 89 ba.resize(socket->bytesAvailable()); 90 socket->read(ba.data(),ba.size()); 91 msg = QString(ba); 92 93 // QTextStream out(socket); 94 // QString msg; 95 // out >> msg; 96 ui->msgListWidget->addItem(msg); 97 } 98 } 99 100 101 void TcpClient::on_sendPushButton_clicked() 102 { 103 QTextStream in(socket); 104 in << ui->sendLineEdit->text(); 105 ui->sendLineEdit->clear(); 106 } 107 108 109 void TcpClient::on_sendLineEdit_returnPressed() 110 { 111 QTextStream in(socket); 112 in << ui->sendLineEdit->text(); 113 ui->sendLineEdit->clear(); 114 }
#include "tcpclient.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); TcpClient w; w.show(); return a.exec(); }
总结:客户端的编写比较容易,只要连接服务器,发送信息给服务器就可以
2.服务器的编写
服务器需要做的事:
- Qt对于服务器有专门的类 QTcpServer封装,所以不需要去自己配置服务器(socket,bind),可以直接侦听(listen)
- 当有客户端连接的时候分配文件描述符和地址标示客户端
- 然后等待客户端发送信息(这里会得到一些信号,通过信号来处理),接收信号,转发(发送给链接在服务器的其他人)或者回馈信息。
- 服务器需要支持多客户端,所以要有保存和删除客户端信息的处理。
因为需要实现支持多客户端接入的服务器,所以需要将显示层(ui),服务器层,socket层分开。可以看下面这个图来理解
简述下这里的流程,首先,客户端发送请求,服务器分配(创建)一个socket,然后incomingConnection()函数捕捉到这个socket,并在此响应客户端(接收消息,转发消息)。然后在这里创建一个信息转发消息到ui界面
通过这样的模型,我们就可以创建一个多客户端的服务器。
接下来的一张图我来解释下我的程序中信号与槽函数之间连接的关系(人类总是对视觉的感官来的更直接点)
结合上面的图和我的代码就很清晰的能看出信号与槽在这三层之间的传递关系
(1)由MyTcpSocket socket产生两个信号,readyread()和disconnected(),然后通过connect连接到两个槽函数readDataSlot()与disconnectSlot()
(2)在上面的两个槽函数中发送emit两个信号sockReadDataSignal(qintptr,QString),disconnectSignal(qintptr),附带信息的信号,使得MyTcpServer能够访问到信息
(3)在MyTcpServer类中通过socket接受这两个信号,并连接到相应的槽函数,把信息返回给其他客户端。
(4)并且我把信号sockReadDataSignal()连接到MyTcpServer内显示到ui界面
下面是的我的代码区
1 #ifndef MYTCPSOCKET_H 2 #define MYTCPSOCKET_H 3 4 #include <QObject> 5 #include <QTcpSocket> 6 #include <QTextStream> 7 #include <QHostAddress> 8 9 10 class MyTcpSocket : public QTcpSocket 11 { 12 Q_OBJECT 13 public: 14 15 MyTcpSocket(); 16 ~MyTcpSocket(); 17 18 signals: 19 void disconnectSignal(qintptr); 20 void sockReadDataSignal(qintptr,QString); 21 22 private slots: 23 void readDataSlot(); 24 void disconnectSlot(); 25 }; 26 27 #endif // MYTCPSOCKET_H
1 #include "mytcpsocket.h" 2 3 MyTcpSocket::MyTcpSocket() 4 { 5 QObject::connect(this,SIGNAL(readyRead()),this,SLOT(readDataSlot())); 6 QObject::connect(this,SIGNAL(disconnected()),this,SLOT(disconnectSlot())); 7 } 8 9 MyTcpSocket::~MyTcpSocket() 10 { 11 12 } 13 14 void MyTcpSocket::readDataSlot() 15 { 16 //读取数据的两种方式 17 qDebug()<<"read data"; 18 QString msg; 19 QByteArray ba; 20 ba.resize(this->bytesAvailable()); 21 this->read(ba.data(),ba.size()); 22 msg = QString(ba); 23 qDebug()<<"ip:"<<this->peerAddress().toString(); 24 25 // QTextStream out(this),in(this); 26 // QString msg; 27 // out >> msg; 28 emit sockReadDataSignal(this->socketDescriptor(),msg); 29 } 30 31 void MyTcpSocket::disconnectSlot() 32 { 33 qDebug()<<"disconnect"; 34 emit disconnectSignal(this->socketDescriptor()); 35 }
1 #ifndef MYTCPSERVER_H 2 #define MYTCPSERVER_H 3 4 #include <QObject> 5 #include <QHostAddress> 6 #include <QTcpServer> 7 #include <QDebug> 8 #include <QList> 9 #include "mytcpsocket.h" 10 11 class MyTcpServer : public QTcpServer 12 { 13 Q_OBJECT 14 public: 15 MyTcpServer(); 16 ~MyTcpServer(); 17 18 protected: 19 void incomingConnection(qintptr socketDescriptor); 20 21 22 private slots: 23 void disconnectSlot(qintptr); 24 void answerMsgSlot(qintptr,QString); 25 26 signals: 27 void serverReadDataSignal(qintptr,QString); 28 29 private: 30 QList<MyTcpSocket *> clients; 31 32 }; 33 34 #endif // MYTCPSERVER_H
1 #include "mytcpserver.h" 2 3 MyTcpServer::MyTcpServer() 4 { 5 listen(QHostAddress::Any,8080); 6 } 7 8 MyTcpServer::~MyTcpServer() 9 { 10 11 } 12 13 void MyTcpServer::incomingConnection(qintptr socketDescriptor)//when a new connection is available. 14 { 15 qDebug()<<"connect success"; 16 MyTcpSocket *sock = new MyTcpSocket; 17 sock->setSocketDescriptor(socketDescriptor); 18 QObject::connect(sock,SIGNAL(disconnectSignal(qintptr)),this,SLOT(disconnectSlot(qintptr))); 19 QObject::connect(sock,SIGNAL(sockReadDataSignal(qintptr,QString)),this,SIGNAL(serverReadDataSignal(qintptr,QString))); 20 QObject::connect(sock,SIGNAL(sockReadDataSignal(qintptr,QString)),this,SLOT(answerMsgSlot(qintptr,QString))); 21 clients << sock; 22 } 23 24 25 void MyTcpServer::disconnectSlot(qintptr sockfd) 26 { 27 // MyTcpSocket *sock = new MyTcpSocket; 28 // sock->setSocketDescriptor(socketDescriptor); 29 qDebug()<<"delete client "<<sockfd; 30 for (int i = 0; i < clients.size(); ++i) 31 { 32 if (clients.at(i)->socketDescriptor() == sockfd) 33 clients.removeAt(i); 34 } 35 36 // delete sock; 37 } 38 39 void MyTcpServer::answerMsgSlot(qintptr sockfd, QString msg) 40 { 41 for (int i = 0; i < clients.size(); ++i) 42 { 43 if(clients.at(i)->socketDescriptor() != sockfd) 44 { 45 QTextStream in(clients.at(i)); 46 in << sockfd << ":" << msg << endl; 47 } 48 } 49 }
1 #ifndef MYTCPSERVERWIDGET_H 2 #define MYTCPSERVERWIDGET_H 3 4 #include <QWidget> 5 #include "mytcpserver.h" 6 #include <QObject> 7 8 namespace Ui { 9 class MyTcpServerWidget; 10 } 11 12 class MyTcpServerWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit MyTcpServerWidget(QWidget *parent = 0); 18 ~MyTcpServerWidget(); 19 20 private slots: 21 void widgetReadDataSlot(qintptr,QString); 22 23 private: 24 Ui::MyTcpServerWidget *ui; 25 MyTcpServer *tcpserver; 26 }; 27 28 #endif // MYTCPSERVERWIDGET_H
1 #include "mytcpserverwidget.h" 2 #include "ui_mytcpserverwidget.h" 3 4 MyTcpServerWidget::MyTcpServerWidget(QWidget *parent) : 5 QWidget(parent), 6 ui(new Ui::MyTcpServerWidget) 7 { 8 ui->setupUi(this); 9 tcpserver = new MyTcpServer; //connect to the server 10 QObject::connect(tcpserver,SIGNAL(serverReadDataSignal(qintptr,QString)),this,SLOT(widgetReadDataSlot(qintptr,QString))); 11 12 } 13 14 MyTcpServerWidget::~MyTcpServerWidget() 15 { 16 delete ui; 17 } 18 19 20 void MyTcpServerWidget::widgetReadDataSlot(qintptr sock, QString msg) 21 { 22 QString id; 23 id = QString::number(sock); 24 ui->listWidget->addItem(id+":"+msg); 25 }
1 #include "mytcpserverwidget.h" 2 #include <QApplication> 3 4 int main(int argc, char *argv[]) 5 { 6 QApplication a(argc, argv); 7 MyTcpServerWidget w; 8 w.show(); 9 10 return a.exec(); 11 }
视频教程在汇文的网校上也是有的:www.huiwen.com
最后附带一个写的很好的blog