Java 网络编程学习笔记

发布于:2025-09-08 ⋅ 阅读:(25) ⋅ 点赞:(0)

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地址字符串

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单播发送示例

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单播接收示例

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

组播发送示例

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();
    }
}

组播接收示例

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服务器示例

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();
    }
}

  1. 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. 使用完网络资源后需要及时关闭,释放资源


网站公告

今日签到

点亮在社区的每一天
去签到