Java Language——网络编程

计算机网络主要功能包括资源共享、信息传输和集中处理、负载均衡和分布式处理、综合信息服务等。实际上 Java 的网络编程就是服务器通过 ServerSocket 建立监听,客户端通过 Socket 连接到指定服务器后,通信双方就可以通过 IO 流进行通信了。

OSI 七层模型:

OSI 七层模型TCP/IP概念层模型功能包含协议
应用层应用层文件传输、电子邮件、文件服务、虚拟终端HTTP、SNMP、FTP、TFTP、SMTP、DNS、Telnet
表示层数据格式化、代码转换、数据加密没有协议
会话层解除或建立与别的接点的联系没有协议
传输层传输层提供端对端的接口TCP(传输控制协议, 安全度高)、UDP(效率快, 安全度低, 可能会有数据丢失)、SSL、TLS
网络层网络层为数据包选择路由IP、ICMP、RIP、OSPF、BGP、IGMP
数据链路层链路层传输有地址的帧以及错误检测功能SLIP、CSLIP、PPP、ARP、RARP、MTU
物理层以二进制数据形式在物理媒体上传输数据ISO2110、IEEE802、IEEE802.2

通信协议通常由语义部分、语法部分、变换规则三部分组成。其实所谓的协议就是在数据传输基础上封装自己的文本内容,先自上而下,后自下而上处理数据头部:

网络编程 数据头部的处理过程

IP地址:32位整数(4个8位二进制数),NIC 统一负责全球 IP 地址的规划、管理,而 Intel NIC、APNIC、RIPE 三大网络信息中心具体负责美国及其他地区的 IP 地址分配,APNIC(总部在日本东京大学)负责亚太地区的 IP 管理,我国申请 IP 地址也要通过 APNIC。IP 地址被分为 A、B、C、D、E 五类。

A类:10.0.0.0-10.255.255.255
B类:172.16.0.0-172.31.255.255
C类:192.168.0.0-192.168.255.255
端口号:16位整数,0-65535
公认端口:0-1023
注册端口:1024-49151
动态和私有端口:49152-65535

1.Java网络API

Java 提供了四大网络通信相关的类 :

  • InetAddress:用于标识网络上的硬件资源,表示 IP 地址;
  • URL:统一资源定位符,格式为 协议名称和资源名称,中间用冒号隔开);
  • Sockets:使用 TCP 协议实现的网络通信的 Socket 相关的类);
  • Datagram:使用 UDP 协议,将数据保存在数据报中,通过网络进行通信)。

java.net 包下 URL 和 URLConnection 等类提供了以编程方式访问 web 服务的功能,URLDecoder 和 URLEncoder 提供了普通字符串和 application/x-www-form-urlencoded MIME 字符串相互转换的静态方法。

1.InetAddress

Java 提供了 InetAddress 类表示 IP 地址:

InetAddress ip = InetAddress.getByName("www.baidu.com"); // 根据主机名来获取对应的InetAddress实例
boolean b = ip.isReachable(1000); // 判断是否可达
String address = ip.getHostAddress(); // 获取该InetAddress实例的IP字符串

InetAddress local = InetAddress.getByAddress(new byte[]{127, 0, 0, 1}); //根据原始IP地址来获取对应的InetAddress实例
boolean b1 = ip.isReachable(1000); // 判断是否可达
String hostName = local.getCanonicalHostName(); // 获取该InetAddress实例对应的全限定域名

2.URL编码

URLDecoder 和 URLEncoder 提供了 URL 编码解码的功能,用于普通字符串和 application/x-www-form-urlencoded MIME 字符串之间的相互转换:

// URL编码
String s = URLDecoder.decode("%E5%8C%97%E4%BA%AC", "UTF-8");

// URL解码
String s1 = URLEncoder.encode("北京" , "UTF-8");

3、URLConnection

URLConnection 指应用程序与 URL 之间的通信连接,HttpURLConnection 指 URL 与 URL 之间的 HTTP 连接,程序可以通过 URLConnection 实例向 URL 发送请求、读取 UR 引用的资源。

4.TCP协议

TCP 协议(传输控制协议)是面向连接的、可靠的、有序的、重量级的、基于字节流的传输层通信协议,TCP 将应用层的数据流分割成报文段并发送给目标节点的 TCP 层。

