TCP 协议(Transmission Control Protocol)全称是传输控制协议是一种面向连接的、可靠的、
基于字节流的传输层通信协议。
TCP 通信必须先建立 TCP 连接,通信端分为客户端和服务端。服务端通过监听某个端口
来监听是否有客户端连接到来,如果有连接到来,则建立新的 socket 连接;客户端通过 ip 和
port 连接服务端,当成功建立连接之后,就可进行数据的收发了。需要注意的是,在 Qt 中,
Qt 把 socket 当成输入输出流来对待的,数据的收发是通过 read()和 write()来进行的,需要与我
们常见的 send()与 recv()进行区分。
TCP 客户端与服务端通信示意图如下。
本例目的:了解 TCP 服务端的使用。
例 08_tcpserver,TCP 服务端(难度:一般)。项目路径为 Qt/2/08_tcpserver。本例大体流程
首先获取本地 IP 地址。创建一个 tcpSocket 套接字,一个 tcpServer 服务端。点击监听即监听本
地的主机 IP 地址和端口,同时等待服务端的连接。此程序需要结合客户端一起使用。
项目文件 08_tcpserver.pro 文件第一行添加的代码部分如下。
08_tcpserver.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 08_tcpserver * @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 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 #include 17 #include 18 19 class MainWindow : public QMainWindow 20 { 21 Q_OBJECT 22 23 public: 24 MainWindow(QWidget *parent = nullptr); 25 ~MainWindow(); 26 27 private: 28 /* tcp 服务器 */ 29 QTcpServer *tcpServer; 30 31 /* 通信套接字 */ 32 QTcpSocket *tcpSocket; 33 34 /* 按钮 */ 35 QPushButton *pushButton[4]; 36 37 /* 标签文本 */ 38 QLabel *label[2]; 39 40 /* 水平容器 */
41 QWidget *hWidget[3]; 42 43 /* 水平布局 */ 44 QHBoxLayout *hBoxLayout[3]; 45 46 /* 垂直容器 */ 47 QWidget *vWidget; 48 49 /* 垂直布局 */ 50 QVBoxLayout *vBoxLayout; 51 52 /* 文本浏览框 */ 53 QTextBrowser *textBrowser; 54 55 /* 用于显示本地 ip */ 56 QComboBox *comboBox; 57 58 /* 用于选择端口 */ 59 QSpinBox *spinBox; 60 61 /* 文本输入框 */ 62 QLineEdit *lineEdit; 63 64 /* 存储本地的 ip 列表地址 */ 65 QList IPlist; 66 67 /* 获取本地的所有 ip */ 68 void getLocalHostIP(); 69 70 private slots: 71 /* 客户端连接处理槽函数 */ 72 void clientConnected(); 73 74 /* 开始监听槽函数 */ 75 void startListen(); 76 77 /* 停止监听槽函数 */ 78 void stopListen(); 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
头文件里主要是声明界面用的元素,及一些槽函数。重点是声明 tcpServer 和 tcpSocket。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp 编程后的代码
/****************************************************************** Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved. * @projectName 08_tcpserver * @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 服务器与 tcp 套接字 */ 10 tcpServer = new QTcpServer(this); 11 tcpSocket = new QTcpSocket(this); 12 13 /* 开始监听按钮 */ 14 pushButton[0] = new QPushButton(); 15 /* 停止监听按钮 */ 16 pushButton[1] = new QPushButton(); 17 /* 清空聊天文本按钮 */ 18 pushButton[2] = new QPushButton(); 19 /* 发送消息按钮 */ 20 pushButton[3] = new QPushButton(); 21 22 /* 水平布局一 */ 23 hBoxLayout[0] = new QHBoxLayout(); 24 /* 水平布局二 */ 25 hBoxLayout[1] = new QHBoxLayout(); 26 /* 水平布局三 */ 27 hBoxLayout[2] = new QHBoxLayout(); 28 /* 水平布局四 */ 29 hBoxLayout[3] = new QHBoxLayout(); 30 31 /* 水平容器一 */ 32 hWidget[0] = new QWidget(); 33 /* 水平容器二 */ 34 hWidget[1] = new QWidget(); 35 /* 水平容器三 */ 36 hWidget[2] = new QWidget(); 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("监听 IP 地址:"); 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("www.openedv.com 正点原子论坛"); 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 /* 水平布局二添加内容 */ 82 hBoxLayout[1]->addWidget(label[0]); 83 hBoxLayout[1]->addWidget(comboBox); 84 hBoxLayout[1]->addWidget(label[1]); 85 hBoxLayout[1]->addWidget(spinBox); 86 87 /* 设置水平容器二的布局为水平布局二 */ 88 hWidget[1]->setLayout(hBoxLayout[1]); 89 90 /* 水平布局三添加内容 */ 91 hBoxLayout[2]->addWidget(lineEdit); 92 hBoxLayout[2]->addWidget(pushButton[3]); 93 94 /* 设置水平容器三的布局为水平布局一 */ 95 hWidget[2]->setLayout(hBoxLayout[2]); 96 97 /* 垂直布局添加内容 */ 98 vBoxLayout->addWidget(textBrowser); 99 vBoxLayout->addWidget(hWidget[1]); 100 vBoxLayout->addWidget(hWidget[0]); 101 vBoxLayout->addWidget(hWidget[2]); 102 103 /* 设置垂直容器的布局为垂直布局 */ 104 vWidget->setLayout(vBoxLayout);
105 106 /* 居中显示 */ 107 setCentralWidget(vWidget); 108 109 /* 获取本地 ip */ 110 getLocalHostIP(); 111 112 /* 信号槽连接 */ 113 connect(pushButton[0], SIGNAL(clicked()), 114 this, SLOT(startListen())); 115 connect(pushButton[1], SIGNAL(clicked()), 116 this, SLOT(stopListen())); 117 connect(pushButton[2], SIGNAL(clicked()), 118 this, SLOT(clearTextBrowser())); 119 connect(pushButton[3], SIGNAL(clicked()), 120 this, SLOT(sendMessages())); 121 connect(tcpServer, SIGNAL(newConnection()), 122 this, SLOT(clientConnected())); 123 } 124 125 MainWindow::~MainWindow() 126 { 127 } 128 129 /* 新的客户端连接 */ 130 void MainWindow::clientConnected() 131 { 132 /* 获取客户端的套接字 */ 133 tcpSocket = tcpServer->nextPendingConnection(); 134 /* 客户端的 ip 信息 */ 135 QString ip = tcpSocket->peerAddress().toString(); 136 /* 客户端的端口信息 */ 137 quint16 port = tcpSocket->peerPort(); 138 /* 在文本浏览框里显示出客户端的连接信息 */ 139 textBrowser->append("客户端已连接"); 140 textBrowser->append("客户端 ip 地址:" 141 + ip); 142 textBrowser->append("客户端端口:" 143 + QString::number(port)); 144 145 connect(tcpSocket, SIGNAL(readyRead()), 146 this, SLOT(receiveMessages())); 147 connect(tcpSocket,
148 SIGNAL(stateChanged(QAbstractSocket::SocketState)), 149 this, 150 SLOT(socketStateChange(QAbstractSocket::SocketState))); 151 } 152 153 /* 获取本地 IP */ 154 void MainWindow::getLocalHostIP() 155 { 156 // /* 获取主机的名称 */ 157 // QString hostName = QHostInfo::localHostName(); 158 159 // /* 主机的信息 */ 160 // QHostInfo hostInfo = QHostInfo::fromName(hostName); 161 162 // /* ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到 163 // * IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP) */ 164 // IPlist = hostInfo.addresses(); 165 // qDebug()<addItem(ip.toString()); 171 // } 172 173 /* 获取所有的网络接口,
174 * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */ 175 QList list 176 = QNetworkInterface::allInterfaces(); 177 178 /* 遍历 list */ 179 foreach (QNetworkInterface interface, list) { 180 181 /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */ 182 QList entryList 183 = interface.addressEntries(); 184 185 /* 遍历 entryList */ 186 foreach (QNetworkAddressEntry entry, entryList) { 187 /* 过滤 IPv6 地址,只留下 IPv4 */ 188 if (entry.ip().protocol() == 189 QAbstractSocket::IPv4Protocol) { 190 comboBox->addItem(entry.ip().toString()); 191 /* 添加到 IP 列表中 */ 192 IPlist< 201 /* 需要判断当前主机是否有 IP 项 */ 202 if (comboBox->currentIndex() != -1) { 203 qDebug()<<"start listen"<listen(IPlist[comboBox->currentIndex()], 205 spinBox->value()); 206 207 /* 设置按钮与下拉列表框的状态 */ 208 pushButton[0]->setEnabled(false); 209 pushButton[1]->setEnabled(true); 210 comboBox->setEnabled(false); 211 spinBox->setEnabled(false); 212 213 /* 在文本浏览框里显示出服务端 */ 214 textBrowser->append("服务器 IP 地址:" 215 + comboBox->currentText()); 216 textBrowser->append("正在监听端口:" 217 + spinBox->text()); 218 } 219 } 220 221 /* 停止监听 */ 222 void MainWindow::stopListen() 223 { 224 qDebug()<<"stop listen"<close(); 227 228 /* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
229 * 因为 socket 未断开,还在监听上一次端口 */ 230 if (tcpSocket->state() == tcpSocket->ConnectedState) 231 tcpSocket->disconnectFromHost(); 232
233 /* 设置按钮与下拉列表框的状态 */ 234 pushButton[1]->setEnabled(false); 235 pushButton[0]->setEnabled(true); 236 comboBox->setEnabled(true); 237 spinBox->setEnabled(true); 238 239 /* 将停止监听的信息添加到文本浏览框中 */ 240 textBrowser->append("已停止监听端口:" 241 + spinBox->text()); 242 } 243 244 /* 清除文本浏览框里的内容 */ 245 void MainWindow::clearTextBrowser() 246 { 247 /* 清除文本浏览器的内容 */ 248 textBrowser->clear(); 249 } 250 251 /* 服务端接收消息 */ 252 void MainWindow::receiveMessages() 253 { 254 /* 读取接收到的消息 */ 255 QString messages = "客户端:" + tcpSocket->readAll(); 256 textBrowser->append(messages); 257 } 258 259 /* 服务端发送消息 */ 260 void MainWindow::sendMessages() 261 { 262 if(NULL == tcpSocket) 263 return; 264 265 /* 如果已经连接 */ 266 if(tcpSocket->state() == tcpSocket->ConnectedState) { 267 /* 发送消息 */ 268 tcpSocket->write(lineEdit->text().toUtf8().data()); 269 270 /* 在服务端插入发送的消息 */ 271 textBrowser->append("服务端:" + lineEdit->text()); 272 } 273 } 274 275 /* 服务端状态改变 */ 276 void MainWindow::socketStateChange(QAbstractSocket::SocketState
state) 277 { 278 switch (state) { 279 case QAbstractSocket::UnconnectedState: 280 textBrowser->append("scoket 状态:UnconnectedState"); 281 break; 282 case QAbstractSocket::ConnectedState: 283 textBrowser->append("scoket 状态:ConnectedState"); 284 break; 285 case QAbstractSocket::ConnectingState: 286 textBrowser->append("scoket 状态:ConnectingState"); 287 break; 288 case QAbstractSocket::HostLookupState: 289 textBrowser->append("scoket 状态:HostLookupState"); 290 break; 291 case QAbstractSocket::ClosingState: 292 textBrowser->append("scoket 状态:ClosingState"); 293 break; 294 case QAbstractSocket::ListeningState: 295 textBrowser->append("scoket 状态:ListeningState"); 296 break; 297 case QAbstractSocket::BoundState: 298 textBrowser->append("scoket 状态:BoundState"); 299 break; 300 default: 301 break; 302 } 303 }
上面的代码主要是服务端开启监听,如果有客户端连到服务端,就会发射 newConnection()
信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用 tcpSocket 发送消
息。注意发送消息和接收消息都是通过 tcpSocket 的 read()和 write()进行。
本例目的:了解 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 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 #include 17 #include 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 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()<addItem(ip.toString()); 197 // } 198 199 /* 获取所有的网络接口,
200 * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */ 201 QList list 202 = QNetworkInterface::allInterfaces(); 203 204 /* 遍历 list */ 205 foreach (QNetworkInterface interface, list) { 206 207 /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */ 208 QList 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< 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()
进行。
开启服务端后,需要选择本地监听的 IP 地址和监听的端口(特别需要注意,不要选择监听
的端口与本地主机的已经使用的端口,所以笔者把端口号设置的特别大,查看本地已经使用的
端口号可以使用 netstat 指令。)
启动客户端后,选择需要连接的服务器 IP 地址和服务器监听的端口。点击连接后就可以相
互发送消息了。
注意服务端和客户端都本例都是选择了本地环回 IP 127.0.0.1 测试。也可以选择本地的其他
IP 地址进行测试。
TCP 服务端:
TCP 客户端: