Qt Network 예제4 - 파일 전송
예제 실행
접속, 대화


서버에서 특정 대상에게 메시지 전송


파일 전송


예제 파일
QTCPServer.zip
0.56MB
QTCPClient.zip
0.56MB
소스 코드
Server
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QDebug> #include <QFile> #include <QFileDialog> #include <QMessageBox> #include <QMetaType> #include <QSet> #include <QStandardPaths> #include <QTcpServer> #include <QTcpSocket> namespace Ui { class MainWindow; } // [ex.02] class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; QTcpServer* m_server; QSet<QTcpSocket*> qset_connectedSKT; void appendToSocketList(QTcpSocket* socket); void sendMessage(QTcpSocket* socket); void sendAttachment(QTcpSocket* socket, QString filePath); void refreshComboBox(); signals: void singal_newMessage(QString); private slots: void slot_newConnection(); void slot_discardSocket(); void slot_displayError(QAbstractSocket::SocketError socketError); void slot_readSocket(); void on_pushButton_sendMessage_clicked(); void on_pushButton_sendAttachment_clicked(); void slot_displayMessage(const QString& str); }; #endif // MAINWINDOW_H
#include "mainwindow.h" #include "ui_mainwindow.h" // [ex.02.1] // MainWindow 생성자 실행 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); m_server = new QTcpServer(); // [ex.02.1.1] // 생성자 실행과 동시에 m_server의 listening을 시작하고 // newMessage 가 들어오면, slot_displayMessage 실행하여 UI에 출력 if(m_server->listen(QHostAddress::Any, 8080)) { // [ex.02.1.3] // 서버로 클라이언트의 새 연결 요청이 들어오면, // slot_newConnection 슬롯 실행하여 대기중인 요청 처리 connect(m_server, &QTcpServer::newConnection, this, &MainWindow::slot_newConnection); // [ex.02.1.4] // signal_newMessage 시그널이 발생하면 (서버 socket read 가 아닌, MainWindow 시그널) // slot_displayMessage 실행하여 UI에 출력 connect(this, &MainWindow::singal_newMessage, this, &MainWindow::slot_displayMessage); // [ex.02.1.5] // 상태바(하단 출력) 서버 실행 상태 표시 ui->statusBar->showMessage("Server is listening..."); } // [ex.02.1.2] // listening 실행이 안되면 실행 else { QMessageBox::critical(this,"QTCPServer",QString("Unable to start the server: %1.").arg(m_server->errorString())); exit(EXIT_FAILURE); } } // [ex.02.2] MainWindow::~MainWindow() { // 서버에 연결된 모든 연결 소켓 해제 foreach (QTcpSocket* socket, qset_connectedSKT) { socket->close(); socket->deleteLater(); } // 서버 소켓 해제 m_server->close(); m_server->deleteLater(); delete ui; } // [ex.02.3] void MainWindow::slot_newConnection() { // appendToSocketList 함수로 연결 객체 관리 처리 while (m_server->hasPendingConnections()) appendToSocketList(m_server->nextPendingConnection()); } // [ex.02.4] void MainWindow::appendToSocketList(QTcpSocket* socket) { // [ex.02.4.1] // 연결된 소켓을 qset으로 관리 qset_connectedSKT.insert(socket); // [ex.02.4.2] // 연결된 소켓에 read 할 데이터가 들어오면, // 이 객체의(MainWindow) slot_readSocket 실행하여 처리 connect(socket, &QTcpSocket::readyRead, this, &MainWindow::slot_readSocket); // [ex.02.4.3] // 연결된 소켓과 연결이 해제되면, // 이 객체의(MainWindow) slot_discardSocket 슬롯 함수 실행하여 처리 connect(socket, &QTcpSocket::disconnected, this, &MainWindow::slot_discardSocket); // [ex.02.4.4] // 연결된 소켓에 문제가 발생하면, // 이 객체의(MainWindow) slot_displayError 슬롯 함수 실행하여 처리 connect(socket, &QAbstractSocket::errorOccurred, this, &MainWindow::slot_displayError); // [ex.02.4.5] // 소켓 디스크립터로 대상 선택 가능하도록 ui 표시 ui->comboBox_receiver->addItem(QString::number(socket->socketDescriptor())); // [ex.02.4.6] // 연결된 클라이언트 정보와, 소켓 디스크립터(정수 식별자) 출력 slot_displayMessage(QString("INFO :: Client with sockd:%1 has just entered the room").arg(socket->socketDescriptor())); } // [ex.02.5] // 연결된 소켓에서 연결이 끊어지면 동작 void MainWindow::slot_discardSocket() { // 연결된 socket 의 disconnected 시그널을 발생시킨 socket 객체를 찾아서 QTcpSocket* socket = reinterpret_cast<QTcpSocket*>(sender()); // 해당 소켓을 qset_connectedSKT 에서 제거하고, 메시지 출력 QSet<QTcpSocket*>::iterator it = qset_connectedSKT.find(socket); if (it != qset_connectedSKT.end()){ slot_displayMessage(QString("INFO :: A client has just left the room").arg(socket->socketDescriptor())); qset_connectedSKT.remove(*it); } // ui 콤보박스 재설정 refreshComboBox(); socket->deleteLater(); } // [ex.02.6] // 연결된 소켓에서 에러 발생하면 출력 void MainWindow::slot_displayError(QAbstractSocket::SocketError socketError) { switch (socketError) { case QAbstractSocket::RemoteHostClosedError: break; case QAbstractSocket::HostNotFoundError: QMessageBox::information(this, "QTCPServer", "The host was not found. Please check the host name and port settings."); break; case QAbstractSocket::ConnectionRefusedError: QMessageBox::information(this, "QTCPServer", "The connection was refused by the peer. Make sure QTCPServer is running, and check that the host name and port settings are correct."); break; default: QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender()); QMessageBox::information(this, "QTCPServer", QString("The following error occurred: %1.").arg(socket->errorString())); break; } } // [ex.02.7] // 첨부파일 또는 메시지 수신 처리 void MainWindow::slot_readSocket() { // [ex.02.5.1] // 슬롯 함수가 실행되면, sender()를 통해 signal을 발생시킨 객체를 찾아 return // 이 슬롯(slot_readSocket)은 서버에 연결된 socket의 readyread 시그널에 대한 슬롯으로 // 연결 메시지를 보낸 socket을 찾을 수 있다. QTcpSocket* socket = reinterpret_cast<QTcpSocket*>(sender()); // QByteArray 타입의 buffer를 만들고 QByteArray buffer; // 서버에 연결된 socket을 stream으로 연결한다. QDataStream socketStream(socket); socketStream.setVersion(QDataStream::Qt_5_15); // stream으로 데이터를 읽어들이고, buffer로 넘기면 socketStream.startTransaction(); socketStream >> buffer; // stream startTransaction 실행 문제시 에러 표시 후 함수 종료 if(!socketStream.commitTransaction()) { QString message = QString("%1 :: Waiting for more data to come..").arg(socket->socketDescriptor()); emit singal_newMessage(message); return; } // client 에서 보낸 payload(순수한 데이터, 전달 메시지)를 // buffer에서 처음 128 byte 부분만 읽어들여서 header 에 담고 fileType을 찾는다. QString header = buffer.mid(0,128); QString fileType = header.split(",")[0].split(":")[1]; // buffer의 128 byte 이후 부분을 buffer = buffer.mid(128); // fileType이 attachment 라면 파일 수신 로직을 실행하고 // fileType이 message 라면 문장 수신 로직을 실핸한다. if(fileType=="attachment") { // 파일 전송은, 1)저장될 파일 이름, 2) 파일 확장자 3) 파일 크기 정보가 필요하다. QString fileName = header.split(",")[1].split(":")[1]; QString ext = fileName.split(".")[1]; QString size = header.split(",")[2].split(":")[1].split(";")[0]; // 파일 전송 메시지를 받으면, 메시지 박스를 띄워서 전송 받을 것인지 확인한다. // 메시지 박스에서 yes를 선택하면 파일을 읽는다. if (QMessageBox::Yes == (QMessageBox::question(this, "QTCPServer", QString("You are receiving an attachment from sd:%1 of size: %2 bytes, called %3. Do you want to accept it?").arg(socket->socketDescriptor()).arg(size).arg(fileName)))) { // 저장될 파일의 경로를 설정하고, 파일 이름과, 확장자를 설정한다. QString filePath = QFileDialog::getSaveFileName(this, tr("Save File"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"+fileName, QString("File (*.%1)").arg(ext)); // file 객체를 위에서 설정한 경로를 기반으로 연결하고 QFile file(filePath); // file 객체를 열고, buffer에 들어있는 byte를 쓴다(내보낸다. 통신이랑 같다). if(file.open(QIODevice::WriteOnly)) { file.write(buffer); // 파일이 저장되는 것에 대한 메시지를 ui에 출력한다. QString message = QString("INFO :: Attachment from sd:%1 successfully stored on disk under the path %2").arg(socket->socketDescriptor()).arg(QString(filePath)); emit singal_newMessage(message); } else QMessageBox::critical(this,"QTCPServer", "An error occurred while trying to write the attachment."); } else { // 메시지 박스에서 No 전송 거부시 메시지를 출력한다. QString message = QString("INFO :: Attachment from sd:%1 discarded").arg(socket->socketDescriptor()); emit singal_newMessage(message); } } else if(fileType=="message") { // 전송된 메시지를 서버에서 출력한다. QString message = QString("%1 :: %2").arg(socket->socketDescriptor()).arg(QString::fromStdString(buffer.toStdString())); emit singal_newMessage(message); } } // [ex.02.8] // 서버에서 메시지를 보낼 때, // 1) 서버에 연결된 특정 대상에게 전송하거나 // 2) 연결된 모든 대상에게 전송하도록 선택한다.(Broadcast) void MainWindow::on_pushButton_sendMessage_clicked() { QString receiver = ui->comboBox_receiver->currentText(); // Broadcast 라면, qset_connectedSKT 에 저장된 모든 대상에게 메시지 전송 if(receiver=="Broadcast") { foreach (QTcpSocket* socket,qset_connectedSKT) { sendMessage(socket); } } // 선택한 대상을 qset_connectedSKT에서 소켓을 찾아 메시지 전송 else { foreach (QTcpSocket* socket, qset_connectedSKT) { if(socket->socketDescriptor() == receiver.toLongLong()) { sendMessage(socket); break; } } } // 메시지 입력창 리셋 ui->lineEdit_message->clear(); } // [ex.02.9] // 서버에서 파일을 보낼 때 void MainWindow::on_pushButton_sendAttachment_clicked() { // 보낼 대상 선택 QString receiver = ui->comboBox_receiver->currentText(); // 파일 경로 가져오고, 경로 문제시 경고 출력 QString filePath = QFileDialog::getOpenFileName(this, ("Select an attachment"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ("File (*.json *.txt *.png *.jpg *.jpeg)")); if(filePath.isEmpty()) { QMessageBox::critical(this,"QTCPClient","You haven't selected any attachment!"); return; } // 보낼 대상이 연결된 모든 socket일때 동작 if(receiver=="Broadcast") { foreach (QTcpSocket* socket, qset_connectedSKT) { sendAttachment(socket, filePath); } } // 보낼 대상이 특정 socket일때 동작 else { foreach (QTcpSocket* socket, qset_connectedSKT) { if(socket->socketDescriptor() == receiver.toLongLong()) { sendAttachment(socket, filePath); break; } } } ui->lineEdit_message->clear(); } // [ex.02.10] void MainWindow::sendMessage(QTcpSocket* socket) { if(socket) { if(socket->isOpen()) { // ui에서 입력할 message를 가져와 QString str = ui->lineEdit_message->text(); // stream으로 보내는데 QDataStream socketStream(socket); socketStream.setVersion(QDataStream::Qt_5_15); // 헤더 부분에 fileType을 message로 설정한다. QByteArray header; header.prepend(QString("fileType:message,fileName:null,fileSize:%1;").arg(str.size()).toUtf8()); header.resize(128); // message 인코딩 설정하고, QByteArray에 할당하고 QByteArray byteArray = str.toUtf8(); // header 정보를 앞에 넣어준다. byteArray.prepend(header); // stream으로 byteArray 정보 전송 socketStream << byteArray; } else QMessageBox::critical(this,"QTCPServer","Socket doesn't seem to be opened"); } else QMessageBox::critical(this,"QTCPServer","Not connected"); } // [ex.02.11] void MainWindow::sendAttachment(QTcpSocket* socket, QString filePath) { if(socket) { if(socket->isOpen()) { // 전송 할 file 객체를 경로 지정해서 열고 QFile m_file(filePath); if(m_file.open(QIODevice::ReadOnly)) { // file 이름을 가져오고 QFileInfo fileInfo(m_file.fileName()); QString fileName(fileInfo.fileName()); // stream으로 보내는데 QDataStream socketStream(socket); socketStream.setVersion(QDataStream::Qt_5_15); // 헤더 부분에 fileType을 attachment로 설정한다. QByteArray header; header.prepend(QString("fileType:attachment,fileName:%1,fileSize:%2;").arg(fileName).arg(m_file.size()).toUtf8()); header.resize(128); // QByteArray에 file을 byte로 할당하고 QByteArray byteArray = m_file.readAll(); // header 정보를 앞에 넣어준다. byteArray.prepend(header); // stream으로 byteArray 정보 전송 socketStream << byteArray; } else QMessageBox::critical(this,"QTCPClient","Couldn't open the attachment!"); } else QMessageBox::critical(this,"QTCPServer","Socket doesn't seem to be opened"); } else QMessageBox::critical(this,"QTCPServer","Not connected"); } // [ex.02.12] void MainWindow::slot_displayMessage(const QString& str) { ui->textBrowser_receivedMessages->append(str); } // [ex.02.13] void MainWindow::refreshComboBox(){ ui->comboBox_receiver->clear(); ui->comboBox_receiver->addItem("Broadcast"); foreach(QTcpSocket* socket, qset_connectedSKT) ui->comboBox_receiver->addItem(QString::number(socket->socketDescriptor())); }
클라이언트
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QAbstractSocket> #include <QDebug> #include <QFile> #include <QFileDialog> #include <QHostAddress> #include <QMessageBox> #include <QMetaType> #include <QString> #include <QStandardPaths> #include <QTcpSocket> namespace Ui { class MainWindow; } // [ex.02] class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; QTcpSocket* m_socket; signals: void signal_newMessage(QString); private slots: void slot_discardSocket(); void slot_displayError(QAbstractSocket::SocketError socketError); void slot_readSocket(); void on_pushButton_sendMessage_clicked(); void on_pushButton_sendAttachment_clicked(); void slot_displayMessage(const QString& str); }; #endif // MAINWINDOW_H
#include "mainwindow.h" #include "ui_mainwindow.h" // [ex.02.1] // MainWindow 생성자 실행 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // [ex.02.1.1] // MainWindow 객체 생성과 동시에 생성자에서 서버로 연결 요청을 보내도록 실행 m_socket = new QTcpSocket(this); m_socket->connectToHost(QHostAddress::LocalHost,8080); if(m_socket->waitForConnected()) { // 연결 성공시 출력 ui->statusBar->showMessage("Connected to Server"); } else { QMessageBox::critical(this,"QTCPClient", QString("The following error occurred: %1.").arg(m_socket->errorString())); exit(EXIT_FAILURE); } // [ex.02.1.2] // 연결된 socket에 read 할 데이터가 들어오면, // 이 객체의(MainWindow) slot_readSocket 실행하여 처리 connect(m_socket, &QTcpSocket::readyRead, this, &MainWindow::slot_readSocket); // [ex.02.1.3] // signal_newMessage 시그널이 발생하면 (socket read 가 아닌, MainWindow 시그널) // slot_displayMessage 실행하여 UI에 출력 connect(this, &MainWindow::signal_newMessage, this, &MainWindow::slot_displayMessage); // [ex.02.1.4] // 연결된 소켓과 연결이 해제되면, // 이 객체의(MainWindow) slot_discardSocket 슬롯 함수 실행하여 처리 connect(m_socket, &QTcpSocket::disconnected, this, &MainWindow::slot_discardSocket); // [ex.02.1.4] // 연결된 소켓에 문제가 발생하면, // 이 객체의(MainWindow) slot_displayError 슬롯 함수 실행하여 처리 connect(m_socket, &QAbstractSocket::errorOccurred, this, &MainWindow::slot_displayError); } // [ex.02.2] MainWindow::~MainWindow() { // socket 해제 if(m_socket->isOpen()) m_socket->close(); delete ui; } // [ex.02.3] // 서버에서 연결이 끊어지면 동작 void MainWindow::slot_discardSocket() { m_socket->deleteLater(); m_socket=nullptr; ui->statusBar->showMessage("Disconnected!"); } // [ex.02.4] // 연결된 소켓에서 에러 발생하면 출력 void MainWindow::slot_displayError(QAbstractSocket::SocketError socketError) { switch (socketError) { case QAbstractSocket::RemoteHostClosedError: break; case QAbstractSocket::HostNotFoundError: QMessageBox::information(this, "QTCPClient", "The host was not found. Please check the host name and port settings."); break; case QAbstractSocket::ConnectionRefusedError: QMessageBox::information(this, "QTCPClient", "The connection was refused by the peer. Make sure QTCPServer is running, and check that the host name and port settings are correct."); break; default: QMessageBox::information(this, "QTCPClient", QString("The following error occurred: %1.").arg(m_socket->errorString())); break; } } // [ex.02.5] // 첨부파일 또는 메시지 수신 처리 void MainWindow::slot_readSocket() { // QByteArray 타입의 buffer를 만들고 QByteArray buffer; // 서버에 연결된 socket을 stream으로 연결한다. QDataStream socketStream(m_socket); socketStream.setVersion(QDataStream::Qt_5_15); // stream으로 데이터를 읽어들이고, buffer로 넘기면 socketStream.startTransaction(); socketStream >> buffer; // stream startTransaction 실행 문제시 에러 표시 후 함수 종료 if(!socketStream.commitTransaction()) { QString message = QString("%1 :: Waiting for more data to come..").arg(m_socket->socketDescriptor()); emit signal_newMessage(message); return; } // client 에서 보낸 payload(순수한 데이터, 전달 메시지)를 // buffer에서 처음 128 byte 부분만 읽어들여서 header 에 담고 fileType을 찾는다. QString header = buffer.mid(0,128); QString fileType = header.split(",")[0].split(":")[1]; // buffer의 128 byte 이후 부분을 buffer = buffer.mid(128); // fileType이 attachment 라면 파일 수신 로직을 실행하고 // fileType이 message 라면 문장 수신 로직을 실핸한다. if(fileType=="attachment") { // 파일 전송은, 1)저장될 파일 이름, 2) 파일 확장자 3) 파일 크기 정보가 필요하다. QString fileName = header.split(",")[1].split(":")[1]; QString ext = fileName.split(".")[1]; QString size = header.split(",")[2].split(":")[1].split(";")[0]; // 파일 전송 메시지를 받으면, 메시지 박스를 띄워서 전송 받을 것인지 확인한다. // 메시지 박스에서 yes를 선택하면 파일을 읽는다. if (QMessageBox::Yes == QMessageBox::question(this, "QTCPServer", QString("You are receiving an attachment from sd:%1 of size: %2 bytes, called %3. Do you want to accept it?").arg(m_socket->socketDescriptor()).arg(size).arg(fileName))) { // 저장될 파일의 경로를 설정하고, 파일 이름과, 확장자를 설정한다. QString filePath = QFileDialog::getSaveFileName(this, tr("Save File"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"+fileName, QString("File (*.%1)").arg(ext)); // file 객체를 위에서 설정한 경로를 기반으로 연결하고 QFile file(filePath); // file 객체를 열고, buffer에 들어있는 byte를 쓴다(내보낸다. 통신이랑 같다). if(file.open(QIODevice::WriteOnly)) { file.write(buffer); // 파일이 저장되는 것에 대한 메시지를 ui에 출력한다. QString message = QString("INFO :: Attachment from sd:%1 successfully stored on disk under the path %2").arg(m_socket->socketDescriptor()).arg(QString(filePath)); emit signal_newMessage(message); } else QMessageBox::critical(this,"QTCPServer", "An error occurred while trying to write the attachment."); } else { // 메시지 박스에서 No 전송 거부시 메시지를 출력한다. QString message = QString("INFO :: Attachment from sd:%1 discarded").arg(m_socket->socketDescriptor()); emit signal_newMessage(message); } } else if(fileType=="message") { // 전송된 메시지를 출력한다. QString message = QString("%1 :: %2").arg(m_socket->socketDescriptor()).arg(QString::fromStdString(buffer.toStdString())); emit signal_newMessage(message); } } // [ex.02.5] // 메시지를 보냄 void MainWindow::on_pushButton_sendMessage_clicked() { if(m_socket) { if(m_socket->isOpen()) { // ui에서 입력할 message를 가져와 QString str = ui->lineEdit_message->text(); // stream으로 보내는데 QDataStream socketStream(m_socket); socketStream.setVersion(QDataStream::Qt_5_15); // 헤더 부분에 fileType을 message로 설정한다. QByteArray header; header.prepend(QString("fileType:message,fileName:null,fileSize:%1;").arg(str.size()).toUtf8()); header.resize(128); // message 인코딩 설정하고, QByteArray에 할당하고 QByteArray byteArray = str.toUtf8(); // header 정보를 앞에 넣어준다. byteArray.prepend(header); // stream으로 byteArray 정보 전송 socketStream << byteArray; // 메시지 입력창 리셋 ui->lineEdit_message->clear(); } else QMessageBox::critical(this,"QTCPClient","Socket doesn't seem to be opened"); } else QMessageBox::critical(this,"QTCPClient","Not connected"); } void MainWindow::on_pushButton_sendAttachment_clicked() { if(m_socket) { if(m_socket->isOpen()) { // 파일 경로 가져오고, 경로 문제시 경고 출력 QString filePath = QFileDialog::getOpenFileName(this, ("Select an attachment"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ("File (*.json *.txt *.png *.jpg *.jpeg)")); if(filePath.isEmpty()) { QMessageBox::critical(this,"QTCPClient","You haven't selected any attachment!"); return; } // 전송 할 file 객체를 경로 지정해서 열고 QFile m_file(filePath); if(m_file.open(QIODevice::ReadOnly)) { // file 이름을 가져오고 QFileInfo fileInfo(m_file.fileName()); QString fileName(fileInfo.fileName()); // stream으로 보내는데 QDataStream socketStream(m_socket); socketStream.setVersion(QDataStream::Qt_5_15); // 헤더 부분에 fileType을 attachment로 설정한다. QByteArray header; header.prepend(QString("fileType:attachment,fileName:%1,fileSize:%2;").arg(fileName).arg(m_file.size()).toUtf8()); header.resize(128); // QByteArray에 file을 byte로 할당하고 QByteArray byteArray = m_file.readAll(); // header 정보를 앞에 넣어준다. byteArray.prepend(header); // stream으로 byteArray 정보 전송 socketStream << byteArray; } else QMessageBox::critical(this,"QTCPClient","Attachment is not readable!"); } else QMessageBox::critical(this,"QTCPClient","Socket doesn't seem to be opened"); } else QMessageBox::critical(this,"QTCPClient","Not connected"); } // [ex.02.12] void MainWindow::slot_displayMessage(const QString& str) { ui->textBrowser_receivedMessages->append(str); }
댓글을 사용할 수 없습니다.