Qt實現伺服器與客戶端傳輸文字和圖片(Qt②)
初學者記錄學習內容,如有錯誤請各位前輩指點。
此次工程完成過程借鑑了下面得兩個帖子,附上鍊接,並致以感謝:
qt 寫的tcp客戶端程式實現簡單的連線接受和傳送訊息
qt寫的一個簡單的tcp伺服器程式,可以接受訊息傳送資料
好了閒話少說進入正題。
瞭解C語言的盆友們應該知道實現Socket程式傳遞訊息需要以下幾點:
在伺服器server端:①建立套接字SOCKET;②bind()函式繫結套接字(和IP,埠Port繫結);③Listen()進入監聽狀態;④accept()進入接收客戶端請求;⑤send()向客戶端傳送資料;⑥close()關閉套接字。
在客戶端Client端:①建立套接字SOCKET;②connect()向伺服器發起請求;③recv()接收伺服器傳回的資料;④printf()列印傳回的資料;⑤close()關閉套接字。
而在Qt實現Socket的過程中,也與此過程有很多相似之處。
傳輸文字的伺服器server實現
在QtDesigner中繪製介面:
QDialog中兩個的PushButton分別命名為pbtnSend和stopButton,以便後面加入槽函式。
注意進行socket連線之前要在.pro中加入network
QT += core gui network
貼入程式碼如下:
sever.h
#ifndef SERVER_H
#define SERVER_H
#include <QDialog>
#include <QTcpSocket>
#include <QTcpServer>
#include <QMessageBox>
#include <QDebug>
namespace Ui {
class Server;
}
class Server : public QDialog
{
Q_OBJECT
public:
explicit Server(QWidget *parent = 0);
~Server();
private slots:
void on_stopButton_clicked();
void acceptConnection();
void sendMessage();
void displayError(QAbstractSocket::SocketError);
private:
Ui::Server *ui;
QTcpServer *tcpServer;
QTcpSocket *tcpSocketConnection;
};
#endif // SERVER_H
server.cpp
#include "server.h"
#include "ui_server.h"
Server::Server(QWidget *parent) :
QDialog(parent),
ui(new Ui::Server)
{
ui->setupUi(this);
tcpServer=new QTcpServer(this);
if (!tcpServer->listen(QHostAddress::Any, 7777)) {
qDebug() << tcpServer->errorString();
close();
}
tcpSocketConnection = NULL;
connect(tcpServer,SIGNAL(newConnection()),
this,SLOT(acceptConnection()));
connect(ui->pbtnSend,SIGNAL(clicked(bool)),
this,SLOT(sendMessage()));
}
Server::~Server()
{
delete ui;
}
void Server::acceptConnection()
{
tcpSocketConnection = tcpServer->nextPendingConnection();
connect(tcpSocketConnection,SIGNAL(disconnected()),this,SLOT(deleteLater()));
connect(tcpSocketConnection,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
}
void Server::on_stopButton_clicked()
{
tcpSocketConnection->abort();
QMessageBox::about(NULL,"Connection","Connection stoped");
}
void Server::sendMessage()
{
if(tcpSocketConnection==NULL)
return;
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_8);
out<<(quint16)0;
out<<"Hello [email protected][email protected]!";
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
tcpSocketConnection->write(block);
}
void Server::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpSocketConnection->errorString();
}
現在對server.cpp進行解釋:
引用查到的一段對QTcpServer和QTcpSocket基本操作的描述,如下涉及到的幾個函式都是特別重要的。
QTcpServer的基本操作:
1、呼叫listen監聽埠。
2、連線訊號newConnection,在槽函式裡呼叫nextPendingConnection獲取連線進來的socket。
QTcpSocket的基本能操作:
1、呼叫connectToHost連線伺服器。
2、呼叫waitForConnected判斷是否連線成功。
3、連線訊號readyRead槽函式,非同步讀取資料。
4、呼叫waitForReadyRead,阻塞讀取資料。
在呼叫這幾個函式之前先在.h檔案中做宣告指標變數:
private:
QTcpServer *tcpServer;
QTcpSocket *tcpSocketConnection;
在.cpp檔案中
tcpServer=new QTcpServer(this);
tcpSocketConnection = NULL;
注意之後要在tcpSocketConnection的基礎上操作資料,所以初始化要保證當前無連線,即賦值為NULL。
然後在建構函式中設定IP地址和埠號:
if (!tcpServer->listen(QHostAddress::Any, 7777)) {
qDebug() << tcpServer->errorString();
close();
}
QTcpServer呼叫Listen()監聽,使用了IPv4的本地主機地址,等價於QHostAddress(“127.0.0.1”),埠號設為”7777”。listen()函式返回的值是bool型,所以如果監聽失敗時,會把錯誤原因列印到控制檯,並關閉連線。
用QT實現並不需要像C語言那麼麻煩,當我們設定好IP和埠進行監聽時,用我的理解就是伺服器進入了迴圈,會不斷監聽檢查發來的連線,當有同樣的IP和埠的連線申請發來的時候,QTcpSocket會發射newConnection()訊號,在程式碼的槽函式中會觸發acceptConnection()函式。
tcpSocketConnection = tcpServer->nextPendingConnection();
當訊號發來的時候,通過呼叫QTcpServer的nextPendingConnection()函式得到的socket連線控制代碼tcpSocketConnection ,注意之後對於訊號和資料的操作都是在這個控制代碼基礎上進行的。
注意到此函式中僅有對tcpSocketConnection進行操作,但是沒有在開頭new一塊記憶體去儲存tcpSocketConnection,這是因為通過nextPendingConnection()得到控制代碼的過程就會被分配一塊記憶體去儲存,所以不用忘記了在之後釋放記憶體空間,即之後的deleteLater()函式。
之後的兩個connect語句,斷開後刪除連線,執行出錯將錯誤資訊列印到控制檯。
最後來看伺服器端最重要的一個槽函式sendMessage()。
當觸發pbtnSend按鈕的clicked()訊號,將固定訊息“Hello [email protected][email protected]!”傳送。首先連線控制代碼tcpSocketConnection是否為空。
然後用到了QByteArray,這在Qt傳輸資料時非常重要,它可以儲存raw bytes即原始位元組,將文字或者圖片轉化為raw bytes傳送,在接收端進行解析。
QDataStream則提供了一個二進位制的資料流。看程式碼:
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_8);
out<<(quint16)0;
out<<"Hello [email protected][email protected]!";
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
tcpSocketConnection->write(block);
將僅可寫入WriteOnly的資料流out與block進行繫結。
設定資料流out的版本,注意客戶端和伺服器端使用的版本要相同,這裡我們使用的是Qt5.8。然後通過C++的過載操作符<<實現對流的操作。
這裡著重說一點,使用<<的時候並不關心輸入的型別,換句話說無論是什麼型別寫入資料流,在讀出的時候都會以寫入的順序和型別讀出,而不用考慮佔幾個位元組,需要一個一個位元組取出來,高位低位組合起來等等情況。
比如此程式中,先以quint16型別(2個位元組)的輸入”0”佔位,然後寫入字串”Hello [email protected][email protected]!”。seek(0)將指標移動到0所在的那一位處,block.size()得出block的長度15,sizeof(quint16)得出一個quint16型別的長度為2,相減得13覆蓋儲存到剛才用0佔位的儲存quint16型別的記憶體中,這就是傳輸的資料的長度,與資料一起傳給伺服器用於比對資料是否傳輸完整。
注意quint16是QT軟體下的一種自定義型別,代表16位的無符號整型,可以儲存2^16個數字,就是0-65535,以此儲存傳送的文字的長度足以。但是如果需要傳輸圖片的大小就需要使用quint32型別來儲存,在後面中我們會用到。
最後是QTcpSocket的write函式,將QByteArray型別的block寫入Socket快取中,之後就是客戶端的工作了。
傳輸文字的客戶端Client實現
在QtDesigner中繪製介面:
QDialog下兩個QLineEdit用於填入埠號和IP地址,PushButton命名為sendButton用於觸發向伺服器的連線申請。QTextEdit命名為messageShow用於顯示從伺服器傳來的字串。
貼入程式碼如下:
client.h
#ifndef CLIENT_H
#define CLIENT_H
#include <QDialog>
#include <QTcpSocket>
#include <QObject>
#include <QMessageBox>
#include <QDebug>
#include <QDateTime>
namespace Ui {
class Client;
}
class Client : public QDialog
{
Q_OBJECT
public:
explicit Client(QWidget *parent = 0);
~Client();
private slots:
void on_sendButton_clicked();
void showMessage();
void displayError(QAbstractSocket::SocketError);
private:
Ui::Client *ui;
QTcpSocket *tcpSocket;
quint16 blockSize;
};
#endif // CLIENT_H
client.cpp
#include "client.h"
#include "ui_client.h"
Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
connect(ui->sendButton,SIGNAL(clicked()),this,SLOT(on_sendButton_clicked()));
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(showMessage()));
connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));
blockSize = 0;
}
Client::~Client()
{
delete ui;
}
void Client::on_sendButton_clicked()
{
if(tcpSocket->state()!=QAbstractSocket::ConnectedState)
{
tcpSocket->connectToHost(ui->ipLineEdit->text(),ui->portLineEdit->text().toInt());
if(tcpSocket->waitForConnected(10000))
{
QMessageBox::about(NULL, "Connection", "Connection success");
}
else
{
QMessageBox::about(NULL,"Connection","Connection timed out");
}
}
else
QMessageBox::information(NULL,"","Connected!");
}
void Client::showMessage()
{
QDataStream in(tcpSocket);
in.setVersion(QDataStream::Qt_5_8);
if(blockSize==0)
{
if(tcpSocket->bytesAvailable()<(int)sizeof(quint16))
return;
in >> blockSize;
}
if(tcpSocket->bytesAvailable()<blockSize)
return;
char* buf=new char[512];
in >> buf;
QDateTime time = QDateTime::currentDateTime();
QString str = time.toString("yyyy-MM-dd hh:mm:ss");
ui->messageShow->setText(buf+str);
if(buf)
delete buf;
}
void Client::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpSocket->errorString();
}
現在對client.cpp進行解釋。
多餘的東西不再贅述,點選sendButton執行槽函式on_sendButton_clicked()。這裡引用一段對QAbstractSocket的描述:
QAbstractSocket都有一個狀態,而我們可以通過呼叫成員函式state返回這個狀態,才開始的狀態是UnconnectedState,
當程式呼叫了connectToHost之後,QAbstractSocket的狀態會變成HostLookupState,,
如果主機被找到,QAbstaractSocket進入connectingState狀態並且發射HostFound()訊號,當連線被建立的時候QAbstractSocket 進入了connectedState狀態 並且發射connected()訊號,如果再這些階段出現了錯誤,QAbstractSocket將會發射error()訊號,無論在什麼時候,如果狀態改變了,都會發射stateChanged(),如果套接字準備好了讀寫資料,isValid()將會返回true。
如上所述呼叫state()判斷QAbstractSocket是否是connectedState,即已經連線的狀態。如果尚未連線繼續執行,呼叫隨後呼叫QTcpSocket的connectToHost(從控制元件中獲取的IP和埠號)連線伺服器,呼叫waitForConnected判斷是否連線成功,但如果是已經連線成功彈出提示框。
QMessageBox彈出提示語對話方塊,常用於幫助你判斷socket是否連線成功。
在伺服器中通過write()將資料寫入socket緩衝區,在已經連線成功的情況下客戶端當有資料要讀的時候,會觸發readyRead()訊號,隨後執行showMessage()槽函式,showMessage()函式是將傳過來的資料型別轉化後輸出。宣告資料流in接收資料,設定QT版本。
注意在.h中定義:
private:
quint16 blockSize;
quint16 型別的blockSize用於從資料流中接收之前儲存為quint16 的字串的大小,用來進行資料是否傳輸完整的比對,並在.cpp中賦值為blockSize = 0。
tcpSocket->bytesAvailable()返回已經接收到的可以被讀出的位元組數,(int)sizeof(quint16)返回一個quint16型別變數的大小,只有當前者大於後者說明資料的長度已經傳輸完整,則儲存到blockSize變數中,否則直接返回,繼續接收資料。
隨後用blockSize值進行比對,只有當接收到的位元組數大於blockSize的值才說明資料全部傳輸成功,否則返回繼續接收資料。
傳輸資料的大小和比對,這兩步是基本步驟,必不可少。
定義一個暫時儲存資料的char型陣列的指標,new一段大小為512的記憶體,將接下來的char型的字串輸入其中。一定記得在最後刪除指標。
關於刪除指標著重說一點——
關於C++中的解構函式,分配到棧(區域性記憶體管理)的不需要釋放,分配到堆(全域性記憶體管理)需要釋放。一般區域性物件在區域性函式結束的時候會自動釋放。
New一個記憶體空間或者malloc()動態分配記憶體空間一般都需要自己在解構函式中釋放。比如在此程式中,函式執行結束之後指向記憶體空間的指標buf最後會被自動刪除,而這一塊儲存空間並不會被釋放,而且也將再無法對其進行操作,長此以往記憶體會愈來愈小。因此有兩條路可選,一是對該指標進行儲存以便以後對其指向的記憶體空間進行管理。二是在函式結束之前釋放記憶體空間。
最後獲取當前的時間和日期顯示到textEdit中。結束。
傳輸圖片的server和client實現
關於socket的連線,這裡將不再累述,只對傳送和顯示圖片的過程進行簡單地講解。
同上,先再QTdesigner中繪製客戶端和伺服器的介面,同傳輸文字大致相同,只需將客戶端介面中的textEdit換成QLabel用於顯示圖片。
伺服器中傳送圖片和客戶端中接收圖片的槽函式如下:
void pictureSever::on_sendPictureButton_clicked()
{
if(tcpSocket==NULL)
return;
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_8);
out<<(quint32)buffer.data().size();
block.append(buffer.data());
tcpSocket->write(block);
}
在.h中宣告
private:
QBuffer buffer;
在.cpp的建構函式中,將圖片儲存在buffer中:
QPixmap(":/new/prefix1/sendPicture/007.bmp").save(&buffer,"BMP");
圖片在專案的資原始檔中。
此處將字串讀入socket中的方法與上例中的方法大致相同,先讀入資料的大小,後讀入資料。但過程不同,兩個過程並無太大區別,可自行選擇。
不過要注意的就是上面已經談過的,這裡使用quint32而不是quint16來儲存圖片的大小。
void pictureClient::showPicture()
{
while(tcpSocketConnection->bytesAvailable()>0)
{
if(blockSize==0)
{
QDataStream in(tcpSocketConnection);
in.setVersion(QDataStream::Qt_5_8);
if(tcpSocketConnection->bytesAvailable()<sizeof(quint32))
return;
in>>blockSize;
}
if(tcpSocketConnection->bytesAvailable()<blockSize)
return;
QByteArray array = tcpSocketConnection->read(blockSize);//blockSize作read()的引數。
QBuffer buffer(&array);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer,"BMP");
QImage image = reader.read();
blockSize=0;//①
if(!image.isNull())
{
image=image.scaled(ui->showPicturelabel->size());
ui->showPicturelabel->setPixmap(QPixmap::fromImage(image));
blockSize=0;//②
}
}
}
同樣的方式,先確定用quint32變數儲存的圖片大小的的值已經傳入,然後比對確定圖片完全傳入。
注意這裡要使用QImageReader將圖片的資料流轉化回BMP格式,必須使用QBuffer,將資料裝入QBuffer型別的變數中進行轉化。
有概念性的疑問一定去找Qt幫助文件,這才是權威。
最後說一點:blockSize的問題,在傳輸字串時,由於我們要傳輸的資料比較短,我們就當作記錄字串長度的變數一次性傳入成功,其實這是存在問題的。資料較多時,並不一定能一次成功的,應該使用傳輸圖片時使用的while迴圈比對判斷。因為要使用blockSize==0作為判斷條件,在迴圈體的結尾處要將blockSize再置為0,①處是傳輸異常情況下走不到下面的if結構中時的情況,②處是傳輸正常情況下的置0。
最後將圖片讀入到image,自適應Label的大小並顯示。
有點囉嗦,如有錯誤還望指正,謝謝。
相關推薦
Qt實現伺服器與客戶端傳輸文字和圖片(Qt②)
初學者記錄學習內容,如有錯誤請各位前輩指點。 此次工程完成過程借鑑了下面得兩個帖子,附上鍊接,並致以感謝: qt 寫的tcp客戶端程式實現簡單的連線接受和傳送訊息 qt寫的一個簡單的tcp伺服器程式,可以接受訊息傳送資料 好了閒話少說進入正題。 瞭解C
二、Netty實現伺服器與客戶端完整互動連線實戰
本節內容是程式碼實現伺服器與客戶端完整連線過程。整體把控netty的工作流程。我們先不要被某個類,某個api的封裝深入挖掘,這樣你會踩很多坑,陷入進去而拔不出來,後面我會一一講解,原始碼剖析工作原理。這就是我個人學習技術的一種方法,深入淺出
【隨堂筆記】unity開發中Socket的用法(一,實現伺服器與客戶端簡單的連結)
實現了簡單的連結,也增加了客戶端沒有連結到伺服器的自動重連 伺服器程式碼 using System; using System.Net; using System.Net.Sockets; namespace SeverSocket { class Program
Vue+Java servlet 通過websocket實現伺服器與客戶端雙向通訊
1. vue程式碼 methods: { //在方法裡呼叫 this.websocketsend()傳送資料給伺服器 onConfirm () { //需要傳輸的資料 let data = { cod
JAVA整合WebSocket,實現伺服器與客戶端握手
WebSocket實現伺服器與客戶端握手 自學的WebSocket途中遇到很多坑,希望需要使用的朋友可以少走彎路, 使用的環境:tomcat7.0,mysql,springMvc,spring,M
socket程式設計(一),實現伺服器與客戶端簡單通訊
本節主講客戶端向服務傳送資訊,伺服器轉發給客戶端,當然也可以稍微改一下程式碼可以實現互傳訊息,不再贅述。 難點在於伺服器端的程式碼思路: (1)主程式Socket socket=server.acc
挖掘機(DMS)(伺服器與客戶端讀寫檔案,收發資料)
Client package com.company; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IO
一個基於TCP/IP的伺服器與客戶端通訊的小專案(超詳細版)
1.目的:實現客戶端向伺服器傳送資料 原理: 2.建立兩個控制檯應用,一個為伺服器,用於接收資料。一個為客戶端,用於傳送資料。 關鍵類與對應方法: 1)類IPEndPoint: 1.是抽象類EndPoint的實現類 2.So
SqlServer資料庫連線數與客戶端連線池關係測試(一)
連線池連線數 DB連線數 峰值錯誤資訊 連線峰值 40000 0(32767) ? ? 3000 0(32767) ? ? 40000 3000 ? ?
CSS3動畫實現高亮光弧效果,文字和圖片(一閃而過)
前言 好久沒有寫部落格啦,高亮文字和圖片一閃而過的特效,用CSS3來寫 先看文字吧, 就上程式碼了 .shadow { /* 背景顏色線性漸變 */ /* 老式寫法 */ /* linear為線性漸變,也可以用下面的那種寫法。left top,right t
C#.網路程式設計 Socket基礎(三) 基於WinForm系統Socket TCP協議 實現端到端(伺服器與客戶端).txt.word.png等不同型別檔案傳輸
一、簡介: 前面的兩篇介紹了字串傳輸、圖片傳輸: 其實,本文針對Socket基礎(二)進一步完成,以便可以進行多種檔案傳輸。 二、基於不同的流(檔案流、記憶體流、網路等)讀寫。 1、圖片傳輸 方法一:(在客戶端用檔案流傳送(即將圖片寫到檔案流去,以便傳送),
搭建FTP伺服器與客戶端(1) - Python實現
FTP背景介紹:FTP(File Transfer Protocol)協議,顧名思義為檔案傳輸協議。由已故的Jon Postel與Joyce Reynolds開發,並於1985年10月釋出。其底層基於TCP/IP協議。FTP目前主要用於匿名下載公共檔案,也可以在兩臺跨系統的計算機之間傳輸檔案。為了實現F
【開發筆記】Unity聯網鬥地主的實現(一,伺服器與客戶端的資料傳遞流程)
話不多說,先上我李老師的思維導圖 大致構思了一個框架 1.首先要定義一下伺服器與客戶端的傳輸協議,必須保持一致 2.定義服務於客戶端傳輸的訊息型別,如(申請加入,同意加入,出牌,之類的) 3.定義一下牌的型別,出的牌的型別,在客戶端判斷是否可以出牌,牌型傳給伺服器,伺服器在完成三個玩家的出
Qt 多執行緒伺服器與客戶端
文章目錄 思路 伺服器 myserver.h myserver.cpp mythread.h mythread.cpp mysocket.h mysocket.cpp
C#.網路程式設計 Socket基礎(一)Socket TCP協議 實現端到端(伺服器與客戶端)簡單字串通訊
簡介: 本章節主要討論了Socket的入門知識,還未針對Socket的難點問題(比如TCP的無訊息邊界問題)展開討論,往後在其他章節中進行研究。 注意點: 伺服器(比如臺式電腦)的IP為1.1.1.2,那麼客戶端(其他裝置,比如手機,Ipad)連線的一定是
C#.網路程式設計 Socket基礎(四) WPF系統Socket TCP協議 伺服器與客戶端 不同型別檔案傳輸,同時解決UI執行緒與工作執行緒的卡頓問題
一、簡介 雖然,本文的前面幾篇文章在WinForm中實現了Socket TCP協議 伺服器與客戶端 不同型別檔案傳輸,詳情見 但是,卻沒有在WPF中實現 Socket TCP協議 伺服器與客戶端 不同型別檔案傳輸。因此,本文將描述如何在WPF中實現該功能。
JAVA NIO 伺服器與客戶端實現示例(程式碼1)
公共類: [java] view plain copy print? package com.stevex.app.nio; import java.nio.ByteBuffer; import java.nio.CharBuffer; import j
linux c伺服器與客戶端之間的檔案傳輸
最近做了一下linux C網路方面的專案,簡單的寫了一下伺服器於客戶端之間上傳,下載檔案,利用併發伺服器,可以實現多個客戶端同時上傳,下載。 寫的不好,還請大神多多指教!多的不說,一切都在程式碼中,部分程式碼如下所示: /*server.c */ 伺服器端 void *re
Java實現簡單的Socket伺服器與客戶端字串通訊(適合初學者閱讀)
近段時間,頻繁看到很多學生做畢業設計用到了Socket通訊技術,問題非常多,特寫一個小例子,希望對馬上畢業的同學有所幫助。如果希望學習的更加深入,需要掌握的知識有:面向物件、多執行緒、Socket通訊、IO流、異常處理 伺服器端程式碼: import java
JAVA NIO 伺服器與客戶端實現示例
公共類: package com.stevex.app.nio; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingExcepti