TCP 和 UDP 通信
TCP
先通过一张图片了解TCP通信过程,QT的TCP Socket(套接字)通信仍然有服务端、客户端之分。图中左为客户端,右为服务端。服务端通过监听某个端口来监听是否有客户端连接到来,如果有连接到来,则建立新的SOCKET连接;客户端通过IP和PORT(端口)连接服务端,当成功建立连接之后,就可进行数据的接收和发送了。数据的收发是通过read()和write()来进行的。Socket,也就是常说的“套接字”。Socket简单地说,就是一个IP地址加一个port端口
TCP通信原理图
先来看运行结果:
服务端
- 新建项目,选择Qt Widgets应用,项目名为TCP_connect,类名为ServerWidget。
在 TCP_connect.pro内联网,添加以下代码,添加后先编译不运行。(之后的所有有关UDP和CDP,都要加上这句,以后不再多说。)
1
2
3QT += core gui network
CONFIG += C++11 //之后可能会用到c++中的Lambdas表达式,所以把这句也加上设计ui界面如生成结果中的服务端
- 在ServerWidget.h头文件中,添加头文件并,创建监听套接字和通信套接字对象。
1 |
|
- 在ServerWidget.h源文件构造函数添加以下
1 | setWindowTitle("服务器:8888"); |
- 发送消息(文本)按钮和关闭连接按钮,分别右击转到槽。添加代码
1 |
|
客户端
1.右击项目,添加c++class类,类名为clientwidget。设计ui界面如生成结果。
2.在clientwidget.h头文件中添加代码,通信套接字头文件和对象。
1 |
|
3.在clientwidget.cpp文件构造函数中添加代码,
1 | setWindowTitle("客户端"); |
- 三个按钮分别右击转到槽
1 |
|
UDP
UDP(User Datagram Protocol即用户数据报协议)是一个轻量级的,不可靠的,面向数据报的无连接协议。就像我们现在使用的QQ,其聊天时就是使用UDP协议进行消息发送的。像QQ那样,当有很多用户,发送的大部分都是短消息,要求能及时响应,并且对安全性要求不是很高的情况下使用UDP协议。即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
在Qt中提供了QUdpSocket 类来进行UDP数据报(datagrams)的发送(writeDatagram)和接收(readDatagram)。
先来看一下UDP通信过程
现在我们来创造下面如图所示的一个服务器:
新建项目,选择Qt Widgets应用,项目名为udpsocket,类名为Widget。
- 先根据上图设计界面
- 在头文件中添加通信套接字声明,再定义一个对象
1 |
|
- 分配空间和绑定端口,连接通信套接字自动触发readyRead()信号和处理的槽函数
先在头文件声明公有槽函数:1
void dealMag();//槽函数,处理对方发送过来的信号。
在构造函数添加: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
41Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
setWindowTitle("服务端口为:8888");
//分配空间,指定父对象
udpsocket= new QUdpSocket(this);
//绑定端口
udpsocket->bind(8888);
//udpsocket->bind(QHostAddress::AnyIPv4,8888);
//加入某个组播
//组播地址是D类地址
//udpsocket->joinMulticastGroup(QHostAddress("244.0.0.2"));
//udpsocket->leaveMulticastGroup(1);//退出组播
//当对方成功发送数据过来
//自动触发readyRead()信号
connect(udpsocket,&QUdpSocket::readyRead,this,&Widget::dealMag);
}
void Widget::dealMag()
{
//读取对方发送的内容
char buf[1024]={0};
QHostAddress clientAddr; //对方地址
quint16 clientPort; //对方端口
qint64 len=udpsocket->readDatagram(buf,sizeof(buf),&clientAddr,&clientPort);
if(len>0)
{
//接收内容格式化
QString str = QString("[%1:%2] %3")
.arg(clientAddr.toString())
.arg(clientPort)
.arg(buf);
ui->textEdit->setText(str);
}
}
- 两个按钮转到槽
1 |
|
- 根据以上方法就可以创建很多服务端(客户端),通过绑定的端口就可以相互通信了。
UDP组播问题
因为在实际项目中,用户有N个电脑预览实时视频,如果同时有N多个终端去连接服务器,服务器的压力发送数据带宽的压力很大,所以给提出采用组播的方式去解决此类的问题。就像QQ群一样,限定人数,避免广播风暴。组播的原理大致就是服务器往某一组播地址和端口发数据,之后客户端从指定的组播地址和端口去取数据,好处就是减轻了服务器发送的压力
开启多个服务器,用客户端发送数据,所有服务器端(复制的过程)都会收到客户端发送的数据。
组播实现在上个例子代码中注释部分有,需要注意:
- 发送端既可以加入组播,也可以不加入组播;
-吧服务端绑定的ip地址必须是ipv4地址;
- 组播地址必须是D类地址
TCP传输文件
先来看过传输文件程图:
运行结果:
首先看到运行结果如上图所示
新建项目,选择Qt Widgets应用,项目名为tcpfile,类名为ServerWidget。
服务端
- 根据运行结果设计界面.
- ServerWidget.h
1 |
|
ServerWidget.cpp中添加代码
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
setWindowTitle("服务器端口:8888");
//分配空间
tcpserver = new QTcpServer(this);
//监听
tcpserver->listen(QHostAddress::Any,8888);
//没建立连接前是不能对进行操作的
ui->buttonfile->setEnabled(false);
ui->buttonsend->setEnabled(false);
//自动触发newconnection
connect(tcpserver,&QTcpServer::newConnection,
[=]()
{
//取出建立好的连接套接字,获取ip和端口
tcpsocket = tcpserver->nextPendingConnection();
QString ip = tcpsocket->peerAddress().toString();
quint16 port = tcpsocket->peerPort();//无符号的
//格式化并显示在文本编辑框中
QString str = QString("[%1;%2] 连接成功!").arg(ip).arg(port);
ui->textEdit->setText(str);
//连接成功后,才能发送文件
ui->buttonfile->setEnabled(true);
});
connect(&timer,&QTimer::timeout,
[=]()
{
//关闭定时器
timer.stop();
//发送文件
sendData();
});
}
void ServerWidget::sendData()
{
qint64 len=0;
do
{
//每次发送数据的大小
char buf[4*1024]={0};//每次发4k
len=0;
//往文件中读数据
len = file.read(buf,sizeof(buf));
//发送数据,读多少发多少
len = tcpsocket->write(buf,len);//数据和最大大小
//发送数据需要累加
sendsize += len;
}while(len > 0);
//是否文件发送完毕
if(sendsize == filesize)
{
ui->textEdit->append("文件发送完毕");
file.close();
//把客户端断开
tcpsocket->disconnectFromHost();
tcpsocket->close();
}
}按钮转到槽函数
1 | void ServerWidget::on_buttonfile_clicked() |
客户端
- 右击添加,class c++新文件,类名为clientwidget.设计界面
- clientwidget.h中添加代码
1 |
|
- clientwidget.cpp中添加代码
1 |
|
- 按钮转到槽
1 | void clientWidget::on_bottonconnect_clicked() |
- 主函数添加为:
1 |
|
心得
在学完上面的四个知识后,我写了一个实现两个pc机之间互相通信的程序,一个简单的聊天室,运行结果如下图,另一端改变绑定端口即可,使用的UDP协议,可以实现发文字消息和传输文件(文件对方接收后可以直接创建,但只能写一部分进去,我一直以为是文件写入有问题,后来才找出是我发文件的这一端发送文件大小小于实际文件大小,然后拖着拖着就难得去改了(优秀)),里面最大的问题是判断你接受的是文件还是文字问题,然后我用了分片(section)加关键字(file 和 text)区分。之后我会把我的代码上传到码云,这里不再说明过程。