Java网络编程:TCP与UDP通信实现及网络编程基础

目录

Java网络编程学习笔记(简化案例版):TCP与UDP通信实现及网络编程基础

一、网络编程基础:InetAddress与协议概述

1.1 InetAddress类:IP地址与主机名操作

InetAddress是Java中用于表示IP地址的类,提供了获取主机名、IP地址等网络信息的方法。

常用方法及案例
java 复制代码
import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
        // 1. 获取本地主机信息
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println("本地主机名:" + localHost.getHostName()); // 例如:DESKTOP-XXX
        System.out.println("本地IP地址:" + localHost.getHostAddress()); // 例如:192.168.1.100

        // 2. 根据域名获取IP地址(DNS解析)
        InetAddress baidu = InetAddress.getByName("www.baidu.com");
        System.out.println("百度主机名:" + baidu.getHostName()); // www.baidu.com
        System.out.println("百度IP地址:" + baidu.getHostAddress()); // 例如:180.101.50.242

        // 3. 判断是否可达(超时时间:3000毫秒)
        boolean reachable = baidu.isReachable(3000);
        System.out.println("百度服务器是否可达:" + reachable); // true(网络正常时)
    }
}

注意事项

  • getByName(String host):host可以是域名(如www.baidu.com)或IP字符串(如192.168.1.1);
  • isReachable(int timeout):判断主机是否可达,依赖ICMP协议(类似ping),部分系统可能需要管理员权限。

1.2 TCP与UDP协议:特点与适用场景

TCP(传输控制协议)和UDP(用户数据报协议)是TCP/IP协议族中两种核心传输层协议,核心区别在于是否提供可靠连接

对比维度 TCP UDP
连接方式 面向连接(三次握手建立连接) 无连接(直接发送,无需建立连接)
可靠性 可靠(重传丢失数据包,保证顺序) 不可靠(可能丢包、乱序,不重传)
速度 较慢(三次握手、确认机制开销) 较快(无连接开销,实时性高)
数据边界 无(字节流,需应用层处理边界) 有(数据报独立,一次发送一个完整包)
适用场景 文件传输、登录认证、HTTP通信等 视频通话、直播、DNS查询、游戏数据等
类比生活场景 打电话(需接通,说话有顺序,对方确认听到) 发短信(无需确认对方是否接收,直接发送)

二、UDP通信:无连接的数据报传输

UDP是一种无连接、不可靠的传输协议,数据以"数据报"形式发送,适用于对实时性要求高但可容忍少量丢包的场景(如视频直播、游戏)。

2.1 UDP核心类:DatagramSocket与DatagramPacket

  • DatagramSocket:用于发送/接收数据报的套接字(类似"快递收发站");
  • DatagramPacket:封装数据的数据包(类似"快递包裹",包含数据、目标地址和端口)。

2.2 UDP通信案例:客户端发送消息,服务端接收

服务端实现(接收数据)
java 复制代码
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    public static void main(String[] args) throws Exception {
        // 1. 创建DatagramSocket,绑定端口(服务端必须指定端口,让客户端知道发送到哪里)
        DatagramSocket socket = new DatagramSocket(8888); // 端口8888(1024-65535之间,避免冲突)
        
        // 2. 创建数据包,用于接收数据(缓冲区大小:1024字节)
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        
        System.out.println("UDP服务端启动,等待接收数据...");
        
        // 3. 接收数据(阻塞方法,直到收到数据包)
        socket.receive(packet); // 将接收的数据存入packet
        
        // 4. 解析数据包
        String data = new String(packet.getData(), 0, packet.getLength(), "UTF-8"); // 从缓冲区提取有效数据
        String clientIP = packet.getAddress().getHostAddress(); // 获取客户端IP
        int clientPort = packet.getPort(); // 获取客户端端口
        
        System.out.println("收到来自 " + clientIP + ":" + clientPort + " 的消息:" + data);
    }
}
客户端实现(发送数据)
java 复制代码
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {
    public static void main(String[] args) throws Exception {
        // 1. 创建DatagramSocket(客户端可不指定端口,系统自动分配临时端口)
        DatagramSocket socket = new DatagramSocket();
        
        // 2. 准备发送的数据
        String message = "Hello UDP Server!";
        byte[] data = message.getBytes("UTF-8"); // 字符串转字节数组(网络传输必须用字节)
        
        // 3. 创建数据包,指定目标IP、端口和数据
        InetAddress serverIP = InetAddress.getByName("127.0.0.1"); // 服务端IP(本地回环地址,测试用)
        int serverPort = 8888; // 服务端端口(必须与服务端绑定的端口一致)
        DatagramPacket packet = new DatagramPacket(data, data.length, serverIP, serverPort);
        
        // 4. 发送数据包
        socket.send(packet);
        
        // 5. 关闭资源
        socket.close();
    }
}

