主要的考虑还是要区分包的丢失是由于链路故障还是乱序等其他因素引发。
两次duplicated ACK时很可能是乱序造成的!三次duplicated ACK时很可能是丢包造成的!四次duplicated ACK更更更可能是丢包造成的,但是这样的响应策略太慢。丢包肯定会造成三次duplicated ACK!综上是选择收到三个重复确认时窗口减半效果最好,这是实践经验。
在没有fast retransmit / recovery 算法之前,重传依靠发送方的retransmit timeout,就是在timeout内如果没有接收到对方的ACK,默认包丢了,发送方就重传,包的丢失原因。
1)包checksum 出错
2)网络拥塞
3)网络断,包括路由重收敛,但是发送方无法判断是哪一种情况,于是采用最笨的办法,就是将自己的发送速率减半,即CWND 减为1/2,这样的方法对2是有效的,可以缓解网络拥塞,3则无所谓,反正网络断了,无论发快发慢都会被丢;但对于1来说,丢包是因为偶尔的出错引起,一丢包就对半减速不合理。
于是有了fast retransmit 算法,基于在反向还可以接收到ACK,可以认为网络并没有断,否则也接收不到ACK,如果在timeout 时间内没有接收到> 2 的duplicated ACK,则概率大事件为乱序,乱序无需重传,接收方会进行排序工作;
而如果接收到三个或三个以上的duplicated ACK,则大概率是丢包,可以逻辑推理,发送方可以接收ACK,则网络是通的,可能是1、2造成的,先不降速,重传一次,如果接收到正确的ACK,则一切OK,流速依然(包出错被丢)。
而如果依然接收到duplicated ACK,则认为是网络拥塞造成的,此时降速则比较合理。
半关闭状态。
发送断开请求一方还有接收数据能力,但已经没有发送数据能力。
被动关闭连接一方接收到FIN包会立即回应ACK包表示已接收到断开请求。
被动关闭连接一方如果还有剩余数据要发送就会进入CLOSE_WAIT状态。
感谢网友指出笔误,已勘误-2021.11.16 https://github.com/forthespada/InterviewGuide/issues/23
目的是接收方通过TCP头窗口字段告知发送方本方可接收的最大数据量,用以解决发送速率过快导致接收方不能接收的问题。所以流量控制是点对点控制。
TCP是双工协议,双方可以同时通信,所以发送方接收方各自维护一个发送窗和接收窗。
发送窗:用来限制发送方可以发送的数据大小,其中发送窗口的大小由接收端返回的TCP报文段中窗口字段来控制,接收方通过此字段告知发送方自己的缓冲(受系统、硬件等限制)大小。
接收窗:用来标记可以接收的数据大小。
TCP是流数据,发送出去的数据流可以被分为以下四部分:已发送且被确认部分 | 已发送未被确认部分 | 未发送但可发送部分 | 不可发送部分,其中发送窗 = 已发送未确认部分 + 未发但可发送部分。接收到的数据流可分为:已接收 | 未接收但准备接收 | 未接收不准备接收。接收窗 = 未接收但准备接收部分。
发送窗内数据只有当接收到接收端某段发送数据的ACK响应时才移动发送窗,左边缘紧贴刚被确认的数据。接收窗也只有接收到数据且最左侧连续时才移动接收窗口。
服务器:
创建socket -> int socket(int domain, int type, int protocol);
domain:协议域,决定了socket的地址类型,IPv4为AF_INET。
type:指定socket类型,SOCK_STREAM为TCP连接。
protocol:指定协议。IPPROTO_TCP表示TCP协议,为0时自动选择type默认协议。
绑定socket和端口号 -> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket返回的套接字描述符,类似于文件描述符fd。
addr:有个sockaddr类型数据的指针,指向的是被绑定结构变量。
// IPv4的sockaddr地址结构
struct sockaddr_in {
sa_family_t sin_family; // 协议类型,AF_INET
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IP地址
};
struct in_addr {
uint32_t s_addr;
}监听端口号 -> int listen(int sockfd, int backlog);
sockfd:要监听的sock描述字。
backlog:socket可以排队的最大连接数。
接收用户请求 -> int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);
sockfd:服务器socket描述字。
addr:指向地址结构指针。
addrlen:协议地址长度。
注:一旦accept某个客户机请求成功将返回一个全新的描述符用于标识具体客户的TCP连接。
从socket中读取字符 -> ssize_t read(int fd, void *buf, size_t count);
fd:连接描述字。
buf:缓冲区buf。
count:缓冲区长度。
注:大于0表示读取的字节数,返回0表示文件读取结束,小于0表示发生错误。
关闭socket -> int close(int fd);
fd:accept返回的连接描述字,每个连接有一个,生命周期为连接周期。
注:sockfd是监听描述字,一个服务器只有一个,用于监听是否有连接;fd是连接描述字,用于每个连接的操作。
客户机:
创建socket -> int socket(int domain, int type, int protocol);
连接指定计算机 -> int connect(int sockfd, struct sockaddr* addr, socklen_t addrlen);
sockfd客户端的sock描述字。
addr:服务器的地址。
addrlen:socket地址长度。
向socket写入信息 -> ssize_t write(int fd, const void *buf, size_t count);
fd、buf、count:同read中意义。
大于0表示写了部分或全部数据,小于0表示出错。
关闭oscket -> int close(int fd);
建立连接(标志位):通信前确认通信实体存在。
序号机制(序号、确认号):确保了数据是按序、完整到达。
数据校验(校验和):CRC校验全部数据。
超时重传(定时器):保证因链路故障未能到达数据能够被多次重发。
窗口机制(窗口):提供流量控制,避免过量发送。
拥塞控制:同上。
首部校验 这个校验机制能够确保数据传输不会出错吗? 答案是不能。
原因
TCP协议中规定,TCP的首部字段中有一个字段是校验和,发送方将伪首部、TCP首部、TCP数据使用累加和校验的方式计算出一个数字,然后存放在首部的校验和字段里,接收者收到TCP包后重复这个过程,然后将计算出的校验和和接收到的首部中的校验和比较,如果不一致则说明数据在传输过程中出错。
这就是TCP的数据校验机制。 但是这个机制能够保证检查出一切错误吗?显然不能。
因为这种校验方式是累加和,也就是将一系列的数字(TCP协议规定的是数据中的每16个比特位数据作为一个数字)求和后取末位。 但是小学生都知道A+B=B+A,假如在传输的过程中有前后两个16比特位的数据前后颠倒了(至于为什么这么巧合?我不知道,也许路由器有bug?也许是宇宙中的高能粒子击中了电缆?反正这个事情的概率不为零,就有可能会发生),那么校验和的计算结果和颠倒之前是一样的,那么接收端肯定无法检查出这是错误的数据。
解决方案
传输之前先使用MD5加密数据获得摘要,跟数据一起发送到服务端,服务端接收之后对数据也进行MD5加密,如果加密结果和摘要一致,则认为没有问题
提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
7、UDP是面向报文的,发送方的UDP对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,论应用层交给UDP多长的报文,它统统发送,一次发送一个。而对接收方,接到后直接去除首部,交给上面的应用层就完成任务了。因此,它需要应用层控制报文的大小
TCP是面向字节流的,它把上面应用层交下来的数据看成无结构的字节流会发送,可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着TCP会根据当前网络的拥塞状态来确定每个报文段的大小。
封包和拆包都是基于TCP的概念。因为TCP是无边界的流传输,所以需要对TCP进行封包和拆包,确保发送和接收的数据不粘连。
那么,再说一次TCP的特点:
FTP:定义了文件传输协议,使用21端口. Telnet:它是一种用于远程登陆的端口,23端口 SMTP:定义了简单邮件传送协议,服务器开放的是25号端口。 POP3:它是和SMTP对应,POP3用于接收邮件。
DNS:用于域名解析服务,用的是53号端口 SNMP:简单网络管理协议,使用161号端口 TFTP(Trival File Transfer Protocal):简单文件传输协议,69
| 协议 | 名称 | 作用 |
|---|---|---|
| ARP | 地址解析协议 | 根据IP地址获取物理地址 |
| RARP | 反向地址转换协议 | 根据物理地址获取IP地址 |
| PPP | 点对点协议 | 主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案 |
ping命令基于网络层的命令,是基于ICMP协议工作的。
当然,这个没有唯一答案,相对于不同的系统,不同的要求,其得到的答案是不一样的。
我这里仅对像ICQ一类的发送聊天消息的情况作分析,对于其他情况,你或许也能得到一点帮助:首先,我们知道,TCP/IP通常被认为是一个四层协议系统,包括链路层,网络层,运输层,应用层.UDP属于运输层,
下面我们由下至上一步一步来看:以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的.这个1500字节被称为链路层的MTU(最大传输单元).但这并不是指链路层的长度被限制在1500字节,其实这这个MTU指的是链路层的数据区.并不包括链路层的首部和尾部的18个字节.
所以,事实上,这个1500字节就是网络层IP数据报的长度限制。因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节.而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的.又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节.这个1472字节就是我们可以使用的字节数。
当我们发送的UDP数据大于1472的时候会怎样呢? 这也就是说IP数据报大于1500字节,大于MTU.这个时候发送方IP层就需要分片(fragmentation). 把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组. 这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便 无法重组数据报.将导致丢弃整个UDP数据报。
因此,在普通的局域网环境下,我建议将UDP的数据控制在1472字节以下为好.
进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值. 如果我们假定MTU为1500来发送数据的,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机 制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作.
鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时. 最好将UDP的数据长度控件在548字节(576-8-20)以内
流量控制是为了控制发送方发送速率,保证接收方来得及接收。TCP 利用滑动窗口实现流量控制。
TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据。
例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。
超时重传:发送端发送报文后若长时间未收到确认的报文则需要重发该报文。可能有以下几种情况:
发送的数据没能到达接收端,所以对方没有响应。
接收端接收到数据,但是ACK报文在返回过程中丢失。
接收端拒绝或丢弃数据。
RTO:从上一次发送数据,因为长期没有收到ACK响应,到下一次重发之间的时间。就是重传间隔。
通常每次重传RTO是前一次重传间隔的两倍,计量单位通常是RTT。例:1RTT,2RTT,4RTT,8RTT……
重传次数到达上限之后停止重传。
RTT:数据从发送到接收到对方响应之间的时间间隔,即数据报在网络中一个往返用时。大小不稳定。
跨站点脚本攻击,指攻击者通过篡改网页,嵌入恶意脚本程序,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。如何防范XSS攻击 1)前端,服务端,同时需要字符串输入的长度限制。 2)前端,服务端,同时需要对HTML转义处理。将其中的”<”,”>”等特殊字符进行转义编码。 防 XSS 的核心是必须对输入的数据做过滤处理。
跨站点请求伪造,指攻击者通过跨站请求,以合法的用户的身份进行非法操作。可以这么理解CSRF攻击:攻击者盗用你的身份,以你的名义向第三方网站发送恶意请求。CRSF能做的事情包括利用你的身份发邮件,发短信,进行交易转账,甚至盗取账号信息。
安全框架,例如Spring Security。 token机制。在HTTP请求中进行token验证,如果请求中没有token或者token内容不正确,则认为CSRF攻击而拒绝该请求。 验证码。通常情况下,验证码能够很好的遏制CSRF攻击,但是很多情况下,出于用户体验考虑,验证码只能作为一种辅助手段,而不是最主要的解决方案。 referer识别。在HTTP Header中有一个字段Referer,它记录了HTTP请求的来源地址。如果Referer是其他网站,就有可能是CSRF攻击,则拒绝该请求。但是,服务器并非都能取到Referer。很多用户出于隐私保护的考虑,限制了Referer的发送。在某些情况下,浏览器也不会发送Referer,例如HTTPS跳转到HTTP。 1)验证请求来源地址; 2)关键操作添加验证码; 3)在请求地址添加 token 并验证。
文件上传漏洞,指的是用户上传一个可执行的脚本文件,并通过此脚本文件获得了执行服务端命令的能力。 许多第三方框架、服务,都曾经被爆出文件上传漏洞,比如很早之前的Struts2,以及富文本编辑器等等,可被攻击者上传恶意代码,有可能服务端就被人黑了。
文件上传的目录设置为不可执行。
1)判断文件类型。在判断文件类型的时候,可以结合使用MIME Type,后缀检查等方式。因为对于上传文件,不能简单地通过后缀名称来判断文件的类型,因为攻击者可以将可执行文件的后缀名称改为图片或其他后缀类型,诱导用户执行。
2)对上传的文件类型进行白名单校验,只允许上传可靠类型。
3)上传的文件需要进行重新命名,使攻击者无法猜想上传文件的访问路径,将极大地增加攻击成本,同时向shell.php.rar.ara这种文件,因为重命名而无法成功实施攻击。
4)限制上传文件的大小。
5)单独设置文件服务器的域名。