Socket网络编程(一):Socket网络编程理论知识

博客主页html

1. 软件结构

  1. C/S结构:全称为Client/Server结构,是指客户端和服务器结构,常见的程序如:QQ、迅雷等
  2. B/S结构:全称为Browser/Server结构,是🈯️浏览器和服务器结构,常见浏览器有:谷歌、火狐等

两种结构各有优点,可是不管哪一种结构,都离不开网络的支持,网络编程,就是在必定的协议下,实现两台计算机通讯的程序。java

2. 网络通讯协议

咱们先来看这段代码:程序员

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>

它符合协议的三要素:服务器

  1. 符合语法,也就是说,只有按照上面那个格式来,浏览器才认。例如,上来是状态,而后是首部,而后是内容。
  2. 符合语义,就是要按照约定的意思来。例如,状态200,表述的意思是网页成功返回。若是不成功,就是咱们常见的“404”。
  3. 符合顺序,你一点浏览器,就是发送出一个HTTP请求,而后才有上面那一串HTTP返回的东西。

经常使用的网络协议
当在浏览器里面输入 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四层模型对比:

3. 协议分类

通讯的协议仍是比较复杂的,java.net包中包含的类和接口,它们提供封装了底层的通讯细节,能够直接使用这些类和接口,专一网络程序开发,而不用考虑通讯细节。

传输层里比较重要的两个协议:一个是TCP,一个是UDP。对于平常开发来讲,最经常使用的就是这两个协议。

3.1 UDP协议

那么TCP和UDP有哪些区别呢?通常在面试的时候问这两个协议的区别,大部分人会回答,TCP是面向链接的,UDP是面向无链接的。而后就没有了~~~

什么叫面向链接,什么叫无链接呢?在互通以前,面向链接的协议会先创建链接。例如,TCP会三次握手,而UDP不会。那为何要创建链接呢?你TCP三次握手,我UDP也能够发三个包玩玩,有什么区别吗?

所谓的创建链接,是为了在客户端和服务端维护链接,而创建必定的数据结构来维护双方交互的状态,用这样的数据结构来保证所谓的面向链接的特性。

  1. TCP提供可靠交付。经过TCP链接传输的数据,无差错、不丢失、不重复、而且按序到达。咱们都知道IP包是没有任何可靠性保证的,一旦发出去,都只能随它去。而UDP继承了IP包的特性,不保证不丢失,不保证按顺序到达。
  2. TCP是面向字节流的。发送的时候发的是一个流,没头没尾。IP包可不是一个流,而是一个个的IP包。之因此变成了流,这也是TCP本身的状态维护作的事情。而UDP继承了IP的特性,基于数据报的,一个一个地发,一个一个地收。
  3. TCP是能够有拥塞控制的。它意识到包丢弃了或者网络的环境很差了,就会根据状况调整本身的行为,看看是否是发快了,要不要发慢点。UDP就不会,应用让我发,我就发。
  4. 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的三大特色:

  1. 沟通简单,不须要一肚子花花肠子(大量的数据结构、处理逻辑、包头字段)。前提是它相信网络世界是美好的,秉承性善论,相信网络通路默认就是很容易送达的,不容易被丢弃的。
  2. 轻信他人。它不会创建链接,虽然有端口号,可是监听在这个地方,谁均可以传给他数据,他也能够传给任何人数据,甚至能够同时传给多我的数据。
  3. 愣头青,作事不懂权变。不知道何时该坚持,何时该退让。它不会根据网络的状况进行发包的拥塞控制,不管网络丢包丢成啥样了,它该怎么发还怎么发。

UDP的三大使用场景:

  1. 须要资源少,在网络状况比较好的内网,或者对于丢包不敏感的应用
  2. 不须要一对一沟通,创建链接,而是能够广播的应用。UDP的不面向链接的功能,可使得能够承载广播或者多播的协议。DHCP就是一种广播的形式,就是基于UDP协议的。

