博客主页html
两种结构各有优点,可是不管哪一种结构,都离不开网络的支持,网络编程,就是在必定的协议下,实现两台计算机通讯的程序。java
咱们先来看这段代码:程序员
public class HelloWorld { public static void main(String[] args){ System.out.println("Hello World!"); } }
做为程序员,这段代码必定都很熟悉了。其实这段代码也是一种协议,是人类和计算机沟通的协议,只有经过这种协议,计算机才知道咱们想让它作什么。面试
协议三要素
这种协议比较接近人类语言,机器不能直接读懂,须要进行翻译,翻译的工做交给编译器,也就是compile。这个过程比较复杂,其中的编译原理很是复杂,我在这里不进行详述。
计算机语言做为程序员控制一台计算机工做的协议,具有了协议的三要素:算法
可是,要想打造互联网世界的通天塔,只教给一台机器作什么是不够的,你须要学会教给一大片机器作什么。这就须要网络协议。只有经过网络协议,才能使一大片机器互相协做、共同完成一件事。编程
举一个例子:打开浏览器输入一个网站的地址,浏览器就会展现展现该网址的页面内容。segmentfault
浏览器是如何识别的呢?是由于它收到了一段来自HTTP协议的“东西”浏览器
HTTP/1.1 200 OK Date: Tue, 27 Mar 2018 16:50:26 GMT Content-Type: text/html;charset=UTF-8 Content-Language: zh-CN <!DOCTYPE html> <html> <head> <base href="https://pages.kaola.com/" /> <meta charset="utf-8"/> <title>网易考拉3周年主会场</title>
它符合协议的三要素:服务器
经常使用的网络协议
当在浏览器里面输入 https://www.taobao.com ,这是一个URL。浏览器只知道名字是“www.taobao.com”,可是不知道具体的地点,因此不知道应该如何访问。因而,它打开地址簿去查找。可使用通常的地址簿协议DNS去查找,还可使用另外一种更加精准的地址簿查找协议HTTPDNS。网络
不管用哪种方法查找,最终都会获得这个地址:218.98.31.235。这个是IP地址,是互联网世界的“门牌号”。
不管是HTTP协议仍是HTTPS协议,里面都会写明“你要买什么和买多少”。
DNS、HTTP、HTTPS所在的层咱们称为应用层。通过应用层封装后,浏览器会将应用层的包交给下一层去完成,经过socket编程来实现。下一层是传输层。传输层有两种协议,一种是无链接的协议UDP,一种是面向链接的协议TCP。所谓的面向链接就是,TCP会保证这个包可以到达目的地。若是不能到达,就会从新发送,直至到达。
TCP协议里面会有两个端口,一个是浏览器监听的端口,一个是服务器监听的端口。操做系统每每经过端口来判断,它获得的包应该给哪一个进程。
传输层封装完毕后,浏览器会将包交给操做系统的网络层。网络层的协议是IP协议。在IP协议里面会有源IP地址,即浏览器所在机器的IP地址和目标IP地址,也即服务器的IP地址。
操做系统知道了目标IP地址,就开始想如何根据这个门牌号找到目标机器。就要经过网关,操做系统在启动的时候,就会被DHCP协议配置IP地址,以及默认的网关的IP地址192.168.1.1。
操做系统如何将IP地址发给网关呢?经过ARP协议,找到MAC地址。
因而操做系统将IP包交给了下一层,也就是MAC层。网卡再将包发出去。因为这个包里面是有MAC地址的,于是它可以到达网关。
网关收到包以后,会根据本身的知识,判断下一步应该怎么走。网关每每是一个路由器,到某个IP地址应该怎么走,这个叫做路由表。
路由器有点像玄奘西行路过的一个个国家的一个个城关。每一个城关都连着两个国家,每一个国家至关于一个局域网,在每一个国家内部,均可以使用本地的地址MAC进行通讯。
一旦跨越城关,就须要拿出IP头来,里面写着贫僧来自东土大唐(就是源IP地址),欲往西天拜佛求经(指的是目标IP地址)。路过宝地,借宿一晚,明日启行,请问接下来该怎么走啊?
城关每每是知道这些“知识”的,由于城关和临近的城关也会常常沟通。到哪里应该怎么走,这种沟通的协议称为路由协议,经常使用的有OSPF和BGP。
七层网络模型OSI和TCP/IP四层模型对比:
通讯的协议仍是比较复杂的,java.net包中包含的类和接口,它们提供封装了底层的通讯细节,能够直接使用这些类和接口,专一网络程序开发,而不用考虑通讯细节。
传输层里比较重要的两个协议:一个是TCP,一个是UDP。对于平常开发来讲,最经常使用的就是这两个协议。
那么TCP和UDP有哪些区别呢?通常在面试的时候问这两个协议的区别,大部分人会回答,TCP是面向链接的,UDP是面向无链接的。而后就没有了~~~
什么叫面向链接,什么叫无链接呢?在互通以前,面向链接的协议会先创建链接。例如,TCP会三次握手,而UDP不会。那为何要创建链接呢?你TCP三次握手,我UDP也能够发三个包玩玩,有什么区别吗?
所谓的创建链接,是为了在客户端和服务端维护链接,而创建必定的数据结构来维护双方交互的状态,用这样的数据结构来保证所谓的面向链接的特性。
UDP包头是什么样的?
咱们本身知道发的是一个UDP的包,而收到的那台机器咋知道的呢?在IP头里面有个8位协议,这里会存放,数据里面究竟是TCP仍是UDP,固然这里是UDP。因而,若是咱们知道UDP头的格式,就能从数据里面,将它解析出来。解析出来之后呢?数据给谁处理呢?
处理完传输层的事情,内核的事情基本就干完了,里面的数据应该交给应用程序本身去处理,但是一台机器上跑着这么多的应用程序,应该给谁呢?
不管应用程序写的使用TCP传数据,仍是UDP传数据,都要监听一个端口。正是这个端口,用来区分应用程序,要不说端口不能冲突呢。两个应用监听一个端口,到时候包给谁呀?因此,按理说,不管是TCP仍是UDP包头里面应该有端口号,根据端口号,将数据交给相应的应用程序。
UDP包最大长度计算:
从图中可知:使用16位(2个字节)存储长度信息,能够存储2^16=64k-1=65536-1=65535,自身协议占用16位+16位+16位+16位=64位=8个字节,UDP包最大长度为:65535-8=65507 byte
UDP的三大特色:
UDP的三大使用场景:
对于多播,其中D类地址为多播预留,使用这个地址,能够将包多播给一批机器。
UDP简单、处理速度快,不像TCP那样,操这么多的心,各类重传啊,保证顺序啊,前面的不收到,后面的无法处理啊。否则等这些事情作完了,时延早就上去了。而TCP在网络很差出现丢包的时候,拥塞控制策略会主动的退缩,下降发送速度,这就至关于原本环境就差,还自断臂膀,用户原本就卡,这下更卡了。
传输控制协议TCP简介:
TCP包头格式
先来看TCP头的格式。从这个图上能够看出,它比UDP复杂得多。
首先,源端口号和目标端口号是不可少的,这一点和UDP是同样的。若是没有这两个端口号。数据就不知道应该发给哪一个应用。
接下来是包的序号。为何要给包编号呢?固然是为了解决乱序的问题。不编好号怎么确认哪一个应该先来,哪一个应该后到呢。编号是为了解决乱序问题。
还应该有的就是确认序号。发出去的包应该有确认,要否则我怎么知道对方有没有收到呢?若是没有收到就应该从新发送,直到送达。这个能够解决不丢包的问题。
TCP是靠谱的协议,可是这不能说明它面临的网络环境好。从IP层面来说,若是网络情况的确那么差,是没有任何可靠性保证的,而做为IP的上一层TCP也无能为力,惟一能作的就是更加努力,不断重传,经过各类算法保证。也就是说,对于TCP来说,IP层你丢不丢包,我管不着,可是我在个人层面上,会努力保证可靠性。
接下来有一些状态位。例如:URG是紧急指针标志;SYN是同步序号标志,用于创建链接过程;ACK是回复,确认序号标志;PSH是push标志;RST是从新链接;FIN是finish标志,用于释放链接等。TCP是面向链接的,于是双方要维护链接的状态,这些带状态位的包的发送,会引发双方的状态变动。
还有一个重要的就是窗口大小。TCP要作流量控制,通讯双方各声明一个窗口,标识本身当前可以的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。
除了作流量控制之外,TCP还会作拥塞控制,对于真正的通路堵车不堵车,它无能为力,惟一能作的就是控制本身,也即控制发送的速度。不能改变世界,就改变本身嘛。
经过对TCP头的解析,咱们知道要掌握TCP协议,重点应该关注如下几个问题:
TCP的三次握手
握手是为了创建链接,TCP三次握手流程以下:
在TCP/IP协议中,TCP协议提供可靠的链接服务,经过三次握手创建链接:
一开始,客户端和服务端都处于CLOSED状态。先是服务端主动监听某个端口,处于LISTEN状态。而后客户端主动发起链接SYN,以后处于SYN-SENT状态。服务端收到发起的链接,返回SYN,而且ACK客户端的SYN,以后处于SYN-RCVD状态。客户端收到服务端发送的SYN和ACK以后,发送ACK的ACK,以后处于ESTABLISHED状态,由于它一发一收成功了。服务端收到ACK的ACK以后,处于ESTABLISHED状态,由于它也一发一收了。
一、为何要三次,而不是两次?按说两我的打招呼,一来一回就能够了啊?为了可靠,为何不是四次?
假设A和B之间通讯,A要发起一个链接,当发了第一个请求杳无音信的时候,会有不少的可能性,好比第一个请求包丢了,再如没有丢,可是绕了弯路,超时了,还有B没有响应,不想和我链接。
A不能确认结果,因而再发,再发。终于,有一个请求包到了B,可是请求包到了B的这个事情,目前A仍是不知道的,A还有可能再发。
B收到了请求包,就知道了A的存在,而且知道A要和它创建链接。若是B不乐意创建链接,则A会重试一阵后放弃,链接创建失败,没有问题;若是B是乐意创建链接的,则会发送应答包给A。
固然对于B来讲,这个应答包也是不知道能不能到达A。这个时候B天然不能认为链接是创建好了,由于应答包仍然会丢,会绕弯路,或者A已经挂了都有可能。于是两次握手确定不行。
若是A就是不发数据,创建链接后空着。在程序设计的时候,能够要求开启keepalive机制,即便没有真实的数据包,也有探活包。
另外,做为服务端B的程序设计者,对于A这种长时间不发包的客户端,能够主动关闭,从而空出资源来给其余客户端使用。
三次握手除了双方创建链接外,主要仍是为了沟通一件事情,就是TCP包的序号的问题。(为了初始化Sequence Number初始化)
TCP四次挥手
TCP采用四次挥手释放链接:
挥手就是终止链接,TCP四次挥手的流程图:
断开的时候,咱们能够看到,当A说“不玩了”,就进入FIN_WAIT_1的状态,B收到“A不玩”的消息后,发送知道了,就进入CLOSE_WAIT的状态。
A收到“B说知道了”,就进入FIN_WAIT_2的状态,若是这个时候B直接跑路,则A将永远在这个状态。TCP协议里面并无对这个状态的处理,可是Linux有,能够调整tcp_fin_timeout这个参数,设置一个超时时间。
若是B没有跑路,发送了“B也不玩了”的请求到达A时,A发送“知道B也不玩了”的ACK后,从FIN_WAIT_2状态结束,按说A能够跑路了,可是最后的这个ACK万一B收不到呢?则B会从新发一个“B不玩了”,这个时候A已经跑路了的话,B就再也收不到ACK了,于是TCP协议要求A最后等待一段时间TIME_WAIT,这个时间要足够长,长到若是B没收到ACK的话,“B说不玩了”会重发的,A会从新发一个ACK而且足够时间到达B。
A直接跑路还有一个问题是,A的端口就直接空出来了,可是B不知道,B原来发过的不少包极可能还在路上,若是A的端口被一个新的应用占用了,这个新的应用会收到上个链接中B发过来的包,虽然序列号是从新生成的,可是这里要上一个双保险,防止产生混乱,于是也须要等足够长的时间,等到原来B发送的全部的包都死翘翘,再空出端口来。
等待的时间设为2MSL,MSL是Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。由于TCP报文基因而IP协议的,而IP头中有一个TTL域,是IP数据报能够通过的最大路由数,每通过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。协议规定MSL为2分钟,实际应用中经常使用的是30秒,1分钟和2分钟等。
还有一个异常状况就是,B超过了2MSL的时间,依然没有收到它发的FIN的ACK,怎么办呢?按照TCP的原理,B固然还会重发FIN,这个时候A再收到这个包以后,A就表示,我已经在这里等了这么长时间了,已经仁至义尽了,以后的我就都不认了,因而就直接发送RST,B就知道A早就跑了。
一、为何会有TIME_WAIT状态?
Socket编程进行的是端到端的通讯,每每意识不到中间通过多少局域网,多少路由器,于是可以设置的参数,也只能是端到端协议之上网络层和传输层的。
在网络层,Socket函数须要指定究竟是IPv4仍是IPv6,分别对应设置为AF_INET和AF_INET6。另外,还要指定究竟是TCP仍是UDP。还记得我们前面讲过的,TCP协议是基于数据流的,因此设置为SOCK_STREAM,而UDP是基于数据报的,于是设置为SOCK_DGRAM。
TCP的服务端要先监听一个端口,通常是先调用bind函数,给这个Socket赋予一个IP地址和端口。
为何须要端口呢?
要知道,你写的是一个应用程序,当一个网络包来的时候,内核要经过TCP头里面的这个端口,来找到你这个应用程序,把包给你。
为何要IP地址呢?
有时候,一台机器会有多个网卡,也就会有多个IP地址,你能够选择监听全部的网卡,也能够选择监听一个网卡,这样,只有发给这个网卡的包,才会给你。
当服务端有了IP和端口号,就能够调用listen函数进行监听。在TCP的状态图里面,有一个listen状态,当调用这个函数以后,服务端就进入了这个状态,这个时候客户端就能够发起链接了。
在内核中,为每一个Socket维护两个队列。一个是已经创建了链接的队列,这时候链接三次握手已经完毕,处于established状态;一个是尚未彻底创建链接的队列,这个时候三次握手还没完成,处于syn_rcvd的状态。
接下来,服务端调用accept函数,拿出一个已经完成的链接进行处理。若是尚未完成,就要等着。
在服务端等待的时候,客户端能够经过connect函数发起链接。先在参数中指明要链接的IP地址和端口号,而后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的accept就会返回另外一个Socket。
监听的Socket和真正用来传数据的Socket是两个,一个叫做监听Socket,一个叫做已链接Socket。
链接创建成功以后,双方开始经过read和write函数来读写数据,就像往一个文件流里面写东西同样。
这个图就是基于TCP协议的Socket程序函数调用过程:
对于UDP来说,过程有些不同。UDP是没有链接的,因此不须要三次握手,也就不须要调用listen和connect,可是,UDP的的交互仍然须要IP和端口号,于是也须要bind。UDP是没有维护链接状态的,于是不须要每对链接创建一组Socket,而是只要有一个Socket,就可以和多个客户端通讯。也正是由于没有链接状态,每次通讯的时候,都调用sendto和recvfrom,均可以传入IP地址和端口。
这个图的内容就是基于UDP协议的Socket程序函数调用过程:
先来算一下理论值,也就是最大链接数,系统会用一个四元组来标识一个TCP链接。
{本机IP, 本机端口, 对端IP, 对端端口}
服务器一般固定在某个本地端口上监听,等待客户端的链接请求。所以,服务端端TCP链接四元组中只有对端IP, 也就是客户端的IP和对端的端口,也即客户端的端口是可变的,所以,最大TCP链接数=客户端IP数×客户端端口数。对IPv4,客户端的IP数最多为2的32次方,客户端的端口数最多为2的16次方,也就是服务端单机最大TCP链接数,约为2的48次方。
固然,服务端最大并发TCP链接数远不能达到理论上限。首先主要是文件描述符限制,Socket都是文件,因此首先要经过ulimit配置文件描述符的数目;另外一个限制是内存,每一个TCP链接都要占用必定内存,操做系统是有限的。
若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)