Java 网络编程学习笔记

Java网络编程学习笔记

网络编程概述

CS和BS架构

  • CS(Client Server):客户端-服务器架构

  • BS(Browser Server):浏览器-服务器架构

网络编程三要素

要素 描述
IP 设备在网络中的地址,唯一标识
端口 应用程序在设备中唯一的标识
协议 数据在网络中传输的规则,如TCP/UDP

IP地址

IPv4

  • 32位,点分十进制表示

  • 范围:0.0.0.0 - 255.255.255.255

  • 特殊地址:127.0.0.1(localhost,本地回环地址)

IPv6

  • 128位,冒分十六进制表示

  • 范围:0:0:0:0:0:0:0:1 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff

  • 0位压缩表示法:FF01:0:0:0:0:0:0:1101 → FF01::1101(只能使用一次)

常用命令

  • ipconfig:获取本机IP

  • ping:测试IP是否可用

端口

  • 端口号范围:0-65535(2字节表示)

  • 0-1023:系统保留端口,不能被程序使用

  • 一个端口号只能被一个应用程序使用

协议

网络协议分层

层级 协议示例
应用层 HTTP, FTP, DNS, SMTP, Telnet
传输层 TCP, UDP
网络层 IP, ICMP, ARP
物理链路层 MAC

UDP协议

  • 用户数据报协议(User Datagram Protocol)

  • 面向无连接通信协议

  • 特点:速度快,有大小限制(一次最多发送64KB数据),数据不安全,易丢失数据

TCP协议

  • 传输控制协议(Transmission Control Protocol)

  • 面向连接的通信协议

  • 特点:速度慢,没有大小限制,数据安全

InetAddress类

InetAddress类表示互联网协议(IP)地址,封装IP地址。

常用方法

  • static InetAddress getByName(String host):确定主机名称的IP地址

  • String getHostName():获取此IP地址的主机名

  • String getHostAddress():返回文本显示中的IP地址字符串

java 复制代码
import java.net.InetAddress;
import java.net.UnknownHostException;
​
public class NetDemo {
    public static void main(String[] args) throws UnknownHostException {
        // 获取InetAddress对象
        InetAddress address = InetAddress.getByName("10.102.160.242");
        
        System.out.println(address);
        System.out.println("主机名: " + address.getHostName()); // DESKTOP-PB9U58V
        System.out.println("IP地址: " + address.getHostAddress()); // 10.102.160.242
    }
}

注意

  • InetAddress.getByName()方法根据提供的主机名进行网络查询或DNS解析

  • 如果提供的是IP地址字符串,则不会进行任何网络查询或DNS解析

UDP协议

UDP单播发送示例

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
​
public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
        // 创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();
        Scanner sc = new Scanner(System.in);
        
        while (true) {
            System.out.println("请输入发送的数据:");
            String str = sc.nextLine();
            if ("886".equals(str)) {
                break;
            }
            
            byte[] bytes = str.getBytes();
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10086;
            
            // 打包数据
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
            
            // 发送数据
            ds.send(dp);
        }
        
        // 释放资源
        ds.close();
    }
}

UDP单播接收示例

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
​
public class ReceiveMessageDemo {
    public static void main(String[] args) throws IOException {
        // 创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket(10086);
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
        
        while (true) {
            // 接收数据(阻塞方法)
            ds.receive(dp);
            
            // 解析数据
            byte[] data = dp.getData();
            int length = dp.getLength();
            String hostName = dp.getAddress().getHostAddress();
            String name = dp.getAddress().getHostName();
            
            System.out.println("ip为" + hostName + "的" + name + "说:" + 
                              new String(data, 0, length));
        }
    }
}

注意事项

  • 接收端需要绑定端口,且端口必须与发送端指定的端口一致

  • receive()方法是阻塞的,程序会等待发送端发送消息

单播、组播和广播

组播地址

  • 组播地址范围:224.0.0.0 - 239.255.255.255

  • 其中224.0.0.0 - 224.0.0.255为预留的组播地址

广播地址

  • 广播地址:255.255.255.255