对于多播,其中D类地址为多播预留,使用这个地址,能够将包多播给一批机器。

  1. 须要处理速度快,时延低,能够容忍少数丢包,可是要求即使网络拥塞,也绝不退缩,勇往直前的时候。

UDP简单、处理速度快,不像TCP那样,操这么多的心,各类重传啊,保证顺序啊,前面的不收到,后面的无法处理啊。否则等这些事情作完了,时延早就上去了。而TCP在网络很差出现丢包的时候,拥塞控制策略会主动的退缩,下降发送速度,这就至关于原本环境就差,还自断臂膀,用户原本就卡,这下更卡了。

3.2 TCP协议

传输控制协议TCP简介:

  • 面向链接的,可靠的,基于字节流的传输层通讯协议
  • 将应用层的数据流分割成报文段并发送给目标节点的TCP层
  • 数据报都有序号,对方收到将发送ACK确认,未收到则重传
  • 使用校验和校验数据在传输过程当中是否有误

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协议提供可靠的链接服务,经过三次握手创建链接:

  1. 第一次握手:创建链接时,服务端发送SYN包(seq=x)到服务器,并进入SYN-SENT状态,等待服务器确认
  2. 第二次握手:服务器收到SYN包,必须确认客户的SYN(ack=x+1),同时本身也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN-RCVD状态
  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端与服务器进入ESTABLISHED状态,完成三次握手。


一开始,客户端和服务端都处于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采用四次挥手释放链接:

  1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传输,Client进入FIN_WAIT_1状态
  2. 第二次挥手:Server收到FIN,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态
  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传输,Server进入LAST_ACK状态
  4. 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSE状态,完成四次挥手。

挥手就是终止链接,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状态?

  • 确保有足够的时间让对方收到ACK包
  • 避免新旧链接混淆

4 基于TCP和UDP协议的Socket编程

Socket编程进行的是端到端的通讯,每每意识不到中间通过多少局域网,多少路由器,于是可以设置的参数,也只能是端到端协议之上网络层和传输层的。

在网络层,Socket函数须要指定究竟是IPv4仍是IPv6,分别对应设置为AF_INET和AF_INET6。另外,还要指定究竟是TCP仍是UDP。还记得我们前面讲过的,TCP协议是基于数据流的,因此设置为SOCK_STREAM,而UDP是基于数据报的,于是设置为SOCK_DGRAM。

4.1 基于TCP协议的Socket程序函数调用过程

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程序函数调用过程:

4.2 基于UDP协议的Socket程序函数调用过程

对于UDP来说,过程有些不同。UDP是没有链接的,因此不须要三次握手,也就不须要调用listen和connect,可是,UDP的的交互仍然须要IP和端口号,于是也须要bind。UDP是没有维护链接状态的,于是不须要每对链接创建一组Socket,而是只要有一个Socket,就可以和多个客户端通讯。也正是由于没有链接状态,每次通讯的时候,都调用sendto和recvfrom,均可以传入IP地址和端口。

这个图的内容就是基于UDP协议的Socket程序函数调用过程:

4.3 服务器最大链接数

先来算一下理论值,也就是最大链接数,系统会用一个四元组来标识一个TCP链接。

{本机IP, 本机端口, 对端IP, 对端端口}

服务器一般固定在某个本地端口上监听,等待客户端的链接请求。所以,服务端端TCP链接四元组中只有对端IP, 也就是客户端的IP和对端的端口,也即客户端的端口是可变的,所以,最大TCP链接数=客户端IP数×客户端端口数。对IPv4,客户端的IP数最多为2的32次方,客户端的端口数最多为2的16次方,也就是服务端单机最大TCP链接数,约为2的48次方。

固然,服务端最大并发TCP链接数远不能达到理论上限。首先主要是文件描述符限制,Socket都是文件,因此首先要经过ulimit配置文件描述符的数目;另外一个限制是内存,每一个TCP链接都要占用必定内存,操做系统是有限的。

若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)

相关文章
相关标签/搜索