网络通信之TCP、UDP

一、UDP

1、udp一个socket接收缓冲区的默认值

cat /proc/sys/net/core/rmem_default   

~$ 212992,单位Byte,=208KB

3、每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。

UDP:当套接字接收缓冲区满时,新来的数据报无法进入接收缓冲区,此数据报就被丢弃。UDP是没有流量控制的,快的发送者可以很容易的湮没慢的接收者,导致接收方的UDP 丢弃新来的数据报。

4、概念

  实际上在同一个网段,或者在信号很好的局域网,UDP实际上是非常可靠的。如没有丢包并包的按顺序依次到达(这个几乎是短局域网的常态),并不需要重新传输包,所以TCP的所有应答和等待只会浪费时间,增加网络延时。

5、单播 

  一对一通信。通常我们讨论的udp的程序都是一对一的单播程序。

6、广播

  一个主机对整个局域网上所有主机上的数据通信。

  广播UDP与单播UDP的区别就是IP地址不同,广播使用广播地址255.255.255.255,将消息发送到在同一广播网络上的每个主机。值得强调的是:本地广播信息是不会被路由器转发。当然这是十分容易理解的,因为如果路由器转发了广播信息,那么势必会引起网络瘫痪。这也是为什么IP协议的设计者故意没有定义互联网范围的广播机制。

  广播地址通常用于在网络游戏中处于同一本地网络的玩家之间交流状态信息等。

  其实广播顾名思义,就是想局域网内所有的人说话,但是广播还是要指明接收者的端口号的,因为不可能接受者的所有端口都来收听广播。

7、多播(Multicasting)

  单播用于两个主机之间的端对端通信,广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网上的主机进行通信。实际情况下,经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是多播的用途。

  这里可以简化下TCP/IP/UDP的相关讨论,默认我们知道IP(UDP和TCP一样)可以把数据包在一个网络中发到另一个设备。更准确点就是IP把数据包从一个IP地址发到另一个IP地址。多播的决窍就是在同一时间把一个数据包发送到多个设备,可以把一个特定的IP地址指定为多播地址,并同时发送到多个设备。

  IP多播首先要知道的是只有UDP有多播,没有TCP多播这样的东西,为什么呢?多播的重点是高效的把同一个包尽可能多的发送到不同的,甚至可能是未知的设备。但是TCP连接可能要求丢包重发或者延时或重组顺序,这些操作可能非常消耗资源,不适于许多使用多播的应用场景。(同时多播不知道发出的包是不是已经到达,这个也导致不能使用TCP)。

二、TCP协议

基于TCP Socket编程,假设TCP最多一次只能发送1K byte的数据,服务器端程序首先要将300K数据按照顺序砍成300块 (Segment),按照从头到尾编号,1-300,然后调用send()函数300次,严格按照时间顺序,第一次调用发编号1,第二次调用发编号2,…第三百次调用发编号300,这个不复杂,只要编写一个循环程序(300次)即可,只要每次调用的返回值都OK,应用程序的任务就算完成了。

以上的编号1-300就是TCP segment编号,那字节流的编号呢,就是 1,300000,其中 1-1000 字节被编在1号TCP segment,1001-2000字节编在2号segment,以此类推。

TCP不关心本地send()给自己的内容是啥(反正都是字节),只关心时序,先发给自己的肯定先编号,后发的后编号,TCP本身只保证传输的顺序,至于在服务器端本地、客户端本地的顺序则由Receive()/ Send()时序来保证!

问题的关键在于TCP是有缓冲区,作为对比,UDP面向报文段是没有缓冲区的。

TCP发送报文时,是将应用层数据写入TCP缓冲区中,然后由TCP协议来控制发送这里面的数据,而发送的状态是按字节流的方式发送的,跟应用层写下来的报文长度没有任何关系,所以说是流。

作为对比的UDP,它没有缓冲区,应用层写的报文数据会直接加包头交给网络层,由网络层负责分片,所以是面向报文段的。

3、tcpdump

sudo tcpdump -i enp0s25 udp port 2000 

三、网络收发数据与缓存大小

1、 UDP发送测试

实测,在未经更改的ubuntu上,UDP的sendto函数,一次最大发送数据量为65507byte。超过这个大小,发送会失败,sendto()函数返回-1。

和理论计算结果一致,UDP协议数据报最大长度 = 65535 - 20(IP头)-8(UDP头) = 65507

2、UDP接收测试

1、正常情况下,一般设置接收buf大于发送buf,在阻塞模式下,UDP的通信是以数据包作为界限的,即使server端的缓冲区再大也要按照client发包的次数来多次接收数据包,server只能一次一次的接收,client发送多少次,server就需接收多少次,即客户端分几次发送过来,服务端就必须按几次接收。

2、接收buf  < 发送buf,接收方实际情况如何?

server端调用recvfrom()函数按包接收,接收buf能够接收到length(buf)长度的有效数据,不能够循环接收,这个包剩余的数据会被丢弃,如下图:发送方发送两个字节数据,第一个字节存储9,第二个字节存储2。当接收缓存设置1byte时,接收到的数据永远是9,第二个字节被丢弃。

3、TCP发送测试

 

4、TCP接收测试

发送方一次发送20M数据,接受缓存4K;

1、接收buf  < 发送buf,接收方实际情况如何?

TCP传输的实质,是流式传输,就是不管你发送的时候是一次发送多少,TCP的底层处理,都是一个字节一个字节按流发送的。
发送端: 可以正常发送出去;
接收端:能接收到4K数据, 可循环接收;

下图测试发送buf =4,接收buf =1。可以看出接收方循环接收了每个数据。

四、接收阻塞问题

接收函数recv()、recvfrom(),如果不进行设置,默认都是阻塞模式,即接收不到消息不返回。

而且我们经常将接收函数放在一个子线程中循环接收,如果写while(1)死循环,就会造成ctrl + c结束不了程序。处理方法有如下几种:

1、fake一个假数据包给这个相应的端口,通过长度/内容判断是否退出线程。

2、在调用recvfrom前,将recvfrom设置成非阻塞模式,这个方法容易导致CPU占用率大幅上升。

3、使用select/epoll来接收。

4、在调用recvfrom前,将recvfrom设置一个超时时间,然后使用signal函数处理ctrl+c信号,从而改变标志位

#include <csignal>
#include <sys/time.h>

static int keepRunning = 1; //定义为全局变量

void sig_handler( int sig )
{
    if ( sig == SIGINT)
    {
        keepRunning = 0;
    }
}

signal(SIGINT, sig_handler );

//子线程
·····
//设置接收超时
struct timeval tv_out;
tv_out.tv_sec = 1;//等待1秒
tv_out.tv_usec = 0;
setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&tv_out, sizeof(tv_out));
while(keepRunning){
·····
	recvfrom(······);

}
·······

 

posted @ 2020-03-17 19:28  chenjian688  阅读(1512)  评论(0编辑  收藏  举报