
Java 网络编程:TCP 与 UDP 的「通信江湖」(基于UDP回显服务器)
你有没有想过,当你用 Java 写的聊天工具发送消息,或是用客户端给服务端传数据时,背后藏着两个 "通信高手"------TCP 和 UDP,在 Java 网络编程中,TCP(传输控制协议)与 UDP(用户数据报协议)是传输层的两种核心协议,分别适用于不同的数据交互场景。二者在连接机制、传输可靠性、数据格式等方面存在显著差异,希望这看完这篇博客后,能对其有所了解。
此次所讲的处在应用层,两个协议由操作系统提供的一组api=>socket.api,由于TCP和UDP差别还是比较大的,编写代码的时候,也是不同的风格,因此,socket api 提供了两套不同的api。
一、UDP 协议:无连接的高效传输
UDP的核心特征为:无连接,不可靠传输,面向数据报 和全双工
1.核心技术特征
- 无连接:无连接机制,无需建立客户端与服务端的逻辑连接,发送方直接向目标地址发送数据,接收方被动接收数据。也就是对于UDP来说,协议本身,不保存对方的信息。
注意:此连接是抽象概念上的连接,就比如说一对男女朋友去领结婚证,就从男女朋友变成了夫妻,在法律上建立了证明婚姻关系的连接。
- 不可靠传输:UDP不提供数据传输的可靠性保证。这意味着使用UDP发送数据时,不能保证数据一定会到达目的地,也不能保证数据的顺序和完整性,也就是说UDP就只负责传输,数据丢不丢失,与其无关,只要传了就行。
可不可靠传输就是表明,尽可能的将数据包传输送达,切如果出现丢包,能感知到
- 面向数据报:面向数据报,读写数据的时候,以一个数据报为单位(不是字符)
- 全双工:一个通信链路,支持双向通信,能读也能写
全双工看字面确实难以理解,这里以打电话的生活中例子帮助你理解一下:
当你和朋友打电话时,你说出 "今天加班吗?" 的同时,朋友可以直接接话 "不加班,约饭吗?"------ 你发消息(说话)的同时,也能接收朋友的消息(听话);
不会出现 "你必须等朋友说完,才能开始说" 的限制,双向互动是实时且同时的,这就是全双工。
2. Java 中 UDP 的实现
API 介绍
DatagramSocket:
DatagramSocket是UDP socket,⽤于发送和接收UDP数据报。
构造方法:
| ⽅法签名 | ⽅法说明 | 
|---|---|
| DatagramSocket() | 创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端⼝(⼀般⽤于客⼾端) | 
| DatagramSocket(int port) | 创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端⼝(⼀般⽤于服务端) | 

方法:
| ⽅法签名 | ⽅法说明 | 
|---|---|
| void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该⽅法会阻塞等待 | 
| void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) | 
| void close() | 关闭此数据报套接字 | 

DatagramPacket:
DatagramPacket是UDP Socket发送和接收的数据报,表示一个完整的UDP数据报。
构造方法:
| ⽅法签名 | ⽅法说明 | 
|---|---|
| DatagramPacket(byte[] buf, int length) | 构造⼀个DatagramPacket以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length) | 
| DatagramPacket(byte[] buf, int offset, int length,SocketAddress address) | 构造⼀个DatagramPacket以⽤来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定⻓度(第⼆个参数length)。address指定⽬的主机的IP和端⼝号 | 
UDP数据报的载荷数据,就可以通过构造方法来指定
⽅法:
| ⽅法签名 | ⽅法说明 | 
|---|---|
| InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP址发送的数据报中,获取接收端主机IP地址 | 
| int getPort() | 从接收的数据报中,获取发送端主机的端⼝号;或从发送的数据报中,获取接收端主机端⼝号 | 
| byte[] getData() | 获取数据报中的数据 | 
3.构造UDP Echo Server
根据上述api,我们可以构建一个简单的回显服务器
客户端给服务器发一个数据请求,服务器返回一个数据响应,回显服务器就是请求是啥,响应是啥
代码⽰例:
服务器主要思路:
- 读取请求并解析
- 根据请求。计算响应(服务器最关键的逻辑)
- 把响应返回给客户端
- 打印一个日志
            
            
              java
              
              
            
          
          public class UdpEchoServer {
    private DatagramSocket socket=null;
    public UdpEchoServer(int port) throws SocketException {
            socket=new DatagramSocket(port);
    }
    public void start() throws IOException {
        //启动服务器
        System.out.println("服务器启动");
        while (true){
        	//1。读取请求并解析
            DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request=new String(requestPacket.getData(),0,requestPacket.getLength()); 
            //2.根据请求。计算响应(服务器最关键的逻辑)     
            String response =process(request);
            DatagramPacket responsePacket=new 
            //3.把响应返回给客户端
            DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //4.打印一个日志
            System.out.printf("[%s:%d] req:%s, resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }
    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer server=new UdpEchoServer(9090);
        server.start();
    }
}重点细节解析:
回显服务器代码我们思路一下几个步骤来理解:
处理请求的过程,典型的服务器都是分成三个步骤
1.读取数据并请求解析
2.根据请求,计算响应(服务器最关键的逻辑)
此处写的是回显服务器,这个环境就相当于省略了
3...把响应返回给客户端
客户端主要思路:
- 从控制台读取用户输入的内容
- 把请求发给服务器,需要构造DatagramPacket对象
- 发送数据报
- 接受服务器的响应
- 从服务器读取的数据进行解析,打印出来
            
            
              java
              
              
            
          
          public class UdpEchoClient {
    private DatagramSocket socket=null;
    //UDP 本身不保存对端的信息,就自己的代码保存一下
    private String serverIp;
    private int serverPort;
    //和服务器不同,此处构造方法是要指定访问的服务器的地址
    public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp=serverIp;
        this.serverPort=serverPort;
        socket=new DatagramSocket();
    }
    public void start() throws IOException {
        Scanner scanner=new Scanner(System.in);
        while (true){
            //1.从控制台读取用户输入的内容
            System.out.println("请输入要发送的内容:");
            if (!scanner.hasNext()){
                break;
            }
            String requset=scanner.next();
            //2.把请求发给服务器,需要构造DatagramPacket对象
            DatagramPacket requestPacket=new DatagramPacket(requset.getBytes(),requset.getBytes().length, InetAddress.getByName(serverIp),serverPort);
            //3.发送数据报
            socket.send(requestPacket);
            //4.接受服务器的响应
            DatagramPacket responsePacket =new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            //5.从服务器读取的数据进行解析,打印出来
            String response=new String(responsePacket.getData(),0,requestPacket.getLength());
            System.out.println(response);
        }
    }
    public static void main(String[] args) throws IOException {
        UdpEchoClient client=new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }
}重点细节解析:
测试代码运行结果












