這裏用Qt來簡單設計實現一個場景,即:html
(1)兩端:服務器QtServer和客戶端QtClient服務器
(2)功能:服務端鏈接客戶端,二者可以互相發送消息,傳送文件,而且顯示文件傳送進度。網絡
環境:VS20013 + Qt5.11.2 + Qt設計師架構
先看效果:app
客戶端與服務器的基本概念不說了,關於TCP通訊的三次握手等等,在《計算機網絡》裏都有詳細介紹。這裏說下二者是如何創建起通訊鏈接的。socket
(1)IP地址:首先服務器和每個客戶端都有一個地址,即IP地址。(底層的MAC地址,不關心,由於TCP通訊以及IP,是七層架構裏面的網絡層、傳輸層了,底層透明)。對於服務器來講,客戶端的數量及地址是未知的,除非創建了鏈接。可是對於客戶端來講,必須知道服務器的地址,由於二者之間的鏈接是由客戶端主動發起的。tcp
(1)端口號:軟件層面的端口號,指的是 「應用層的各類協議進程與運輸實體進行層間交互的一種地址」。簡而言之,每個TCP鏈接都是一個進程,操做系統須要爲每一個進程分配一個協議端口(即每個客戶端與服務端的鏈接,不是兩臺主機的鏈接,而是兩個端口的鏈接)。但一臺主機一般會有不少服務,不少進程,單靠一個IP地址不能標識某個具體的進程或者鏈接。因此用端口號來標識訪問的目標服務器以及服務器的目標服務類型。端口號也有分類,但這不是本文的重點,詳見教材。函數
(3)TCP鏈接:總的來講,TCP的鏈接管理分爲單個階段:創建鏈接 -> 數據傳送 -> 鏈接釋放。在(2)裏說到,每一個TCP鏈接的是具體IP地址的主機的兩個端口,即TCP鏈接的兩個端點由IP地址和端口號組成,這便是套接字(socket)的概念:套接字socket = IP + 端口號。ui
所以,咱們要經過經過套接字來創建服務端與客戶端的通訊鏈接。
this
QTcpSocket:提供套接字
QTcpServer:提供基於TCP的服務端,看官方文檔的解釋以下:
This class makes it possible to accept incoming TCP connections. You can specify the port or have QTcpServer pick one automatically. You can listen on a specific address or on all the machine’s addresses.
這個解釋裏面提到兩點:
(1)指定端口:即開通哪個端口用於創建TCP鏈接;
(2)監聽:監聽(1)中指定的端口是否有鏈接的請求。
(1)服務器:
(2)客戶端:
客戶端:
服務端:
咱們先要在工程文件中加入network
QT += core gui network
下面咱們來看看服務器程序步驟:
一、初始化 QTcpServer 對象
TCP_server = new QTcpServer();
二、啓動服務器監聽
TCP_server->listen(QHostAddress::Any,9988);//9988爲端口號
三、鏈接 QTcpServer 對象的 newConnection 信號槽,當有客戶端連接時,客戶端會發送 newConnection 信號給服務器,觸發槽函數接受連接(獲得一個與客戶端通訊的套接字 QTcpSocket)
connect(TCP_server, SIGNAL(newConnection()), this, SLOT(slot_newconnect())); // 在與 newConnection 信號鏈接的槽函數中,獲取與相應客戶端通訊的套接字 TCP_connectSocket = mServer->nextPendingConnection();
四、QTcpsocket 對象調用成員函數 write,發送數據給客戶端
TCP_connectSocket->write(msg.toUtf8());
五、當客戶端有數據發送來,QTcpSocket 對象就會發送 readyRead 信號,關聯槽函數讀取數據
connect(mSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
六、鏈接 QTcpsocket 對象的 disconnected 信號槽,當客戶端對象調用成員函數 close,會觸發 QTcpsocket 對象的 disconnected 信號,進而觸發槽函數進行相應處理
connect(mSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect()));
TCP_Server.h
#ifndef TCP_SERVER_H #define TCP_SERVER_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> namespace Ui { class TCP_Server; } class TCP_Server : public QWidget { Q_OBJECT public: explicit TCP_Server(QWidget *parent = nullptr); ~TCP_Server(); private slots: void slot_newconnect(); //創建新鏈接的槽 void slot_sendmessage(); //發送消息的槽 void slot_recvmessage(); //接收消息的槽 void slot_disconnect(); //取消鏈接的槽 private: Ui::m_tcpServer *ui; QTcpServer *TCP_server; //QTcpServer服務器 QTcpSocket *TCP_connectSocket; //與客戶端鏈接套接字 }; #endif // TCP_SERVER_H
TCP_Server.cpp
#include "tcp_server.h" #include "ui_tcp_server.h" #include <QMessageBox> #include <QDateTime> TCP_Server::TCP_Server(QWidget *parent) : QWidget(parent), ui(new Ui::TCP_Server) { ui->setupUi(this); //初始化 TCP_server = new QTcpServer(); TCP_connectSocket = nullptr; connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage())); //調用listen函數監聽同時綁定IP和端口號 if(TCP_server->listen(QHostAddress::LocalHost,10000)) //判斷listen是否成功,成功則繼續執行,鏈接新接收信號槽 { this->connect(TCP_server,SIGNAL(newConnection()),this,SLOT(slot_newconnect())); //將服務器的新鏈接信號鏈接到接收新鏈接的槽 } else { QMessageBox::critical(this,"錯誤","IP綁定錯誤,請關閉其它服務端或更改綁定端口號"); } } TCP_Server::~TCP_Server() { delete ui; } //創建新鏈接的槽 void TCP_Server::slot_newconnect() { if(TCP_server->hasPendingConnections()) //查詢是否有新鏈接 { TCP_connectSocket = TCP_server->nextPendingConnection(); //獲取與真實客戶端相連的客戶端套接字 ui->textBrowser->append("client login!"); //如有新鏈接,則提示 this->connect(TCP_connectSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage())); //鏈接客戶端的套接字的有新消息信號到接收消息的槽 this->connect(TCP_connectSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect())); //鏈接客戶端的套接字取消鏈接信號到取消鏈接槽 } } //發送消息的槽 void TCP_Server::slot_sendmessage() { QString sendMessage = ui->lineEdit->text(); //獲取單行文本框內要發送的內容 if(TCP_connectSocket != nullptr && !sendMessage.isEmpty()) //確保有客戶端鏈接,而且發送內容不爲空 { TCP_connectSocket->write(sendMessage.toLatin1()); //發送消息到客戶端 QString localDispalyMessage = "send to client: " + sendMessage \ + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n"); ui->textBrowser->append(localDispalyMessage); //將要發送的內容顯示在listwidget } ui->lineEdit->clear(); } //接收消息的槽 void TCP_Server::slot_recvmessage() { if(TCP_connectSocket != nullptr) //與客戶端鏈接的socket,不是nullptr,則說明有客戶端存在 { QByteArray array = TCP_connectSocket->readAll(); //接收消息 QHostAddress clientaddr = TCP_connectSocket->peerAddress(); //得到IP int port = TCP_connectSocket->peerPort(); //得到端口號 QDateTime datetime = QDateTime::currentDateTime(); QString sendMessage = tr("recv from :") + clientaddr.toString() + tr(" : ") \ + QString::number(port) + tr(" ") + datetime.toString("yyyy-M-dd hh:mm:ss") + tr("\n"); sendMessage += array; ui->textBrowser->append(sendMessage); //將接收到的內容加入到listwidget } } //取消鏈接的槽 void TCP_Server::slot_disconnect() { if(TCP_connectSocket != nullptr) { ui->textBrowser->append("client logout!"); TCP_connectSocket->close(); //關閉客戶端 TCP_connectSocket->deleteLater(); } }
下面咱們來看看客戶端程序步驟:
一、初始化 QTcpSocket 對象
TCP_server = new QTcpSocket();
二、QTcpSocket 調用 connectToHost(QHostAddress("IP"), 端口號),鏈接服務器IP和端口號
TCP_sendMesSocket->connectToHost("127.0.0.1",10000);
三、QTcpsocket 對象調用成員函數 write,發送數據給服務器
//取發送信息編輯框內容 QString msg = ui->sendEdit->toPlainText(); TCP_sendMesSocket->write(msg.toUtf8());//轉編碼
四、鏈接QTcpsocket 對象的 connected() 信號槽,當客服端成功鏈接到服務器後觸發 connected() 信號
connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected()));
五、鏈接QTcpsocket 對象的 readyread() 信號槽,當客戶端接收到服務端發來數據時觸發 readyread() 信號
connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage()));
六、鏈接 QTcpsocket 對象的 disconnected 信號槽,當客戶端對象調用成員函數 close,會觸發 QTcpsocket 對象的 disconnected 信號,進而觸發槽函數進行相應處理
connect(TCP_sendMesSocket, SIGNAL(disconnected()), this, SLOT(slot_disconnect()));
TCP_Client.h
#ifndef TCP_CLIENT_H #define TCP_CLIENT_H #include <QWidget> #include <QTcpSocket> namespace Ui { class TCP_Client; } class TCP_Client : public QWidget { Q_OBJECT public: explicit TCP_Client(QWidget *parent = nullptr); ~TCP_Client(); //與按鈕交互,故函數都設置爲槽函數 private slots: void slot_connected(); //處理成功鏈接到服務器的槽 void slot_sendmessage(); //發送消息到服務器的槽 void slot_recvmessage(); //接收來自服務器的消息的槽 void slot_disconnect(); //取消與服務器鏈接的槽 private: Ui::TCP_Client *ui; bool isconnetion; //判斷是否鏈接到服務器的標誌位 QTcpSocket *TCP_sendMesSocket; //發送消息套接字 }; #endif // TCP_CLIENT_H
TCP_Client.cpp
#include "tcp_client.h" #include "ui_tcp_client.h" #include <QHostAddress> #include <QDateTime> #include <QMessageBox> TCP_Client::TCP_Client(QWidget *parent) : QWidget(parent), ui(new Ui::TCP_Client) { ui->setupUi(this); /*** 初始化TCP ***/ this->setWindowTitle("TCP客戶端"); this->isconnetion = false; //初始化sendMesSocket this->TCP_sendMesSocket = new QTcpSocket(); //終止以前的鏈接,重置套接字 TCP_sendMesSocket->abort(); //給定IP和端口號,鏈接服務器 this->TCP_sendMesSocket->connectToHost("127.0.0.1",10000); //QHostAddress::LocalHost等於127.0.0.1,因此二者均可以互相替換 //成功鏈接服務器的connected()信號鏈接到slot_connected() (注意:不是connect()信號) connect(TCP_sendMesSocket,SIGNAL(connected()),this,SLOT(slot_connected())); //發送按鈕的clicked()信號鏈接到slot_sendmessage() connect(ui->pushButton_send,SIGNAL(clicked()),this,SLOT(slot_sendmessage())); //有新數據到達時的readyread()信號鏈接到slot_recvmessage() connect(TCP_sendMesSocket,SIGNAL(readyRead()),this,SLOT(slot_recvmessage())); //與服務器斷開鏈接的disconnected()信號鏈接到slot_disconnect() connect(TCP_sendMesSocket,SIGNAL(disconnected()),this,SLOT(slot_disconnect())); } TCP_Client::~TCP_Client() { delete ui; } //處理成功鏈接到服務器的槽 void TCP_Client::slot_connected() { this->isconnetion = true; ui->textBrowser->append(tr("與服務器鏈接成功:") + QDateTime::currentDateTime().toString("yyyy-M-dd hh:mm:ss")); } //發送消息到服務器的槽 void TCP_Client::slot_sendmessage() { if(this->isconnetion) { QString sendMessage = ui->lineEdit->text(); //從單行文本框得到要發送消息 if(!sendMessage.isEmpty()) { //發送消息到服務器 this->TCP_sendMesSocket->write(sendMessage.toLatin1()); //本地顯示發送的消息 QString localDispalyMessage = tr("send to server: ") + sendMessage \ + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n"); ui->textBrowser->append(localDispalyMessage); } else QMessageBox::warning(this,"錯誤","消息不能爲空!",QMessageBox::Ok); } else QMessageBox::warning(this,"錯誤","未鏈接到服務器!",QMessageBox::Ok); ui->lineEdit->clear(); } //接收來自服務器的消息的槽 void TCP_Client::slot_recvmessage() { //接收來自服務器的消息 QByteArray byteArray = this->TCP_sendMesSocket->readAll(); QString recvMessage = tr("recv from server: ") + byteArray + QDateTime::currentDateTime().toString(" yyyy-M-dd hh:mm:ss") + tr("\n"); ui->textBrowser->append(recvMessage); } //取消與服務器鏈接的槽 void TCP_Client::slot_disconnect() { QMessageBox::warning(this,"警告","與服務器的鏈接中斷",QMessageBox::Ok); //關閉並隨後刪除socket TCP_sendMesSocket->close(); TCP_sendMesSocket->deleteLater(); }
參考: