网络编程
一、网络体系结构
TCP/IP 结构
TCP/IP传输协议,即传输控制/网络协议,也叫作网络通讯协议。是在网络的使用中的最基本的通信协议。
TCP/IP传输协议,严格来说是一个四层的体系结构:
-
应用层:为用户的应用程序提供接口,使用户可以访问网络。
-
传输层:提供端到端的通信(两台计算机上的软件间的连接),对信息流具有调节作用。
-
网络层:在TCP/IP协议中网络层可以进行网络连接的建立和终止以及IP地址的寻找等功能。
-
数据链路层:在两个网络实体之间提供数据链路通路的建立、维持和释放。
二、网络编程的要素
2.1 协议
网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
- 语义:表示要做什么
- 语法:表示要怎么做
- 时序:表示要做的顺序
2.2 IP地址
IP地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址。
IP地址是IP协议提供的一种统一的地址格式,为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
- 唯一定位一台网络上的计算机。
- 本机localhost:
127.0.0.1
IP地址的分类:
- ipv4:32位
- ipv6:128位
编址方式
类别 | 最大网络数 | IP地址范围 | 单个网段最大主机数 | 私有IP地址范围 |
---|---|---|---|---|
A | 126(2^7-2) | 1.0.0.1—127.255.255.254 | 16777214 | 10.0.0.0—10.255.255.255 |
B | 16384(2^14) | 128.0.0.0—191.255.255.255 | 65534 | 172.16.0.0—172.31.255.255 |
C | 2097152(2^21) | 192.0.0.0—223.255.255.255 | 254 | 192.168.0.0—192.168.255.255 |
2.3 端口号
网络的通信,本质上是两个进程(应用程序)之间的通信,每台计算机都有很多的进程。
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)。
- 端口号为:0~65535
- TCP、UDP各有65535个端口,不同协议可以使用相同端口,单个协议下端口不能冲突
端口号分类
- 公认端口:0到1023,紧密绑定于一些服务。通常这些端口的通讯明确表明了某种服务的协议。
- HTTP : 80
- HTTPS : 443
- FTP : 21
- Telent : 23
- 程序注册端口:1024到49151。松散地绑定于一些服务,分配给用户或者程序。
- Tomcat : 8080
- MySQL : 3306
- Oracle : 1521
- 动态、私有端口:49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。
三、TCP
3.1 简介
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
-
序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
-
确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
-
确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效。
-
同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
-
终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接
-
PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。
字段 | 含义 |
---|---|
URG | 紧急指针是否有效。为1,表示某一位需要被优先处理 |
ACK | 确认号是否有效,一般置为1。 |
PSH | 提示接收端应用程序立即从TCP缓冲区把数据读走。 |
RST | 对方要求重新建立连接,复位。 |
SYN | 请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1 |
FIN | 希望断开连接。 |
三次握手
三次握手:即建立TCP连接,需要客户端和服务端总共发送至少三个包,确认连接的建立。
- 第一次握手:客户端发送初始序号seq=x和请求标志SYN=1
- 第二次握手:服务器发送请求标志SYN=1,发送确认标志ACK=1,发送自己的序号seq=y,发送客户端的确认序号ack=x+1
- 第三次握手:客户端发送确认标志ACK=1,发送自己的序号seq=x+1,发送服务器的确认序号ack=y+1
三次握手的原因
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。
四次挥手
四次挥手:即终止TCP连接,需要客户端和服务端总共发送4个包,确认连接的断开。
- 客户端发送一个FIN,用来关闭客户端到服务器的数据传送,客户端进入FINWAIT1状态。
- 服务器收到FIN后,发送一个ACK给客户端,服务器进入CLOSE_WAIT状态。
- 服务器发送一个FIN,用来关闭服务器到客户端的数据传送,服务器进入LAST_ACK状态。
- 客户端收到FIN后,客户端进入TIME_WAIT状态,发送ACK给服务器,服务器进入CLOSED状态,完成四次握手。
四次挥手的原因
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
- 确保最后一个确认报文能够到达。如果 B(服务器) 没收到 A (客户端)发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
- 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。
3.2 代码编写
服务端
- 建立服务器端口
- 等待客户端的连接
- 读取客户端的消息
- 读取完毕后关闭数据流
public class TcpServer {
public static void main(String[] args) throws IOException {
//1.建立服务端口
ServerSocket serverSocket = new ServerSocket(8080);
//2.等待客户端连接
Socket socket = serverSocket.accept();
//3.读取客户端的消息
InputStream inputStream = socket.getInputStream();
//管道流,相比直接byte的接收,可以接收中文
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
baos.write(bytes,0,len);
}
System.out.println(baos.toString());
//4.关闭数据流
baos.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
客户端
- 建立与服务器的连接
- 发送信息
- 关闭数据流
public class TcpClient {
public static void main(String[] args) throws IOException {
//1.建立与服务器的连接
//1.1 服务器地址、端口
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
int port = 8080;
//1.2 建立socket连接
Socket socket = new Socket(serverIP, port);
//2.发送消息
OutputStream outputStream = socket.getOutputStream();
outputStream.write("客户端发送了一条信息".getBytes());
//3.关闭数据流
outputStream.close();
socket.close();
}
}
运行测试
- 服务器开启,等待客户端连接
- 客户端启动,连接服务器,发送消息
- 服务器收到客户端发送的消息
3.3 面试题
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次挥手?
- 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
- 但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
- 虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
- 在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。
- Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
- 3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
- 现在把三次握手改成仅需要两次握手,死锁是可能发生的。
- 作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
- TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。
- 服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。
- 若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
四、UDP
4.1 简介
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
4.2 代码编写
服务端
- 开启服务器端口
- 接收客户端的数据包
- 读取数据
- 关闭连接
public class UdpServer {
public static void main(String[] args) throws IOException {
//1.开启服务器端口
DatagramSocket socket = new DatagramSocket(8080);
//2.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
//3.关闭数据流
socket.close();
}
}
客户端
- 建立与服务器之间的连接
- 发送数据
- 关闭数据流
public class UdpClient {
public static void main(String[] args) throws IOException {
//1.建立socket
DatagramSocket socket = new DatagramSocket();
//2.服务器地址、端口
InetAddress address = InetAddress.getByName("localhost");
int port = 8080;
//3.信息
String msg = "hello, socket";
//4.信息打包
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.length(),address,port);
//5.发送包
socket.send(packet);
//6.关闭数据流
socket.close();
}
}
运行测试
- 服务器开启,等待客户端发送数据包
- 客户端启动,连接服务器,发送信息包
- 服务器收到客户端发送的信息
五、TCP 和 UDP区别
-
UDP是无连接的,即发送数据之前不需要建立连接
-
UDP使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制
-
UDP是面向报文的,没有拥塞控制,适合多媒体通信要求
-
UDP支持一对一,一对多,多对一和多对多的交互通信
-
UDP首部开销小,只有8个字节
TCP是面向连接的运输层协议
TCP只能一对一连接
TCP提供可靠的交付服务,提供全双工通信
TCP 面向字节流,头部最低20个字节
六、从输入网址到获取页面的过程
- 查询DNS, 获取域名对应的IP地址
- 浏览器搜索自身的DNS缓存
- 搜索操作系统的DNS缓存
- 读取本地的HOST文件
- 发起一个DNS系统调用(宽带运营服务器查看本身缓存,运营服务器发起一个迭代DNS解析请求)
- 浏览器获得域名对应的IP地址后,发起HTTP三次握手
- TCP/IP建立连接后,浏览器可以向服务器发送HTTP请求了
- 服务器接收到请求后,根据路径参数,经过后端处理将页面返回给浏览器
- 浏览器渲染页面,和外部资源,最终将完整的页面呈现给用户
七、网络安全
对称加密算法(加密、解密 密钥相同)
名称 | 密钥长度 | 运算速度 | 安全性 | 资源消耗 |
---|---|---|---|---|
DES | 56位 | 较快 | 低 | 中 |
3DES | 112位 或 168位 | 慢 | 中 | 高 |
AES | 128、192、256位 | 快 | 高 | 低 |
非对称加密算法(加密密钥 和 解密密钥 不同)
名称 | 成熟度 | 安全性(取决于密钥长度) | 运算速度 | 资源消耗 |
---|---|---|---|---|
RSA | 高 | 高 | 慢 | 高 |
DSA | 高 | 高 | 慢 | 只能用于数字签名 |
ECC | 低 | 高 | 快 | 低(计算量小,存储空间占用小,带宽要求低) |
散列算法
名称 | 安全性 | 速度 |
---|---|---|
SHA-1 | 高 | 慢 |
MD5 | 中 | 快 |