2.3 UDP通信步骤与注意事项

服务端步骤:
  1. 创建DatagramSocket并绑定端口(new DatagramSocket(端口号));
  2. 创建DatagramPacket作为接收缓冲区;
  3. 调用socket.receive(packet)阻塞接收数据;
  4. 解析packet获取数据、客户端IP和端口;
客户端步骤:
  1. 创建DatagramSocket(可不指定端口,系统自动分配);
  2. 准备数据并转为字节数组;
  3. 创建DatagramPacket,指定服务端IP、端口和数据;
  4. 调用socket.send(packet)发送数据;
  5. 关闭socket
注意事项:
  • 无连接特性:UDP客户端发送数据前无需与服务端建立连接,可能出现"服务端未启动,客户端仍能发送数据"的情况(数据会丢失);
  • 数据报边界 :每个DatagramPacket是独立的,服务端receive()一次接收一个完整数据包;
  • 不可靠性:UDP不保证数据一定到达,也不保证顺序,需应用层自行处理(如添加序号、重传机制);
  • 端口冲突 :服务端绑定的端口若被占用,会抛出BindException,需更换端口。

三、TCP通信:面向连接的可靠传输

TCP是一种面向连接、可靠的传输协议,通过三次握手建立连接,四次挥手断开连接,保证数据按序、不丢失地传输,适用于对可靠性要求高的场景(如文件传输、登录)。

3.1 TCP核心类:Socket与ServerSocket

  • ServerSocket:服务端套接字,用于监听客户端连接请求(类似"总机接线员");
  • Socket:客户端与服务端建立连接后的"双向通道",通过输入流接收数据,输出流发送数据(类似"电话接通后的通话线路")。

3.2 TCP通信案例1:多线程服务端(支持同时处理多个客户端)

服务端
java 复制代码
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws Exception {
        //目标:创建服务器,接收数据
        System.out.println("服务器启动...");
        //创建TCP服务器
        ServerSocket server = new ServerSocket(9999);
        while (true) {
            //等待客户端连接
            Socket ss = server.accept();
            //输出连接客户端的IP地址
            System.out.println(ss.getInetAddress().getHostAddress()+"客户端已上线");
            //将客户端连接的Socket对象交给ServerRead线程处理
            new ServerRead(ss).start();
        }

    }
}

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ServerRead extends Thread{
    private Socket tcpserver;
    //创建有参数构造方法接收服务器套接字
    public ServerRead(Socket tcpserver)
    {
        this.tcpserver = tcpserver;
    }
    @Override
    public void run()
    {
        try {
            //创建输入流
            InputStream is = tcpserver.getInputStream();
            //使用特殊数据流包装输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                //获取数据
                String str = dis.readUTF();
                System.out.println("收到客户端:"+ tcpserver.getInetAddress().getHostAddress() +",端口:"+ tcpserver.getPort() + ",的数据:" + str);
            }
        } catch (Exception e) {
            //输入exit或客户端断开连接(非正常退出),则输出客户端已下线
            System.out.println(tcpserver.getInetAddress().getHostAddress() + "客户端已下线");
        }
    }
}
客户端实现
java 复制代码
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {
    public static void main(String[] args) throws Exception {
        //目标:创建TCP客户端,发送数据
        System.out.println("客户端启动...");
        //创建TCP客户端,指定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1", 9999);
        //创建输出流
        OutputStream os = socket.getOutputStream();
        //使用特殊数据流包装输出流
        DataOutputStream dos = new DataOutputStream(os);
        Scanner sc = new Scanner(System.in);
        //发送数据
        while (true) {
            System.out.println("请输入数据:");
            String str = sc.nextLine();
            if ("exit".equals(str)){
                System.out.println("客户端退出...");
                socket.close();
                break;
            }
            dos.writeUTF(str);
            dos.flush();
        }
    }
}