组播发送示例

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
​
public class SendMessageDemo1 {
    public static void main(String[] args) throws IOException {
        // 创建MulticastSocket对象
        MulticastSocket ms = new MulticastSocket();
        
        // 创建DatagramPacket对象
        String data = "hello,udp";
        byte[] bytes = data.getBytes();
        InetAddress address = InetAddress.getByName("224.0.0.1");
        int port = 10086;
        
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
        
        // 发送数据
        ms.send(dp);
        
        // 释放资源
        ms.close();
    }
}

组播接收示例

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
​
public class ReceiveMessageDemo1 {
    public static void main(String[] args) throws IOException {
        // 创建MulticastSocket对象
        MulticastSocket ms = new MulticastSocket(10086);
        
        // 将当前本机添加到224.0.0.1组中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);
        
        // 创建DatagramPacket对象
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
        
        // 接收数据
        ms.receive(dp);
        
        // 解析数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        String ip = dp.getAddress().getHostAddress();
        String name = dp.getAddress().getHostName();
        int port = dp.getPort();
        
        System.out.println("数据是: " + new String(data, 0, length));
        System.out.println("发送端的ip是: " + ip);
        System.out.println("发送端的端口是: " + port);
        System.out.println("发送端的名称是: " + name);
        
        // 释放资源
        ms.close();
    }
}

TCP协议

TCP通信程序

  • 通信的两端各建立一个Socket对象

  • 通信之前要保证连接已经建立

  • 通过Socket对象产生IO流来进行网络通信

TCP服务器示例

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
​
public class Server {
    public static void main(String[] args) throws IOException {
        // 先启动服务端
        
        // 1.创建ServerSocket对象
        ServerSocket ss = new ServerSocket(10086);
        
        // 2.监听客户端连接(阻塞方法)
        Socket accept = ss.accept(); // 返回客户的连接对象
        
        // 3.从连接通道中获取输入流读取数据
        InputStream in = accept.getInputStream();
        InputStreamReader isr = new InputStreamReader(in);
        
        int b;
        while ((b = isr.read()) != -1) {
            System.out.print((char) b);
        }
        
        // 4.释放资源
        in.close();
        ss.close();
    }
}

TCP 三次握手与四次挥手

三次握手(建立连接)

TCP 使用三次握手(3-way Handshake)机制来建立一条可靠的连接。这个过程确保客户端和服务器都准备好进行数据传输,并同步序列号。

详细过程:

步骤详解:

  1. 第一次握手(SYN)

    • 客户端发送一个 TCP 数据包,其中:

      • SYN 标志位设置为 1(表示请求建立连接)

      • 随机生成一个序列号 Seq = J

    • 客户端进入 SYN-SENT 状态

    • 目的:客户端告诉服务器"我想建立连接,我的初始序列号是 J"

  2. 第二次握手(SYN+ACK)

    • 服务器收到请求后,发送回应数据包,其中:

      • SYNACK 标志位都设置为 1

      • 随机生成自己的序列号 Seq = K

      • 确认号 Ack = J + 1(表示期望收到从 J+1 开始的数据)

    • 服务器进入 SYN-RCVD 状态

    • 目的:服务器说"我同意连接,我的初始序列号是 K,已收到你的请求"

  3. 第三次握手(ACK)

    • 客户端收到回应后,发送确认数据包,其中:

      • ACK 标志位设置为 1

      • 序列号 Seq = J + 1

      • 确认号 Ack = K + 1

    • 客户端进入 ESTABLISHED 状态

    • 服务器收到后也进入 ESTABLISHED 状态

    • 目的:客户端确认"收到你的同意,我们现在可以开始通信了"

为什么需要三次握手?

  • 防止已失效的连接请求突然传到服务器:网络延迟可能导致旧的连接请求晚到达,二次握手无法区分新旧请求

  • 同步双方序列号:确保双方都知道对方的初始序列号,为可靠传输做准备

  • 双向确认:确保客户端和服务器都有发送和接收能力

四次挥手(断开连接)

TCP 使用四次挥手(4-way Handshake)机制来终止连接。这个过程允许双方优雅地关闭连接,确保所有数据都传输完毕。

详细过程:

步骤详解:

  1. 第一次挥手(FIN)

    • 主动关闭方(假设是客户端)发送 FIN 数据包:

      • FIN 标志位设置为 1

      • 序列号 Seq = U(等于已传送数据的最后一个字节的序号加1)

    • 客户端进入 FIN-WAIT-1 状态

    • 目的:客户端告诉服务器"我的数据发完了,准备关闭连接"

  2. 第二次挥手(ACK)

    • 服务器收到 FIN 后,发送 ACK 回应:

      • ACK 标志位设置为 1

      • 确认号 Ack = U + 1

      • 序列号 Seq = V

    • 服务器进入 CLOSE-WAIT 状态

    • 客户端收到后进入 FIN-WAIT-2 状态

    • 目的:服务器说"收到你的关闭请求,但我可能还有数据要发给你"

  3. 第三次挥手(FIN)

    • 服务器完成数据发送后,发送 FIN 数据包:

      • FIN 标志位设置为 1

      • 序列号 Seq = W(可能等于 V 或 V+已发送数据量)

      • 确认号 Ack = U + 1

    • 服务器进入 LAST-ACK 状态

    • 目的:服务器说"我的数据也发完了,准备关闭连接"

  4. 第四次挥手(ACK)

    • 客户端收到 FIN 后,发送 ACK 回应:

      • ACK 标志位设置为 1

      • 序列号 Seq = U + 1

      • 确认号 Ack = W + 1

    • 客户端进入 TIME-WAIT 状态,等待 2MSL(最大报文段生存时间)后关闭

    • 服务器收到 ACK 后立即关闭连接

    • 目的:客户端确认"收到你的关闭请求,现在我们都关闭吧"

为什么需要四次挥手?

  • 半关闭状态:TCP 连接是全双工的,允许一方在发送完数据后关闭发送通道,同时保持接收通道开放

  • 确保数据完整传输:给予被动方时间处理完剩余数据后再完全关闭

  • 可靠终止:确保双方都知道连接已终止,避免悬空连接

为什么需要 TIME-WAIT 状态?

  • 防止旧连接数据包干扰:等待 2MSL 确保所有本连接的数据包都在网络中消失

  • 保证可靠终止:如果最后一次 ACK 丢失,服务器会重发 FIN,客户端可以再次响应

实际编程中的注意事项:

  • 作为服务器,应该正确处理连接关闭,调用 close() 方法

  • 客户端应该优雅关闭连接,而不是强制终止

  • 网络编程中需要注意处理各种异常关闭情况

  • 对于频繁建立关闭的连接,需要注意端口重用问题(SO_REUSEADDR)

补充说明

DatagramSocket细节

  • 绑定端口:通过指定端口往外发送数据

  • 空参构造:从所有可用端口中随机获取一个进行使用

  • 带参构造:指定端口进行绑定使用

DatagramPacket参数

  • byte buf[]:字符数组

  • int length:字符数组的长度

  • InetAddress address:目标地址

  • int port:目标端口

注意事项

  1. TCP通信需要先启动服务端,再启动客户端

  2. UDP接收端需要绑定端口,且端口必须与发送端指定的端口一致

  3. receive()accept()方法是阻塞的,程序会等待数据或连接

  4. 使用完网络资源后需要及时关闭,释放资源

相关推荐
大厂码农老A4 小时前
面试官:“聊聊你最复杂的项目?” 为什么90%的候选人第一句就栽了?
java·面试
爱读源码的大都督4 小时前
Java已死?别慌,看我如何用Java手写一个Qwen Code Agent,拯救Java
java·人工智能·后端
lssjzmn4 小时前
性能飙升!Spring异步流式响应终极指南:ResponseBodyEmitter实战与架构思考
java·前端·架构
LiuYaoheng5 小时前
【Android】View 的基础知识
android·java·笔记·学习
勇往直前plus5 小时前
Sentinel微服务保护
java·spring boot·微服务·sentinel
星辰大海的精灵5 小时前
SpringBoot与Quartz整合,实现订单自动取消功能
java·后端·算法
名誉寒冰5 小时前
TCP, 三次握手, 四次挥手, 滑动窗口, 快速重传, 拥塞控制, 半连接队列, RST, SYN, ACK
网络·tcp/ip·php
塵觴葉5 小时前
Linux内核网络的连接跟踪conntrack简单分析
linux·网络·conntrack
小鸡脚来咯5 小时前
一个Java的main方法在JVM中的执行流程
java·开发语言·jvm