TCP 为了保证不丢失包,所有数据包都有序号,对方收到则发送 ACK 确认,未收到则重传。TCP 还会使用校验和来检验数据在传输过程中是否有误。

1.TCP的三次握手

“握手” 是为了建立连接,三次握手的过程由客户端进行触发,TCP 三次握手的流程:

网络编程 TCP三次握手的流程

  1. 第一次握手:建立连接时,Client 发送 SYN 报文(seq=x)到 Server,并进入 SYN_SEND 状态,等待 Server 确认;
  2. 第二次握手:Server 收到 SYN 报文,必须确认 Client 的 SYN(ack=x+1),同时自己也发送一个 SYN 报文(seq=y),即 SYN + ACK 报文,此时 Server 进入 SYN_RECV 状态;
  3. 第三次握手:Client 收到 Server 的 SYN + ACK 报文,向 Server 发送确认报文 ACK(ack=y+1),此包发送完毕,Client 和 Server 进入 ESTAB_LISHED 状态,完成三次握手。

常见的问题:

1、为什么需要三次握手才能建立起连接?

为了初始化 Sequence Number 的初始值,通信双方需要通知对方自己的 Sequence Number,也就是图中的 x 和 y,这个号会作为以后数据通信的序号,以保证应用层接收到的数据不会因为网络的问题而乱序,TCP 会用这个序号来拼接数据,因此在服务器回发它的 Sequence Number 及第二次握手之后,客户端还需要发送确认报文给服务端,告知服务端客户端已经收到服务端 Sequence Number 了。

2、首次握手 SYN 超时?

服务端收到客户端的 SYN,回复 SYN-ACK 的时候未收到 ACK 确认,服务端就会不断重试直至超时,Linux 默认等待 63 秒才断开连接。

3、建立连接后,客户端出现故障怎么办?

TCP 有保活机制,在一段时间,连接处于非活动状态,开启保活功能的一端将向对方发送保活探测报文,如果未收到响应则继续发送,尝试次数达到保活探测数仍未收到响应则中断连接。

2.TCP的四次挥手

“挥手” 是为了断开连接,四次挥手的过程由客户端或服务端执行 close 进行触发,这里我们假设由客户端主动触发 close,TCP 四次挥手的流程:

网络编程 TCP四次挥手的流程

TCP 连接必须经过时间 2MSL 后才真正释放掉。

  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 进入 CLOSED 状态,完成四次挥手。

常见的问题:

1、为什么需要四次挥手才能断开连接?

因为 TPC 是全双工通信,发送方和接收方都需要 FIN 报文和 ACK 报文,发送方和接收方各自需要两次挥手即可,只不过有一方是被动的。

2、为什么会有 TIME_WAIT 状态?

确保有足够的时间让对方收到 ACK 报文;避免新旧连接混淆。

3、服务器出现大量 CLOSE_WAIT 状态的原因?

对方关闭 socket 连接后,我方忙于读或写,没有及时关闭连接。多数情况是程序里有 bug,需要检查代码,特别是释放资源的代码;检查配置,特别是处理请求的线程配置

3.TCP的滑动窗口

RTT:发送一个数据包到收到对应的 ACK 所花费的时间。
RTO:重传时间间隔。

TCP 使用滑动窗口做流量控制和乱序重排。滑动窗口保证了 TCP 的可靠性和流控特性。

对于 TCP 会话的发送方,任何时候其发送缓存内的数据,都可以分为四类:

  1. 已经发送并且得到 ACK 回应的;
  2. 已经发送但没有得到 ACK 回应的;
  3. 未发送,但对端允许发送的;
  4. 未发送且由于达到了滑动窗口的大小,对端不允许发送的;

2、3 这两部分数据所组成的连续空间就是滑动窗口。

网络编程 TCP的滑动窗口

5.TCP通信

Socket 是 Java 里的 TCP/IP 实现。TCP/IP 协议是一种可靠协议,它在通信两端各建立一个 Socket,从而在通信两端之间形成网络虚拟链路进行通信,Java 使用 Socket 对象来代表两端的通信接口,并通过 Socket 产生 IO 流来进行网络通信。

