这里跟大家分享下我复习时整理的计算机网络常考知识点。
物理层:物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。
链路层:两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。数据报组装成帧。
网络层:网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。数据报, IP
传输层:负责向两台主机进程之间的通信提供通用的数据传输服务,TCP/UDP
应用层:通过应用进程间的交互来完成特定网络应用,报文,HTTP,DNS,SMTP
Time_Wait和3次握手
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你未必会马上会关闭SOCKET,即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。
当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是 先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。
让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
快重传算法首先要求接收方每收到一个失序的报文段就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方),然后若是发送方接收到3个重复确认ACK,则启动快重传算法。
与快重传配合使用的还有快恢复算法,其过程有以下两个要点:
HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:
详解
Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
Cookie 一般用来保存用户信息 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
public class TCPServer {
public static void main(String[] args) throws Exception {
//创建socket,并将socket绑定到65000端口
ServerSocket ss = new ServerSocket(65000);
//死循环,使得socket一直等待并处理客户端发送过来的请求
while (true) {
//监听65000端口,直到客户端返回连接信息后才返回
Socket socket = ss.accept();
//获取客户端的请求信息后,执行相关业务逻辑
new LengthCalculator(socket).start();
}
}
}
public class LengthCalculator extends Thread {
//以socket为成员变量
private Socket socket;
public LengthCalculator(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//获取socket的输出流
OutputStream os = socket.getOutputStream();
//获取socket的输入流
InputStream is = socket.getInputStream();
int ch = 0;
byte[] buff = new byte[1024];
//buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
ch = is.read(buff);
//将接收流的byte数组转换成字符串,这里获取的内容是客户端发送过来的字符串参数
String content = new String(buff, 0, ch);
System.out.println(content);
//往输出流里写入获得的字符串的长度,回发给客户端
os.write(String.valueOf(content.length()).getBytes());
//不要忘记关闭输入输出流以及socket
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class TCPClient {
public static void main(String[] args) throws Exception {
//创建socket,并指定连接的是本机的端口号为65000的服务器socket
Socket socket = new Socket("127.0.0.1", 65000);
//获取输出流
OutputStream os = socket.getOutputStream();
//获取输入流
InputStream is = socket.getInputStream();
//将要传递给server的字符串参数转换成byte数组,并将数组写入到输出流中
os.write(new String("hello world").getBytes());
int ch = 0;
byte[] buff = new byte[1024];
//buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
ch = is.read(buff);
//将接收流的byte数组转换成字符串,这里是从服务端回发回来的字符串参数的长度
String content = new String(buff, 0, ch);
System.out.println(content);
//不要忘记关闭输入输出流以及socket
is.close();
os.close();
socket.close();
}
}
public class UDPServer {
public static void main(String[] args) throws Exception {
// 服务端接受客户端发送的数据报
DatagramSocket socket = new DatagramSocket(65001); //监听的端口号
byte[] buff = new byte[100]; //存储从客户端接受到的内容
DatagramPacket packet = new DatagramPacket(buff, buff.length);
//接受客户端发送过来的内容,并将内容封装进DatagramPacket对象中
socket.receive(packet);
byte[] data = packet.getData(); //从DatagramPacket对象中获取到真正存储的数据
//将数据从二进制转换成字符串形式
String content = new String(data, 0, packet.getLength());
System.out.println(content);
//将要发送给客户端的数据转换成二进制
byte[] sendedContent = String.valueOf(content.length()).getBytes();
// 服务端给客户端发送数据报
//从DatagramPacket对象中获取到数据的来源地址与端口号
DatagramPacket packetToClient = new DatagramPacket(sendedContent,
sendedContent.length, packet.getAddress(), packet.getPort());
socket.send(packetToClient); //发送数据给客户端
}
}
public class UDPClient {
public static void main(String[] args) throws Exception {
// 客户端发数据报给服务端
DatagramSocket socket = new DatagramSocket();
// 要发送给服务端的数据
byte[] buf = "Hello World".getBytes();
// 将IP地址封装成InetAddress对象
InetAddress address = InetAddress.getByName("127.0.0.1");
// 将要发送给服务端的数据封装成DatagramPacket对象 需要填写上ip地址与端口号
DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
65001);
// 发送数据给服务端
socket.send(packet);
// 客户端接受服务端发送过来的数据报
byte[] data = new byte[100];
// 创建DatagramPacket对象用来存储服务端发送过来的数据
DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
// 将接受到的数据存储到DatagramPacket对象中
socket.receive(receivedPacket);
// 将服务器端发送过来的数据取出来并打印到控制台
String content = new String(receivedPacket.getData(), 0,
receivedPacket.getLength());
System.out.println(content);
}
}
注:主动、被动与服务器、客户端没有明确的对应关系。
每个连接均开始于CLOSED状态。当一方执行了被动的连接原语(LISTEN)或主动的连接原语(CONNECT)时,它便会脱离CLOSED状态。如果此时另一方执行了相对应的连接原语,连接便建立了,并且状态变为ESTABLISHED。任何一方均可以首先请求释放连接,当连接被释放后,状态又回到了CLOSED。
CLOSED: 表示初始状态。
LISTEN: 表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_RCVD: 这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态的,除非特意写一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此该状态下收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
SYN_SENT: 表示客户端已发送SYN报文,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。
ESTABLISHED:表示连接已经建立了。
FIN_WAIT_1: 其实FIN_WAIT_1和FIN_WAIT_2状态都表示等待对方的FIN报文。
**而这两种状态的区别是:**FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
FIN_WAIT_2:实际上FIN_WAIT_2状态下的SOCKET,表示半连接,即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。在FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING: 表示双方都正在关闭SOCKET连接,属于一种比较罕见的例外状态。正常情况下,当发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示发送FIN报文后,并没有收到对方的ACK报文,反而却收到了对方的FIN报文。如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,即出现CLOSING状态。
CLOSE_WAIT: 表示在等待关闭。当对方close一个SOCKET后发送FIN报文给自己,你毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来真正需要考虑的事情是查看是否还有数据发要送给对方,如果没有的话,那么就可以close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
关闭的状态转换:假设通信双方是A,B;A主动发起关闭
(1)主动发起关闭方
A首先主动发起FIN报文,准备关闭TCP连接,然后进入FIN_WAIT1状态;
如果A收到了ACK报文,进入FIN_WAIT2状态,说明B还有数据发给A。不久之后,B发送FIN给A,然后,A发送ACK,并进入TIME_WAIT状态。
如果A收到到了ACK + FIN,A直接进入TIME_WAIT状态。
经过2个MSL,没有收到FIN信号,那么TIME_WAIT就自动转化为CLOESD。
(2)被动接收方
B在收到A的FIN报文后,知道A准备关闭 TCP连接了(注意只是A单方面关闭发数据的连接,也就是说A还可以接收数据)。B将发送ACK给A,进入CLOSED_WAIT状态。如果此时B也有数据发送给A,那么就一直发送好了,反正A不会发数据了。此时A处于FIN_WAIT2状态。当B的数据发送完毕之后,那么B发送FIN给 A,B进入LAST_ACK状态,当收到A发过来的ACK信号后,A进入CLOSED状态。
详解
拥塞控制部分图片与文字来源
状态机部分图片与文字来源