1 liunx socket通信流程
1.1 TCP/IP通信流程图
1.2 UDP通信流程图
2 Qt中的Socket通信
Qt中提供的所有的Socket类都是非阻塞的。
Qt中常用的用于socket通信的套接字类:
1 2 3 4
| QTcpServer QTcpSocket QUdpSocket
|
注意:在Qt中使用上述类时需要在.pro文件中写入 QT += network
2.1 TCP/IP
2.1.1 服务器端通信流程
- 创建套接字
- 将套接字设置为监听模式
- 等待并接受客户端请求
可以通过QTcpServer提供的void newConnection()信号来检测是否有连接请求,如果有可以在对应的槽函数中调用nextPendingConnection函数获取到客户端的Socket信息(返回值为QTcpSocket*类型指针),通过此套接字与客户端之间进行通信。
- 接收或者向客户端发送数据
- 接收数据:使用read()或者readAll()函数
- 发送数据:使用write()函数
2.1.2 服务器端代码实现
通过Qt提供的QTcpServer类实现服务器端的socket通信:
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
|
#ifndef SERVERWIDGET_H #define SERVERWIDGET_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> namespace Ui { class ServerWidget; } class ServerWidget : public QWidget { Q_OBJECT public: explicit ServerWidget(QWidget *parent = 0); ~ServerWidget(); private slots: void on_sendButton_clicked(); void on_closeButton_clicked(); private: Ui::ServerWidget *ui; QTcpServer * tcpserver; QTcpSocket * tcpsocket; }; #endif
|
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
#include "serverwidget.h" #include "ui_serverwidget.h" ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ServerWidget) { ui->setupUi(this); setWindowTitle("服务器"); tcpserver = NULL; tcpsocket = NULL; tcpserver = new QTcpServer(this); tcpserver->listen(QHostAddress::Any,8888); connect(tcpserver,&QTcpServer::newConnection, [=]() { tcpsocket = tcpserver->nextPendingConnection(); qint32 ip = tcpsocket->peerAddress().toIPv4Address(); qint16 port = tcpsocket->peerPort(); QString temp = QString("[%1:%2]连接成功").arg(ip).arg(port); ui->showBrowser->setText(temp); connect(tcpsocket,&QTcpSocket::readyRead, [=]() { QByteArray array = tcpsocket->readAll(); ui->showBrowser->append(array); } ); } ); } void ServerWidget::on_sendButton_clicked() { if(NULL == tcpsocket) { return; } QString str = ui->sendBrowser->toPlainText(); tcpsocket->write(str.toUtf8().data()); }
void ServerWidget::on_closeButton_clicked() { if(NULL == tcpsocket) { return; } tcpsocket->disconnectFromHost(); tcpsocket->close(); ui->showBrowser->append("已断开连接"); tcpsocket = NULL; } ServerWidget::~ServerWidget() { delete ui; }
|
2.1.3客户端通信流程
创建套接字
连接服务器
可以使用QTcpSocket类的connectToHost()函数来连接服务器。
向服务器发送或者接受数据
2.1.4 客户端代码实现
客户端通过使用Qt提供的QTcpSocket类可以方便的实现与服务器端的通信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #ifndef CLIENTWIDGET_H #define CLIENTWIDGET_H #include <QWidget> #include <QTcpSocket> namespace Ui { class ClientWidget; } class ClientWidget : public QWidget { Q_OBJECT public: explicit ClientWidget(QWidget *parent = 0); ~ClientWidget(); private slots: void on_connectButton_clicked(); void on_sendButton_clicked(); void on_disconnectButton_clicked(); private: Ui::ClientWidget *ui; QTcpSocket * tcpsocket; }; #endif
|
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include "clientwidget.h" #include "ui_clientwidget.h" #include <QHostAddress> #define Debug qDebug()<<"["<<__FILE__<<":"<<__LINE__<<"]" ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ClientWidget) { ui->setupUi(this); setWindowTitle("客户端"); tcpsocket = NULL; tcpsocket = new QTcpSocket(this); connect(tcpsocket,&QTcpSocket::connected, [=]() { qint32 ip = tcpsocket->peerAddress().toIPv4Address(); qint16 port = tcpsocket->peerPort(); QString temp = QString("和服务器[%1:%2]连接成功").arg(ip).arg(port); ui->textshow->setText(temp); } ); connect(tcpsocket,&QTcpSocket::readyRead, [=]() { QByteArray array = tcpsocket->readAll(); ui->textshow->append(array); } ); } void ClientWidget::on_connectButton_clicked() { QString ip = ui->ip->text(); qint16 port = ui->port->text().toInt(); Debug<<QHostAddress(ip)<<port; tcpsocket->connectToHost(QHostAddress(ip),port); } void ClientWidget::on_sendButton_clicked() { if(NULL == tcpsocket) { return; } QString str = ui->textsend->toPlainText(); tcpsocket->write(str.toUtf8().data()); } void ClientWidget::on_disconnectButton_clicked() { if(NULL == tcpsocket) { return; } tcpsocket->disconnectFromHost(); tcpsocket->close(); ui->textshow->append("已断开连接"); tcpsocket =NULL; } ClientWidget::~ClientWidget() { delete ui; }
|
2.2 UDP
使用Qt提供的QUdpSocket进行UDP通信。在UDP方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发送数据。类似的服务器也不从客户端接收连接,只负责调用接收函数,等待来自客户端的数据的到达。
2.2.1UDP通信流程
在UDP通信中,服务器端和客户端的概念已经显得有些淡化,两部分做的工作都大致相同:
创建套接字
绑定套接字
在UDP中如果需要接收数据则需要对套接字进行绑定,发送数据则不需要对套接字进行绑定。
通过调用bind()函数将套接字绑定到指定端口上。
接收或者发送数据
接收数据:使用readDatagram()函数
接收数据函数声明如下:
1 2 3 4 5 6
| qint64 readDatagram(char* data,qint64 maxSize,QHostAddress* address = 0,quint16 * port = 0)
|
使用pendingDatagramSize()可以获取到将要接收的数据的大小,根据该函数返回值来准备对应大小的内存空间存放将要接收的数据。
- 发送数据: 使用writeDatagram()函数
- 发送数据函数声明如下:
1 2 3 4 5
| qint64 writeDatagram(const QByteArray & datagram,const QHostAddress & host, quint16 port)
|
2.2.2UDP通信代码实现
在UDP通信中,服务器和客服端基本没什么区别,在这里进行局域网广播的方式进行自己发数据,自己接受数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
#ifndef UDPWIDGET_H #define UDPWIDGET_H #include <QWidget> #include <QUdpSocket> namespace Ui { class UDPWidget; } class UDPWidget : public QWidget { Q_OBJECT public: explicit UDPWidget(QWidget *parent = 0); ~UDPWidget(); private slots: void on_sendButton_clicked(); private: Ui::UDPWidget *ui; QUdpSocket * udpsocket; }; #endif
|
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 44 45 46 47 48 49 50 51 52 53 54
|
#include "udpwidget.h" #include "ui_udpwidget.h" #include <QHostAddress> UDPWidget::UDPWidget(QWidget *parent) : QWidget(parent), ui(new Ui::UDPWidget) { ui->setupUi(this); setWindowTitle("UDP通信端口:8888"); udpsocket = new QUdpSocket(this);
#if 0 udpsocket->joinMulticastGroup(QHostAddress("224.0.0.1")); udpsocket->bind(QHostAddress::AnyIPv4,8888); #elif 0 udpsocket->leaveMulticastGroup(QHostAddress("224.0.0.1")); #else udpsocket->bind(8888); #endif connect(udpsocket,&QUdpSocket::readyRead, [=]() { char buff[1024] = {0}; QHostAddress ip; quint16 port; qint64 len = udpsocket->readDatagram(buff,sizeof(buff),&ip,&port); if(len>0) { QString str = QString("[%1:%2\t%3]") .arg(ip.toString()) .arg(port) .arg(buff); ui->textEdit->append("{receive:}"+str+"\n"); } } ); } void UDPWidget::on_sendButton_clicked() { QString ip = ui->ip->text(); quint16 port = ui->port->text().toInt(); if(NULL == ip) { return; } QString str = ui->textEdit->toPlainText(); udpsocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port); } UDPWidget::~UDPWidget() { delete ui; }
|
2.2.3 广播
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为广播地址:QHostAddress::Broadcast此设置相当于QHostAddress(“255.255.255.255”)
使用UDP广播的的特点:
- 使用UDP进行广播,局域网内的其他的UDP用户全部可以收到广播的消息
2.2.4组播
我们再使用广播发送消息的时候会发送给所有用户,但是有些用户是不想接受消息的,这时候我们就应该使用组播,接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。另外组播是可以在Internet中使用的。
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为组播地址,关于组播地址的分类:
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
注册加入到组播地址需要使用QUdpSocket类的成员函数:
1 2
| bool joinMulticastGroup(const QHostAddress & groupAddress); bool leaveMulticastGroup(const QHostAddress & groupAddress);
|
2.3 TCP/IP 和 UDP的区别
|
TCP/IP |
UDP |
是否连接 |
面向连接 |
无连接 |
传输方式 |
基于流 |
基于数据报 |
传输可靠性 |
可靠 |
不可靠 |
传输效率 |
效率低 |
效率高 |
能否广播 |
不能 |
能 |