基于 TCP 协议实现网络通信的类包含客户端的 Socket 类(实现了 TCP/IP 协议,可以连接到服务端收发数据),服务端的 ServerSocket 类。

IP 地址 + 端口就组成了所谓的 Socket,Socket 是网络上运行的程序之间双向通信链路的终结点,是 TCP 和 UDP 的基础。

1、单客户端与单服务端的通信

服务端开启线程,启动 Socket 服务监听,等待客户端连接,连接成功读取数据:

ServerSocket serverSocket = new ServerSocket(10000); // 创建ServerSocket
Socket socket = serverSocket.accept();               // 开始监听端口,等待客户端连接,若连接上则继续往下执行
DataInputStream reader = new DataInputStream(socket.getInputStream()); // 获取数据输入流
String msg = reader.readUTF();                       // 读一个UTF-8的信息
socket.shutdownInput();                              // 关闭输入流
System.out.println(msg);                             // 输出到控制台

DataOutputStream writer = new DataOutputStream(socket.getOutputStream()); // 获取数据输出流
writer.writeUTF("服务端响应消息: 呵呵..");                            // 写一个UTF-8的信息
socket.shutdownOutput();                             // 关闭输出流

// 关闭相关资源
// 对于同一个socket,如果关闭了输出/输入流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可。
socket.close();
serverSocket.close();                                // 关闭socket也会关闭流

客户端需要在子线程中建立服务端连接并发送消息:

Socket socket = new Socket("localhost", 10000);      // 创建客户端Socket
DataOutputStream writer = new DataOutputStream(socket.getOutputStream()); // 获取数据输出流
writer.writeUTF("客户端发送消息: 嘿嘿..");                            // 写一个UTF-8的信息
socket.shutdownOutput();                             // 关闭输出流

DataInputStream reader = new DataInputStream(socket.getInputStream()); // 获取数据输入流
String msg = reader.readUTF();                       // 读一个UTF-8的信息
socket.shutdownInput();                              // 关闭输入流
System.out.println(msg);                             // 输出到控制台

socket.close();                                      // 关闭socket也会关闭流

最终就可以在服务端和客户端的控制台上分别看到 “客户端发送消息: 嘿嘿…” 与 “服务端响应消息: 呵呵…” 信息了。

2、多客户端与单服务端的通信

实现多客户端通信则需要在服务端创建 ServerSocket,循环调用 accept() 等待客户端连接。首先创建服务端线程处理类:

