Qt Network 예제2
예제 실행
접속
접속 종료
예제 파일
Serv. 소스 코드
main.cpp
#include <QApplication>
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// [ex.01]
Widget w;
w.show();
return a.exec();
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "chatserver.h"
namespace Ui {
class Widget;
}
// [ex.02]
// Widget 클래스 : QWidget 상속
class Widget : public QWidget
{
Q_OBJECT
public:
// [ex.02.1]
explicit Widget(QWidget *parent = 0);
// [ex.02.2]
~Widget();
private:
Ui::Widget *ui;
// [ex.02.1.1]
QHostAddress m_hostAddress;
qint16 m_hostPort;
QString m_title;
QHostAddress getMyIP();
// [ex.02.1.2]
ChatServer *server;
private slots:
// [ex.02.3]
void slot_updateClntCNT(int users);
// [ex.02.4]
void slot_showMSG(QString msg);
};
#endif // WIDGET_H
widget.cpp
#include <QtNetwork>
#include "widget.h"
#include "chatserver.h"
#include "ui_widget.h"
// [ex.02.1]
// Widget 클래스의 생성자
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// [ex.02.1.1]
// 프로그램이 실행되는 디바이스의 IP, Port를 설정하고
// Widget의 타이틀을 설정에 반영한다.
m_hostAddress = getMyIP();
m_hostPort = qint16(25000);
m_title = "TCP 채팅 서버 (" + m_hostAddress.toString() + " " + QString::number(m_hostPort) + ")";
setWindowTitle(m_title);
// [ex.02.1.2]
// Widget 클래스는, ChatServer 객체를 맴버로 가진다.
// ChatServer 객체는 QTcpServer를 상속받고, 기능이 추가된 클래스다
// Server 역할을 하는 객체를 세팅하고, 클라이언트의 연결 요청을 대기하게 한다.
server = new ChatServer(this);
server->listen(m_hostAddress, m_hostPort);
// [ex.02.1.3]
// server 객체에 새로운 클라이언트가 들어오는 시그널이 발생하면
// Widget 클래스(=this, 자기 자신)의 slot_updateClntCNT 슬롯이 연결된다.
connect(server, SIGNAL(signal_updateClntCNT(int)),
this, SLOT(slot_updateClntCNT(int)));
// [ex.02.1.4]
// server 객체에서 새로운 메시지가 들어오는 시그널이 발생하면
// Widget 클래스(=this, 자기 자신)의 slot_showMSG 슬롯이 연결된다.
connect(server, SIGNAL(signal_showNewMSG(QString)),
this, SLOT(slot_showMSG(QString)));
}
// [ex.02.2] Widget 클래스의 파괴자
Widget::~Widget()
{
delete ui;
}
// [ex.02.3]
// server 객체에 새로운 클라이언트가 들어오면
// Widget 클래스(=this, 자기 자신)의 label Ui에 접속자 수를 업데이트 한다.
void Widget::slot_updateClntCNT(int users)
{
QString str = QString("접속자수 : %1").arg(users);
ui->label->setText(str);
}
// [ex.02.4]
// server 객체에 새로운 메시지가 들어오면
// Widget 클래스(=this, 자기 자신)의 textEdit Ui에 보여준다.
void Widget::slot_showMSG(QString msg)
{
ui->textEdit->append(msg);
}
// 프로그램이 실행되는 디바이스의 IP 반환
QHostAddress Widget::getMyIP()
{
QHostAddress myAddress;
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
// localhost(127.0.0.1) 가 아닌 것을 사용
for (int i = 0; i < ipAddressesList.size(); ++i)
{
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
ipAddressesList.at(i).toIPv4Address())
{
myAddress = ipAddressesList.at(i);
break;
}
}
// 인터넷이 연결되어 있지 않다면, localhost(127.0.0.1) 사용
if (myAddress.toString().isEmpty())
myAddress = QHostAddress(QHostAddress::LocalHost);
return myAddress;
}
chatserver.h
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <QTcpServer>
#include <QTcpSocket>
// [ex.03]
// ChatServer 클래스: QTcpServer 상속 받음
class ChatServer : public QTcpServer
{
Q_OBJECT
public:
// [ex.03.1]
ChatServer(QObject *parent=0);
~ChatServer();
protected:
// [ex.03.2]
// socketfd = Socket File Descriptor
void incomingConnection(qintptr socketfd);
private:
QSet<QTcpSocket *> qset_clntSKTList;
QMap<QTcpSocket *, QString> qmap_userList;
signals:
void signal_updateClntCNT(int No_Users);
void signal_showNewMSG(QString msg);
private slots:
// [ex.03.3]
void slot_readMSG();
// [ex.03.4]
void slot_sendUserInfoToAll();
// [ex.03.5]
void slot_disconnected();
};
#endif // CHATSERVER_H
chatserver.cpp
#include <QtWidgets>
#include <QRegExp>
#include <QDebug>
#include "chatserver.h"
// [ex.03.1]
// ChatServer의 생성자
ChatServer::ChatServer(QObject *parent): QTcpServer(parent)
{
}
ChatServer::~ChatServer()
{
foreach(QTcpSocket *client, qset_clntSKTList)
client->disconnectFromHost();
}
// [ex.03.2]
// 서버로 연결 요청이 들어오면 동작
void ChatServer::incomingConnection(qintptr socketfd)
{
qDebug() << Q_FUNC_INFO;
// [ex.03.2.1]
// 새로운 연결 소켓을 만들고
// 소켓에 파일 디스크립터를 식별한다.
QTcpSocket *newConnectedSKT = new QTcpSocket(this);
newConnectedSKT->setSocketDescriptor(socketfd);
// [ex.03.2.2]
// qset 을 사용해 연결된 소켓 객체들을 관리한다.
// qset 에 저장된 연결된 소켓(클라이언트)의 수를 전달하는 시그널을 동작시킨다.
qset_clntSKTList.insert(newConnectedSKT);
emit signal_updateClntCNT(qset_clntSKTList.count());
// [ex.03.2.3]
// 새로운 연결 소켓이 만들어질때
// 시그널을 동작시키고 정보 문자열을 매기변수로 넘긴다.
QString str = QString("새로운 접속자: %1").arg(newConnectedSKT->peerAddress().toString());
emit signal_showNewMSG(str);
// [ex.03.2.4]
// 새로운 연결 소켓에서 읽을 메시지가 들어와 readyRead() 시그널이 동작되면
// ChatServer 클래스(=this, 자기 자신)의 slot_readyRead 슬롯 함수가 연결된다.
connect(newConnectedSKT, SIGNAL(readyRead()),
this, SLOT(slot_readMSG()));
// [ex.03.2.5]
// 새로운 연결 소켓에서 disconnected 시그널이 동작되면
// ChatServer 클래스(=this, 자기 자신)의 slot_disconnected 슬롯 함수가 연결된다.
connect(newConnectedSKT, SIGNAL(disconnected()),
this, SLOT(slot_disconnected()));
}
// [ex.03.3]
void ChatServer::slot_readMSG()
{
// [ex.03.3.1]
// QObject *QObject::sender() const
// slot 에서만 리턴 유효한 함수로, 슬롯이 동작시킨 시그널의 객체를 반환한다.
// 즉, 읽을 메시지가 들어와 QTcpSocket의 readyRead() 시그널 소켓을 식별한다.
QTcpSocket* senderSKT = (QTcpSocket*)sender();
while(senderSKT->canReadLine())
{
QString line = QString::fromUtf8(senderSKT->readLine()).trimmed();
// QString str = QString("Read line: %1").arg(line);
// emit signal_showNewMSG(str);
// "Reg"ular "Exp"ression, 정규표현식
// regex_Me 객체 이름으로 ()안의 패턴이 정의된 정규 표현식이 생성됩니다.
QRegExp regex_Me("^/me:(.*)$");
// 클라이언트가 보낸 메시지가
// /me:가 포함된
// 접속자 이름을 알리는 메시지라면, 접속 정보를 모두에게 전송한다.
if(regex_Me.indexIn(line) != -1)
{
QString user = regex_Me.cap(1);
qmap_userList[senderSKT] = user;
foreach(QTcpSocket *SKT, qset_clntSKTList)
{
SKT->write(QString("[서버]: \"%1\" 접속하셨습니다.").arg(user).toUtf8());
}
// [ex.03.4]
slot_sendUserInfoToAll();
}
else if(qmap_userList.contains(senderSKT))
{
QString message = line;
QString user = qmap_userList[senderSKT];
QString str = QString("유저명: %1, 메시지: %2").arg(user, message);
emit signal_showNewMSG(str);
foreach(QTcpSocket *otherClient, qset_clntSKTList)
otherClient->write(QString(user+":"+message+"\n\n").toUtf8());
}
}
}
// [ex.03.4]
// 접속된 유저들 정보를 채팅방에 접속된 모든 사용자들에게 전송
// 새로운 접속자가 있거나, 접속자가 채팅방을 나갈때 사용
void ChatServer::slot_sendUserInfoToAll()
{
//QStringList에 접속된 유저 정보를 담아
QStringList userList2;
foreach(QString user, qmap_userList.values())
userList2 << user;
// 모든 접속자에게 전송
foreach(QTcpSocket *SKT, qset_clntSKTList)
SKT->write(QString("\n[접속된유저:" + userList2.join(",") + "]\n").toUtf8());
}
// [ex.03.5]
// 접속된 사용자와 연결이 끊길때 처리
void ChatServer::slot_disconnected()
{
// 접속 종료 시그널을 발생시킨 소켓을 찾아
QTcpSocket *leftSKT = (QTcpSocket*)sender();
// 접속 끊긴 소켓을 qset에서 제거하고
// 접속자 수를 업데이트한다.
qset_clntSKTList.remove(leftSKT);
emit signal_updateClntCNT(qset_clntSKTList.count());
// 접속 끊긴 소켓을 qmap에서 제거하고
// 접속자 수를 업데이트한다.
QString user = qmap_userList[leftSKT];
qmap_userList.remove(leftSKT);
// 서버 메시지창에 보여주고
QString str = QString("*연결 종료: ID:%1, IP:%2").arg(user, leftSKT->peerAddress().toString());
emit signal_showNewMSG(str);
// 현재 접속된 유저 리스트만 재전송
slot_sendUserInfoToAll();
// 접속 종료 메시지를 연결된 모든 접속자에게 전송
foreach(QTcpSocket *client, qset_clntSKTList)
client->write(QString("서버: %1 접속종료").arg(user).toUtf8());
}
Clnt. 소스 코드
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// [ex.01]
// 채팅기능이 있는 Widget을 숨긴다.
Widget w;
w.hide();
return a.exec();
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include "loginwidget.h"
namespace Ui {
class Widget;
}
// [ex.02]
class Widget : public QWidget
{
Q_OBJECT
public:
// [ex.02.1]
explicit Widget(QWidget *parent = nullptr);
// [ex.02.2]
~Widget();
private:
Ui::Widget *ui;
// [ex.02.1.1]
LoginWidget *loginWidget;
// [ex.02.1.2]
QTcpSocket *socket;
QString m_ipAddr;
QString m_userName;
private slots:
// [ex.02.3]
void slot_loginInfo(QString addr, QString name);
// [ex.02.4]
void slot_sendMyName();
// [ex.02.5]
void slot_readyRead();
// [ex.02.6]
void slot_sayButton_clicked();
// [ex.02.7]
void slot_disconnected();
};
#endif // WIDGET_H
widget.cpp
#include <QDebug>
#include <QRegExp>
#include "widget.h"
#include "ui_widget.h"
// [ex.02.1]
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->sayButton, &QPushButton::pressed,
this, &Widget::slot_sayButton_clicked);
connect(ui->sayLineEdit , &QLineEdit::returnPressed,
ui->sayButton, &QPushButton::pressed);
// [ex.02.1.1]
// loginWidget을 먼저 보여주고 IP, Name을 입력받아, 현재(this) Widget에 넘긴다.
// loginWidget 객체의 loginInfo 시그널이 발생되면
// Widget 클래스(=this, 자기 자신)의 slot_loginInfo 슬롯이 연결된다.
loginWidget = new LoginWidget();
connect(loginWidget, &LoginWidget::signal_loginInfo,
this, &Widget::slot_loginInfo);
loginWidget->show();
// [ex.02.1.2]
// 클라이어트에서 사용할 QTcpSocket 객체를 만들고
socket = new QTcpSocket(this);
connect(socket, SIGNAL(connected()),
this, SLOT(slot_sendMyName()));
connect(socket, SIGNAL(readyRead()),
this, SLOT(slot_readyRead()));
connect(socket, SIGNAL(disconnected()),
this, SLOT(slot_disconnected()));
}
// [ex.02.2]
Widget::~Widget()
{
delete ui;
}
// [ex.02.3]
// 클라이언트가 지정된 서버 IP로 연결
void Widget::slot_loginInfo(QString addr, QString name)
{
qDebug() << Q_FUNC_INFO << addr << name;
m_ipAddr = addr;
m_userName = name;
socket->connectToHost(m_ipAddr, 25000);
}
// [ex.02.4]
// 클라이언트 연결 소켓이 서버와 연결되면
// loginWidget을 숨기고, 접속자(나)의 이름을 서버에 전송한다.
void Widget::slot_sendMyName()
{
loginWidget->hide();
this->window()->show();
setWindowTitle("채팅 클라이언트 [ID:" + m_userName + "]");
socket->write(QString("/me:" + m_userName + "\n").toUtf8());
}
// [ex.02.5]
void Widget::slot_readyRead()
{
while(socket->canReadLine())
{
QString line = QString::fromUtf8(socket->readLine()).trimmed();
QRegExp messageRegex("^([^:]+):(.*)$");
if(messageRegex.indexIn(line) != -1)
{
QString user = messageRegex.cap(1);
QString message = messageRegex.cap(2);
ui->TextEdit_Chat->append("<b style=\"color:blue;\">"+user+"</b>: "+message);
}
}
}
// [ex.02.6]
void Widget::slot_sayButton_clicked()
{
QString message = ui->sayLineEdit->text().trimmed();
if(!message.isEmpty())
{
socket->write(QString(message + "\n").toUtf8());
}
ui->sayLineEdit->clear();
ui->sayLineEdit->setFocus();
}
// [ex.02.7]
// 서버에서 연결을 소켓 연결 종료 정보를 표시한다.
void Widget::slot_disconnected()
{
ui->TextEdit_Chat->append("-> 서버와 연결이 끊겼습니다");
qDebug() << Q_FUNC_INFO << "서버로부터 접속 종료.";
}
loginwidget.h
#ifndef LOGINWIDGET_H
#define LOGINWIDGET_H
#include "qhostaddress.h"
#include <QWidget>
namespace Ui {
class LoginWidget;
}
// [ex.03]
class LoginWidget : public QWidget
{
Q_OBJECT
public:
// [ex.03.1]
explicit LoginWidget(QWidget *parent = nullptr);
// [ex.03.2]
~LoginWidget();
private:
Ui::LoginWidget *ui;
QString m_servIP;
QString m_nickName;
QHostAddress getMyIP();
signals:
void signal_loginInfo(QString addr, QString name);
private slots:
// [ex.03.3]
void slot_loginBtnClicked();
};
#endif // LOGINWIDGET_H
login.cpp
#include <QtNetwork>
#include <QDateTime>
#include "loginwidget.h"
#include "ui_loginwidget.h"
// [ex.03.1]
// loginWidget을 먼저 보여주고 IP, Name을 입력받아,
// loginButton을 누르면, LoginWidget::slot_loginBtnClicked을 실행한다,
LoginWidget::LoginWidget(QWidget *parent) : QWidget(parent), ui(new Ui::LoginWidget)
{
ui->setupUi(this);
ui->ipLineEdit->setText(getMyIP().toString());
ui->nameLineEdit->setText(QTime::currentTime().toString("ss초 zz밀리초"));
connect(ui->loginButton, &QPushButton::pressed,
this, &LoginWidget::slot_loginBtnClicked);
connect(ui->ipLineEdit , &QLineEdit::returnPressed,
ui->loginButton, &QPushButton::pressed);
connect(ui->nameLineEdit , &QLineEdit::returnPressed,
ui->loginButton, &QPushButton::pressed);
}
// [ex.03.2]
LoginWidget::~LoginWidget()
{
delete ui;
}
// [ex.03.3]
void LoginWidget::slot_loginBtnClicked()
{
m_servIP = ui->ipLineEdit->text().trimmed();
m_nickName = ui->nameLineEdit->text().trimmed();
emit signal_loginInfo(m_servIP, m_nickName);
}
// 일반적으로 초기 테스트는 localhost(127.0.0.1) 를 사용해서 쓸 필요가 없는데
// 다른 팀원들 데스크탑 서버 접속 테스트하기 위해
// 비슷한 대역대 IP인 내 IP를 기본 세팅
QHostAddress LoginWidget::getMyIP()
{
QHostAddress myAddress;
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
// localhost(127.0.0.1) 가 아닌 것을 사용
for (int i = 0; i < ipAddressesList.size(); ++i)
{
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
ipAddressesList.at(i).toIPv4Address())
{
myAddress = ipAddressesList.at(i);
break;
}
}
// 인터넷이 연결되어 있지 않다면, localhost(127.0.0.1) 사용
if (myAddress.toString().isEmpty())
myAddress = QHostAddress(QHostAddress::LocalHost);
return myAddress;
}