Qt TCP Socket与WebSocket
网络通信是Qt框架的常见应用,本文以TCP
和WebSocket
为例介绍了二者实现信息收发的基本方法和细节,可以作为初始开发的基本demo,采用cmake方法管理基本文件依赖关系,本文暂不涉及界面开发部分,直接基于QObject而不是QWidget。
前置环境说明
在Linux环境下进行网络通信实验是比较方便的,Windows下则需要一些步骤去适配动态库差异,本文兼顾了两个系统运行,所以某些步骤对Linux环境可能是多余的,本文采用mingw来编译工程,应该提前检查环境变量,并且在Qt Creator-帮助-关于插件-搜索cmake
勾选"load"
。
Ubuntu防火墙检查
首次使用某端口进行通信时,常常需要打开防火墙,windows会有弹窗提示,点击允许即可,linux则使用ufw
检查:
1
2
3
4
5
6
7
8
9
10
11sudo ufw status
sudo ufw allow 8887 #开放端口
sudo ufw enable #启动防火墙
sudo ufw reload #重新加载规则
sudo ufw status #端口已经更新
## 其余规则
sudo ufw delete allow 8887 #取消端口许可
sudo ufw allow from 192.168.254.254 #增加ip许可
sudo ufw delete allow from 192.168.254.254 #取消ip许可
TCP Socket
文件架构如下: 1
2
3
4
5
6
7
8
9
10
11
12
13F:\QT\QT_CODE\TCP
│ clientSocket.cpp
│ clientSocket.h
│ CMakeLists.txt
│ serverSocket.cpp
│ serverSocket.h
├─client
│ client.cpp
│ CMakeLists.txt
├─server
│ CMakeLists.txt
│ server.cpp
└─libs
使用Qt实现客户端接收输入流、服务器端打印字符的效果,类似在Linux操作系统:网络编程中实现的TCP客户端的效果一样:
1 | #顶层Cmake |
Server设计
服务器监听端口连接和接收信息,这里仅使用了一对一通信,如果需要监听多个客户端需要额外创建客户端编号、数组管理即可;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//serverSocket.h
class ServerSocket:public QObject{
Q_OBJECT
public slots:
void getNewMsg();
public:
ServerSocket(QObject* parent = nullptr);
public:
QTcpServer* tcpServer = nullptr;
QTcpSocket* clientSocket;
};
1 | //serverSocket.cpp |
服务器实例化:构造一个实例监听 1
2
3
4
5
6
7
8
9
10
11
12
13
14//server.cpp
using namespace std;
int main(int argc, char*argv[]){
QCoreApplication app(argc, argv);
qDebug() << "This is server";
ServerSocket serverSocket;
return app.exec();
}1
2
3
4
5
6
7
8
9
10
11
12cmake_minimum_required(VERSION 3.5)
project(server)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE server.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE ..)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt5::Core
Qt5::Network
tcp #our dll
)
Client设计
连接9999端口,并通过循环从输入流获取信息并且发送:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//clientSocket.h
class ClientSocket:public QObject{
Q_OBJECT
public:
ClientSocket(QObject*parent = nullptr);
void SendMsg();
public slots:
void handleError(QAbstractSocket::SocketError error); //槽函数检查错误
private:
QTcpSocket* tcpClient;
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43//clientSocket.cpp
ClientSocket::ClientSocket(QObject*parent):QObject(parent){
tcpClient = new QTcpSocket(this);
tcpClient->connectToHost("127.0.0.1",9999);
if (!tcpClient->waitForConnected(3000)) { // 等待 3 秒连接
qDebug() << "Connection failed:" << tcpClient->errorString();
}
connect(tcpClient,&QTcpSocket::connected,[=](){
qDebug()<<"Client connected!!";
});
//Qt 5.15一下使用槽函数检查错误
connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), this, \
SLOT(handleError(QAbstractSocket::SocketError)));
}
void ClientSocket::handleError(QAbstractSocket::SocketError error){
qDebug()<<"Client Connect error:"<<error;
tcpClient->close();
tcpClient->deleteLater();
}
void ClientSocket::SendMsg(){
qDebug()<<tcpClient->state();
char buf[MAXSIZE];
qint64 len = 0;
while(1){
if(strncmp(buf,"quit",4)==0)
break;
fgets(buf,32,stdin);
buf[strlen(buf)-1] = '\0'; //避免回车空行
len += tcpClient->write(buf,strlen(buf));
tcpClient->flush(); //即刻刷新发送
qDebug()<<"Succeed Send:"<<len;
}
}1
2
3
4
5
6
7
8
9
10//client.cpp
int main(int argc, char* argv[]){
qDebug()<<"This is client";
ClientSocket clientSocket;
clientSocket.SendMsg();
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12cmake_minimum_required(VERSION 3.5)
project(client)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE client.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE ..)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt5::Core
Qt5::Network
tcp #our dll
)
效果与讨论
clientf发server收:
这里一个细节是此处在client的main函数中并没有使用事件循环,而是直接break掉并且return 0,在实际工程中这种写法并不多见,因为也很少使用while去阻塞一个进程任务,因为Qt的事件循环和事件驱动具有更加灵活的机制,所以以上while方法读取输入并不典型。
再者,如果需要实现全双工通信,按照原来的方法需要创建新线程或者新进程去监听和打印,在Qt元对象系统中则也有更方便的方法,因为Qt具有事件循环机制,例如可以通过定时监听输入、事件驱动的方式来实现全双工通信,以下WebSocket
实现了这两种方式的demo。
QWebSocket
TCP
是传输层的通信协议,而WebSocket
是基于TCP
应用层的全双工通信协议,其定义了更加多的消息交互细节,例如WebSocket
是面对消息通信,支持二进制和文本两种格式发送,也额外支持信道安全通信协议,例如非加密的ws和加密的wss(TLS加密),更常常用于信息发送和接受。对比http
协议,WebSocket
不仅支持客户端请求-服务器应答机制,也支持服务器直接发送事件,即客户端和服务器的通信地位是平等的。
定时监听输入
对于服务器和客户端,均设定定时任务,相隔100ms监听输入流,如果得到输入流就通过WebSocket
的sendTextMessage
将信息发出,同时服务器和客户端都通过connect
事件打印接收输出,从而实现双工通信,且无需while阻塞输入,文件组织同上述tcp,以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37#顶层Cmake
cmake_minimum_required(VERSION 3.5)
project(websocket)
set(CMAKE_AUTOMOC ON)
find_package(Qt5 REQUIRED
COMPONENTS
WebSockets
)
#输出动态库/可执行程序
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/build/debug)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/build/release)
endif()
#set(SOPATH ${CMAKE_SOURCE_DIR}/libs)
#set(LIBRARY_OUTPUT_PATH ${SOPATH})
add_library(${PROJECT_NAME} SHARED)
target_sources(${PROJECT_NAME} PRIVATE
webClient.cpp
webClient.h
webServer.cpp
webServer.h
)
target_include_directories(${PROJECT_NAME} PRIVATE .)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt5::Core
Qt5::WebSockets
)
add_subdirectory(server)
add_subdirectory(client)
Server设计
1 | //webServer.h |
1 | //webServer.cpp |
实例化服务器对象: 1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char*argv[]){
QCoreApplication app(argc, argv);
qDebug() << "This is Server";
WebServer wServer;
return app.exec();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15cmake_minimum_required(VERSION 3.5)
project(server)
add_executable(server)
target_sources(server PRIVATE
server.cpp
)
target_include_directories(server PRIVATE ..)
target_link_libraries(server PRIVATE
Qt5::Core
Qt5::WebSockets
websocket
)
Client设计
客户端除了和服务器相同发送和接收策略,因为WebSocket
没有专门的wait
函数,这里还引入了超时检查,提高客户端连接的鲁棒性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//webClient.h
class WebClient:public QObject{
Q_OBJECT
public:
WebClient(QObject* parent = nullptr);
void SendTextMsg();
void sendMsg();
void onHandleSend();
bool waitEvent(int timesec);
void onClose();
public slots:
void handleError(QAbstractSocket::SocketError);
private:
QWebSocket* webClient;
QTimer* inputTimer;
qint64 sendLen;
};
1 | //webClient.cpp |
因为没有用while阻塞,这里正常使用事件循环代表客户端持续监听:
1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char* argv[]){
QCoreApplication app(argc, argv);
qDebug()<<"This is Client";
WebClient webClient;
return app.exec();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15cmake_minimum_required(VERSION 3.5)
project(client)
add_executable(client)
target_sources(client PRIVATE
client.cpp
)
target_include_directories(client PRIVATE ..)
target_link_libraries(client PRIVATE
Qt5::Core
Qt5::WebSockets
websocket
)
效果
至此,基于定时监听输入、事件驱动发送、接受的模式就写完了,双端都可以实现信息的发送和接收:
事件监听输入
windows的cmd/powershell
默认为阻塞输入,对于非阻塞监听程序就处于不可输入状态,因此本方法只在Linux环境奏效:
去掉定时器对象成员,改为QSocketNotifier
监听,定义成员变量:
1
QSocketNotifier* noti;
1
2
3
4
5//init
noti = new QSocketNotifier(0,QSocketNotifier::Read, this); //0代表stdin
//signal
connect(noti, &QSocketNotifier::activated, this, &WebClient::sendMsg);
处理输入流: 1
2
3
4
5
6
7
8void WebClient::sendMsg() {
Q_ASSERT(webClient);
QTextStream cin(stdin);
QString input;
cin.readLineInto(&input);
sendLen += webClient->sendTextMessage(input);
qDebug() << "sendlen:" << sendLen;
}
Q&A
- Qt
Creator不支持查找
CMakeLists
,仅查找.pro
文件;
- 没有在帮助-关于插件处load cmake;
- 编译报错
error: undefined reference to 'vtable for ClientSocket'
或典型的LNK2001 error
:1
2
3
4error LNK2001: 无法解析的外部符号 "public: virtual struct QMetaObject const * __thiscall TraceTest::metaObject(void)const " (?metaObject@TraceTest@@UBEPBUQMetaObject@@XZ)
error LNK2001: 无法解析的外部符号 "public: virtual void * __thiscall TraceTest::qt_metacast(char const *)" (?qt_metacast@TraceTest@@UAEPAXPBD@Z)
error LNK2001: 无法解析的外部符号 "public: virtual int __thiscall TraceTest::qt_metacall(enum QMetaObject::Call,int,void * *)" (?qt_metacall@TraceTest@@UAEHW4Call@QMetaObject@@HPAPAX@Z)
fatal error LNK1120: 3 个无法解析的外部命令
- MOC文件没有引入,如果是cmake构建的项目必须加上
set(CMAKE_AUTOMOC ON)
代表使用moc生成必要文件;