public class ServerThread extends Thread {
    Socket socket = null; // 和线程相关的Socket
    DataInputStream reader;
    DataOutputStream writer;
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
    // 线程执行的操作,响应客户端的请求
    @Override
    public void run() {
        try {
            DataInputStream reader = new DataInputStream(socket.getInputStream());// 获取数据输入流
            String msg = reader.readUTF();            // 读一个UTF-8的信息
            socket.shutdownInput();                   // 关闭输入流
            System.out.println(msg);                  // 输出到控制台

            writer = new DataOutputStream(socket.getOutputStream());// 获取数据输出流
            writer.writeUTF("服务端响应消息: 呵呵..");   // 写一个UTF-8的信息
            socket.shutdownOutput();                  // 关闭输出流
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket!=null)
                    socket.close();                   // 关闭socket也会关闭流
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服务端代码修改为:

ServerSocket serverSocket = new ServerSocket(10000); // 创建ServerSocket
Socket socket = null;
// 循环监听等待客户端连接
while (true) {
    socket = serverSocket.accept();                  // 开始监听端口,等待客户端连接,若连接上则继续往下执行
    ServerThread serverThread = new ServerThread(socket);
    // 未设置优先级可能会导致运行时速度非常慢,可降低优先级。
    serverThread.setPriority(4);                     // 设置线程优先级,范围[1,10],默认5
    serverThread.start();                            // 启动线程
}

客户端代码不变。最终就可以运行多个客户端来进行通信了。

另外在实际应用中,更多的是传递对象,传递对象可以使用 ObjectOutputStream 对象序列化流,传递对象:

Socket socket = new Socket("localhost", 10000);      // 创建客户端Socket
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(object);                             // 写一个对象,序列化流
socket.shutdownOutput();                             // 关闭输出流
socket.close();                                      // 关闭socket也会关闭流

6.UDP协议

UDP 协议(用户数据报协议)是无连接的、不可靠的、无序的、面向报文的、轻量级的,UDP 协议以数据报作为数据传输的载体,UDP 数据包报头只有 8 个字节,相比于 TCP 数据包报头有 20 个字节,额外开销较小。

UDP 速度要比 TCP 快,多用于在线视频媒体、电视广播、多人在线游戏等。

7.UDP通信

进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的 Socket(主机地址和端口号),然后再将数据报发送出去。基于 UDP 协议实现网络通信的类包含 DatagramPacket 类(表示数据报包,UDP 通信中的数据单元)和 DatagramSocket 类(进行端到端通信的类)。

1、使用 DatagramSocket 发送、接受数据

服务端代码:

DatagramSocket socket = new DatagramSocket(10000); // 创建服务端DatagramSocket
byte[] data = new byte[1024];                      // 创建字节数组
DatagramPacket packet = new DatagramPacket(data, data.length); // 创建数据报,用于接收客户端发送的数据
socket.receive(packet);                            // 接收客户端发送的数据,接收到数据报才继续执行(会阻塞)

String info = new String(data, 0, packet.getLength());  // 读取数据
System.out.println("服务端接收到信息: "+info);

// 向客户端响应数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 ="欢迎您!".getBytes();
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port); // 创建数据报,包含响应的数据
socket.send(packet2);                              // 响应客户端
socket.close();                                    // 关闭资源

客户端代码:

// 定义服务器地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 10000;
byte[] data ="嘿嘿..".getBytes();

DatagramPacket packet = new DatagramPacket(data, data.length, address, port); // 创建数据报,包含发送的数据
DatagramSocket socket = new DatagramSocket();      // 创建客户端DatagramSocket
socket.send(packet);                               // 向服务端发送数据

// 接受服务端响应的数据
byte[] data2 = new byte[1024];                     // 创建字节数组
DatagramPacket packet2 = new DatagramPacket(data2, data2.length); // 创建数据报,用于接收客户端发送的数据
socket.receive(packet2);                           // 接收服务端响应数据,接收到数据报才继续执行(会阻塞)

String info2 = new String(data, 0, packet.getLength());  // 读取数据
System.out.println("服务端响应信息: "+info2);

最终就可以在服务端和客户端的控制台上看到相应的信息了。实现 UDP 多客户端单服务端通信可以参考 TPC 开启多线程的方式,这里不再赘述。

2、使用 MulticastSocket 实现多点广播

DatagramSocket 只允许数据报发送到指定的目标地址,而 MulticastSocket 可以将数据报以广播方式发送到多个客户端。MulticastSocket 继承于 DatagramSocket。

多点广播示意图如下:

多点广播示意图

创建 MulticastSocket 对象后还需要加入到指定的多点广播地址,MulticastSocket 使用 joinGroup(InetAddress multicastAddr) 方法加入到指定组,使用 leaveGroup(InetAddress multicastAddr) 方法脱离一个组。

MulticastSocket 用于发送、接受数据报的方法和 DatagramSocket 完全一样。但 MulticastSocket 多了一个 setTimeToLive(int ttl) 方法,ttl 参数用于设置数据报最多可以跨过多少个网络(0:停留在本地主机;1:本地局域网;32:只能发送到本站点的网络上;64:保留在本地区;128:保留在本大洲;255:所有地方),默认1。

8.代理服务器

从 Java5 开始,java.net 包下提供了 Proxy(表示代理服务器)、ProxySelector(表示代理选择器)两个类。


常见问题

1、http 和 https 的区别?

https 即安全超文本传输协议,https 在传输层增加了 SSL 层,SSL 采用身份认证和数据加密保证网络通信的安全和数据的完整性。

  • https 需要到 CA 申请证书,而 http 不需要;
  • https 是密文传输,而 http 是明文传输;
  • 连接方式不同,https 默认使用 443 端口,http 使用 80 端口;
  • https = http + 加密 + 认证 + 完整性保护,较 http 安全。

2、https 真的安全吗?

不一定,浏览器默认填充 http://,请求需要进行转发到 https 的端口,有被劫持的风险。这一点可以使用 HSTS 优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值