今日头条/西瓜视频/抖音短视频 同名:正点原子
原子哥今日头条/西瓜视频/抖音短视频账号:正点原子-原子哥
感谢各位的关注和支持,你们的关注和支持是正点原子无限前进的动力。
第十一章《网络编程》
由于本章内容较多,所以第十一章《网络编程》将会分为几个部分进行内容的发布,更多文章内容请持续关注今日头条正点原子官方账号。
11.2.3 TCP客户端应用实例
本例目的:了解TCP客户的使用。
例09_tcpclient,TCP客户端(难度:一般)。项目路径为Qt/2/09_ tcpclient。本例大体流程:首先获取本地IP地址。创建一个tcpSocket套接字,然后用tcpSocket套接字使用connectToHost函数连接服务端的主机IP地址和端口,即可相互通信。
项目文件08_tcpserver.pro文件第一行添加的代码部分如下。
09_tcpclient.pro编程后的代码
1 QT += core gui network
2
3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4
5 CONFIG += c++11
6
7 # The following define makes your compiler emit warnings if you use
8 # any Qt feature that has been marked deprecated (the exact warnings
9 # depend on your compiler). Please consult the documentation of the
10 # deprecated API in order to know how to port your code away from it.
11 DEFINES += QT_DEPRECATED_WARNINGS
12
13 # You can also make your code fail to compile if it uses deprecated APIs.
14 # In order to do so, uncomment the following line.
15 # You can also select to disable deprecated APIs only up to a certain version of Qt.
16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
17
18 SOURCES += \
19 main.cpp \
20 mainwindow.cpp
21
22 HEADERS += \
23 mainwindow.h
24
25 # Default rules for deployment.
26 qnx: target.path = /tmp/${TARGET}/bin
27 else: unix:!android: target.path = /opt/${TARGET}/bin
28 !isEmpty(target.path): INSTALLS += target
在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码
/******************************************************************
Copyright ? Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 09_tcpclient
* @brief mainwindow.h
* @author Deng Zhimao
* @email 1252699831@qq.com
* @net www.openedv.com
* @date 2021-04-13
*******************************************************************/
1 #ifndef MAINWINDOW_H
2 #define MAINWINDOW_H
3
4 #include <QMainWindow>
5 #include <QTcpServer>
6 #include <QTcpSocket>
7 #include <QVBoxLayout>
8 #include <QHBoxLayout>
9 #include <QPushButton>
10 #include <QTextBrowser>
11 #include <QLabel>
12 #include <QComboBox>
13 #include <QSpinBox>
14 #include <QHostInfo>
15 #include <QLineEdit>
16 #include <QNetworkInterface>
17 #include <QDebug>
18
19 class MainWindow : public QMainWindow
20 {
21 Q_OBJECT
22
23 public:
24 MainWindow(QWidget *parent = nullptr);
25 ~MainWindow();
26
27 private:
28 /* 通信套接字 */
29 QTcpSocket *tcpSocket;
30
31 /* 按钮 */
32 QPushButton *pushButton[4];
33
34 /* 标签文本 */
35 QLabel *label[2];
36
37 /* 水平容器 */
38 QWidget *hWidget[3];
39
40 /* 水平布局 */
41 QHBoxLayout *hBoxLayout[3];
42
43 /* 垂直容器 */
44 QWidget *vWidget;
45
46 /* 垂直布局 */
47 QVBoxLayout *vBoxLayout;
48
49 /* 文本浏览框 */
50 QTextBrowser *textBrowser;
51
52 /* 用于显示本地ip */
53 QComboBox *comboBox;
54
55 /* 用于选择端口 */
56 QSpinBox *spinBox;
57
58 /* 文本输入框 */
59 QLineEdit *lineEdit;
60
61 /* 存储本地的ip列表地址 */
62 QList<QHostAddress> IPlist;
63
64 /* 获取本地的所有ip */
65 void getLocalHostIP();
66
67 private slots:
68 /* 连接 */
69 void toConnect();
70
71 /* 断开连接 */
72 void toDisConnect();
73
74 /* 已连接 */
75 void connected();
76
77 /* 已断开连接 */
78 void disconnected();
79
80 /* 清除文本框时的内容 */
81 void clearTextBrowser();
82
83 /* 接收到消息 */
84 void receiveMessages();
85
86 /* 发送消息 */
87 void sendMessages();
88
89 /* 连接状态改变槽函数 */
90 void socketStateChange(QAbstractSocket::SocketState);
91 };
92 #endif // MAINWINDOW_H
头文件里主要是声明界面用的元素,及一些槽函数。重点是声明tcpSocket。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码
/******************************************************************
Copyright ? Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 09_tcpclient
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email 1252699831@qq.com
* @net www.openedv.com
* @date 2021-04-13
*******************************************************************/
1 #include "mainwindow.h"
2
3 MainWindow::MainWindow(QWidget *parent)
4 : QMainWindow(parent)
5 {
6 /* 设置主窗体的位置与大小 */
7 this->setGeometry(0, 0, 800, 480);
8
9 /* tcp套接字 */
10 tcpSocket = new QTcpSocket(this);
11
12 /* 开始监听按钮 */
13 pushButton[0] = new QPushButton();
14 /* 停止监听按钮 */
15 pushButton[1] = new QPushButton();
16 /* 清空聊天文本按钮 */
17 pushButton[2] = new QPushButton();
18 /* 发送消息按钮 */
19 pushButton[3] = new QPushButton();
20
21 /* 水平布局一 */
22 hBoxLayout[0] = new QHBoxLayout();
23 /* 水平布局二 */
24 hBoxLayout[1] = new QHBoxLayout();
25 /* 水平布局三 */
26 hBoxLayout[2] = new QHBoxLayout();
27 /* 水平布局四 */
28 hBoxLayout[3] = new QHBoxLayout();
29
30 /* 水平容器一 */
31 hWidget[0] = new QWidget();
32 /* 水平容器二 */
33 hWidget[1] = new QWidget();
34 /* 水平容器三 */
35 hWidget[2] = new QWidget();
36
37
38 vWidget = new QWidget();
39 vBoxLayout = new QVBoxLayout();
40
41 /* 标签实例化 */
42 label[0] = new QLabel();
43 label[1] = new QLabel();
44
45 lineEdit = new QLineEdit();
46 comboBox = new QComboBox();
47 spinBox = new QSpinBox();
48 textBrowser = new QTextBrowser();
49
50 label[0]->setText("服务器地址:");
51 label[1]->setText("服务器端口:");
52
53 /* 设置标签根据文本文字大小自适应大小 */
54 label[0]->setSizePolicy(QSizePolicy::Fixed,
55 QSizePolicy::Fixed);
56 label[1]->setSizePolicy(QSizePolicy::Fixed,
57 QSizePolicy::Fixed);
58
59 /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
60 spinBox->setRange(10000, 99999);
61
62 pushButton[0]->setText("连接服务器");
63 pushButton[1]->setText("断开连接");
64 pushButton[2]->setText("清空文本");
65 pushButton[3]->setText("发送消息");
66
67 /* 设置停止监听状态不可用 */
68 pushButton[1]->setEnabled(false);
69
70 /* 设置输入框默认的文本 */
71 lineEdit->setText("广州星翼电子科技有限公司");
72
73 /* 水平布局一添加内容 */
74 hBoxLayout[0]->addWidget(pushButton[0]);
75 hBoxLayout[0]->addWidget(pushButton[1]);
76 hBoxLayout[0]->addWidget(pushButton[2]);
77
78 /* 设置水平容器的布局为水平布局一 */
79 hWidget[0]->setLayout(hBoxLayout[0]);
80
81 hBoxLayout[1]->addWidget(label[0]);
82 hBoxLayout[1]->addWidget(comboBox);
83 hBoxLayout[1]->addWidget(label[1]);
84 hBoxLayout[1]->addWidget(spinBox);
85
86 /* 设置水平容器的布局为水平布局二 */
87 hWidget[1]->setLayout(hBoxLayout[1]);
88
89 /* 水平布局三添加内容 */
90 hBoxLayout[2]->addWidget(lineEdit);
91 hBoxLayout[2]->addWidget(pushButton[3]);
92
93 /* 设置水平容器三的布局为水平布局一 */
94 hWidget[2]->setLayout(hBoxLayout[2]);
95
96 /* 垂直布局添加内容 */
97 vBoxLayout->addWidget(textBrowser);
98 vBoxLayout->addWidget(hWidget[1]);
99 vBoxLayout->addWidget(hWidget[0]);
100 vBoxLayout->addWidget(hWidget[2]);
101
102 /* 设置垂直容器的布局为垂直布局 */
103 vWidget->setLayout(vBoxLayout);
104
105 /* 居中显示 */
106 setCentralWidget(vWidget);
107
108 /* 获取本地ip */
109 getLocalHostIP();
110
111 /* 信号槽连接 */
112 connect(pushButton[0], SIGNAL(clicked()),
113 this, SLOT(toConnect()));
114 connect(pushButton[1], SIGNAL(clicked()),
115 this, SLOT(toDisConnect()));
116 connect(pushButton[2], SIGNAL(clicked()),
117 this, SLOT(clearTextBrowser()));
118 connect(pushButton[3], SIGNAL(clicked()),
119 this, SLOT(sendMessages()));
120 connect(tcpSocket, SIGNAL(connected()),
121 this, SLOT(connected()));
122 connect(tcpSocket, SIGNAL(disconnected()),
123 this, SLOT(disconnected()));
124 connect(tcpSocket, SIGNAL(readyRead()),
125 this, SLOT(receiveMessages()));
126 connect(tcpSocket,
127 SIGNAL(stateChanged(QAbstractSocket::SocketState)),
128 this,
129 SLOT(socketStateChange(QAbstractSocket::SocketState)));
130 }
131
132 MainWindow::~MainWindow()
133 {
134 }
135
136 void MainWindow::toConnect()
137 {
138 /* 如果连接状态还没有连接 */
139 if (tcpSocket->state() != tcpSocket->ConnectedState) {
140 /* 指定IP地址和端口连接 */
141 tcpSocket->connectToHost(IPlist[comboBox->currentIndex()],
142 spinBox->value());
143 }
144 }
145
146 void MainWindow::toDisConnect()
147 {
148 /* 断开连接 */
149 tcpSocket->disconnectFromHost();
150
151 /* 关闭socket*/
152 tcpSocket->close();
153 }
154
155 void MainWindow::connected()
156 {
157 /* 显示已经连接 */
158 textBrowser->append("已经连上服务端");
159
160 /* 设置按钮与下拉列表框的状态 */
161 pushButton[0]->setEnabled(false);
162 pushButton[1]->setEnabled(true);
163 comboBox->setEnabled(false);
164 spinBox->setEnabled(false);
165 }
166
167 void MainWindow::disconnected()
168 {
169 /* 显示已经断开连接 */
170 textBrowser->append("已经断开服务端");
171
172 /* 设置按钮与下拉列表框的状态 */
173 pushButton[1]->setEnabled(false);
174 pushButton[0]->setEnabled(true);
175 comboBox->setEnabled(true);
176 spinBox->setEnabled(true);
177 }
178
179 /* 获取本地IP */
180 void MainWindow::getLocalHostIP()
181 {
182 // /* 获取主机的名称 */
183 // QString hostName = QHostInfo::localHostName();
184
185 // /* 主机的信息 */
186 // QHostInfo hostInfo = QHostInfo::fromName(hostName);
187
188 // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
189 // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
190 // IPlist = hostInfo.addresses();
191 // qDebug()<<IPlist<<endl;
192
193 // /* 遍历IPlist */
194 // foreach (QHostAddress ip, IPlist) {
195 // if (ip.protocol() == QAbstractSocket::IPv4Protocol)
196 // comboBox->addItem(ip.toString());
197 // }
198
199 /* 获取所有的网络接口,
200 * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
201 QList<QNetworkInterface> list
202 = QNetworkInterface::allInterfaces();
203
204 /* 遍历list */
205 foreach (QNetworkInterface interface, list) {
206
207 /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
208 QList<QNetworkAddressEntry> entryList
209 = interface.addressEntries();
210
211 /* 遍历entryList */
212 foreach (QNetworkAddressEntry entry, entryList) {
213 /* 过滤IPv6地址,只留下IPv4 */
214 if (entry.ip().protocol() ==
215 QAbstractSocket::IPv4Protocol) {
216 comboBox->addItem(entry.ip().toString());
217 /* 添加到IP列表中 */
218 IPlist<<entry.ip();
219 }
220 }
221 }
222 }
223
224 /* 清除文本浏览框里的内容 */
225 void MainWindow::clearTextBrowser()
226 {
227 /* 清除文本浏览器的内容 */
228 textBrowser->clear();
229 }
230
231 /* 客户端接收消息 */
232 void MainWindow::receiveMessages()
233 {
234 /* 读取接收到的消息 */
235 QString messages = tcpSocket->readAll();
236 textBrowser->append("服务端:" + messages);
237 }
238
239 /* 客户端发送消息 */
240 void MainWindow::sendMessages()
241 {
242 if(NULL == tcpSocket)
243 return;
244
245 if(tcpSocket->state() == tcpSocket->ConnectedState) {
246 /* 客户端显示发送的消息 */
247 textBrowser->append("客户端:" + lineEdit->text());
248
249 /* 发送消息 */
250 tcpSocket->write(lineEdit->text().toUtf8().data());
251 }
252 }
253
254 /* 客户端状态改变 */
255 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
256 {
257 switch (state) {
258 case QAbstractSocket::UnconnectedState:
259 textBrowser->append("scoket状态:UnconnectedState");
260 break;
261 case QAbstractSocket::ConnectedState:
262 textBrowser->append("scoket状态:ConnectedState");
263 break;
264 case QAbstractSocket::ConnectingState:
265 textBrowser->append("scoket状态:ConnectingState");
266 break;
267 case QAbstractSocket::HostLookupState:
268 textBrowser->append("scoket状态:HostLookupState");
269 break;
270 case QAbstractSocket::ClosingState:
271 textBrowser->append("scoket状态:ClosingState");
272 break;
273 case QAbstractSocket::ListeningState:
274 textBrowser->append("scoket状态:ListeningState");
275 break;
276 case QAbstractSocket::BoundState:
277 textBrowser->append("scoket状态:BoundState");
278 break;
279 default:
280 break;
281 }
282 }
上面的代码主要是客户端开使用connectToHost通过IP地址和端口与服务端连接,如果连接成功,就会发射connected ()信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用tcpSocket发送消息。注意发送消息和接收消息都是通过tcpSocket的read()和write()进行。
11.2.4程序运行效果
开启服务端后,需要选择本地监听的IP地址和监听的端口(特别需要注意,不要选择监听的端口与本地主机的已经使用的端口,所以编者把端口号设置的特别大,查看本地已经使用的端口号可以使用netstat指令。)
启动客户端后,选择需要连接的服务器IP地址和服务器监听的端口。点击连接后就可以相互发送消息了。
注意服务端和客户端都本例都是选择了本地环回IP 127.0.0.1测试。也可以选择本地的其他IP地址进行测试。
TCP服务端:
TCP客户端:
未完待续....