3.3 TCP通信案例2:B/S架构模拟(使用线程池管理)

B/S架构(Browser/Server)是TCP通信的典型应用,浏览器(客户端)向Web服务器发送HTTP请求,服务器响应HTML页面。以下模拟这一过程:

简易HTTP服务端(支持浏览器访问)
java 复制代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080); // HTTP默认端口是80,这里用8080测试
        ExecutorService threadPool = Executors.newFixedThreadPool(10); // 线程池管理请求
        System.out.println("HTTP服务端启动,监听端口8080,浏览器访问:http://127.0.0.1:8080");
        
        while (true) {
            Socket socket = serverSocket.accept(); // 接收浏览器连接(大堂经理接客)
            threadPool.submit(() -> handleHttp(socket)); //创建子线程 (转交给服务员)
        }
    }
    
    private static void handleHttp(Socket socket) {
        try (
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
        ) {
            // 读取HTTP请求(只读取请求行,简化处理)
            String requestLine = br.readLine();
            System.out.println("HTTP请求行:" + requestLine); // 例如:GET /index.html HTTP/1.1
            
            // 构建HTTP响应(响应行 + 响应头 + 空行 + 响应体)
            pw.println("HTTP/1.1 200 OK"); // 响应行:协议版本 状态码 状态描述
            pw.println("Content-Type: text/html;charset=UTF-8"); // 响应头:内容类型
            pw.println(); // 空行(分隔响应头和响应体)
            pw.println("<h1>Hello!</h1>"); // 响应体(HTML内容)
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试方式 :启动服务端后,打开浏览器访问http://127.0.0.1:8080,页面会显示"Hello!"。

3.4 TCP通信步骤与注意事项

服务端步骤:
  1. 创建ServerSocket并绑定端口(new ServerSocket(端口号));
  2. 循环调用serverSocket.accept()阻塞等待客户端连接,返回Socket对象;
  3. 通过Socket获取输入流(接收客户端数据)和输出流(发送响应);
  4. 处理数据(如多线程/线程池并发处理多个客户端);
  5. 关闭SocketServerSocket
客户端步骤:
  1. 创建Socket,指定服务端IP和端口(new Socket(ip, port));
  2. 通过Socket获取输入流(接收服务端响应)和输出流(发送数据);
  3. 读写数据;
  4. 关闭Socket
注意事项:
  • 连接建立 :TCP客户端new Socket(ip, port)时,若服务端未启动,会抛出ConnectException(连接拒绝);
  • 流的关闭顺序 :通常先关闭输出流,再关闭输入流,最后关闭Socket
  • 粘包问题:TCP是字节流,多次发送的小数据可能被合并成一个包发送(需应用层处理边界,如固定长度、分隔符);
  • 线程池优化 :服务端处理多个客户端时,使用线程池避免"客户端过多导致线程创建耗尽资源"的问题(如案例3.2中的ExecutorService)。

四、并发与并行:网络编程中的多任务处理

4.1 概念区分

  • 并发(Concurrency):同一时间段内,多个任务交替执行(单核CPU通过时间片切换实现,如"一个CPU同时处理多个客户端请求");
  • 并行(Parallelism):同一时刻,多个任务同时执行(多核CPU,多个任务在不同核心上真正同时运行)。

4.2 网络编程中的应用

  • TCP服务端多线程/线程池:通过并发处理多个客户端连接(如案例3.2中,线程池同时处理5个客户端,单核CPU时交替执行,多核时并行执行);
  • UDP服务端:可通过多线程同时接收和处理多个数据报,提高吞吐量。

五、总结

Java网络编程核心基于TCP和UDP协议,两者各有适用场景:

  • UDP:无连接、速度快、不可靠,适用于实时通信(如视频、游戏);
  • TCP:面向连接、可靠、速度较慢,适用于数据准确性要求高的场景(如文件传输、登录)。

开发步骤上,UDP通过DatagramSocketDatagramPacket实现数据报传输,TCP通过ServerSocketSocket实现字节流传输。实际开发中,需根据业务需求选择协议,并注意资源释放(如关闭Socket)、并发处理(如线程池)等问题。