【Java网络编程02】套接字编程

1. Socket套接字

概念:Socket套接字,就是系统提供用于实现网络通信的技术,是基于TCP/IP协议的网络通信基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
分类
我们可以把Socket套接字分为两类

  1. 流套接字:使用传输层TCP协议

TCP,即Transmission Control Protocol(传输控制协议)
以下为TCP的特点:(细节后续有专门章节解释)

  • 有连接的
  • 可靠传输
  • 面向字节流的
  • 全双工的
  1. 数据报套接字:使用传输层UDP协议

UDP,即User Datagram Protocol(用户数据报协议)
一下为UDP的特点(细节后续有专门章节解释)

  • 无连接的
  • 不可靠传输
  • 面向数据报
  • 全双工

这里简单介绍一些相关概念:

面向字节VS面向数据报
这里与文件流中的字符流与字节流很类似,面向字节表明网络传输数据是以字节为单位的,而面向数据报表明UDP传输依靠UDP数据报进行传输(稍后我们在代码中会体现)

全双工VS半双工
半双工:通信双方基于管道进行传输,但是数据只能单向流动,如图所示:
【Java网络编程02】套接字编程-LMLPHP
全双工:通信双方可以实现数据的双向流动,如图所示:
【Java网络编程02】套接字编程-LMLPHP

2. UDP数据报套接字编程

2.1 相关API

在运用UDP进行网络编程之前,我们需要先熟悉UDP套接字编程相关API的使用,只有掌握了这些API工具才能更好地进行编程的实现,我们主要学习的有两个类:DatagramSocketDatagramPacket

  1. DatagramSocket:OS提供了网络编程所需的API,也叫做"Socket API",而Java又进行了一层封装,使用提供的类DatagramSocket就可以实现对于网卡等硬件设备文件的读写操作。
  2. DatagramPacket:前面我们有介绍过,UDP协议是面向数据报的,因此网络传输单位不是字节而是数据报,Java提供类DatagramPacket相当于数据报的抽象,因此实例化该对象相当于构建了一个数据报。在编程中我们发送的与接收数据的参数就是DatagramPacket对象

DatagramSocket(列举部分)

DatagramPacket(列举部分)

2.2 UDP编程代码

2.2.1 实现需求

作为我们的第一个UDP实验,我们希望实现一个回显服务器的效果(这相当于网络编程的"Hello World"),需求如下:

  1. 程序分为两部分,服务器端和客户端
  2. 客户端可以接收键盘输入内容,封装报文向指定服务器发送数据报
  3. 服务器端接收数据报后在显示器上打印格式为[/127.0.0.1, 52523]服务器接收到请求: xxx,并回复给客户端OK
  4. 客户端发送数据报后等待服务器响应内容,然后将响应内容打印在显示器上
  5. 要求服务器可以持续接收客户端请求,客户端可以不停接收用户键盘输入
2.2.2 代码编写

UDP服务器端代码

/**
 * UDP服务器端代码
 */
public class UdpServer {
    private int serverPort = 0; // 服务器端端口
    private DatagramSocket socket = null;

    public UdpServer(int port) throws SocketException {
        this.serverPort = port;
        this.socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器开始启动....");
        // 1. 循环处理客户端请求
        while (true) {
            // 2. 阻塞等待客户端请求
            DatagramPacket request = new DatagramPacket(new byte[4096], 4096);
            socket.receive(request);
            // 3. 获得请求后进行处理
            String responseMsg = process(request);
            // 4. 将响应回传客户端
            DatagramPacket response = new DatagramPacket(responseMsg.getBytes(), responseMsg.getBytes().length, request.getSocketAddress());
            socket.send(response);
        }
    }

    public String process(DatagramPacket request) {
        // 根据请求数据读取构造字符串
        String msg = new String(request.getData(), 0, request.getLength());
        System.out.printf("[%s, %d]服务器接收到请求: %s\n", request.getAddress(), request.getPort(), msg);
        // 服务器端返回OK
        return "OK";
    }

    public static void main(String[] args) throws IOException {
        UdpServer udpServer = new UdpServer(9090);
        udpServer.start();
    }
}

UDP客户端代码

/**
 * UDP客户端代码
 */
public class UdpClient {
    private String serverIP;

    private int serverPort;

    private DatagramSocket socket;

    public UdpClient(String serverIP, int serverPort) throws SocketException {
        this.serverIP = serverIP;
        this.serverPort = serverPort;
        this.socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动....");
        Scanner scanner = new Scanner(System.in);
        // 1. 用户持续输入
        System.out.print("->");
        while (scanner.hasNext()) {
            String input = scanner.next();
            // 2. 将用户输入内容构造成数据报
            DatagramPacket request = new DatagramPacket(input.getBytes(), input.getBytes().length, InetAddress.getByName(serverIP), serverPort);
            // 3. 向服务器端发送数据报
            socket.send(request);
            // 4. 阻塞等待服务器端响应
            DatagramPacket response = new DatagramPacket(new byte[4096], 4096);
            socket.receive(response);
            // 5. 打印响应内容
            String responseMsg = new String(response.getData(), 0, response.getLength());
            System.out.println(responseMsg);
            System.out.print("->");
        }
    }

    public static void main(String[] args) throws IOException {
        UdpClient udpClient = new UdpClient("127.0.0.1", 9090);
        udpClient.start();
    }
}

运行效果

客户端:
【Java网络编程02】套接字编程-LMLPHP

服务器端:
【Java网络编程02】套接字编程-LMLPHP

2.2.3 流程分析

我们以客户端输入"hello"为例分析客户端和服务器端各自的执行流程

  1. 服务器端执行socket.receive(request);进入阻塞状态,等待客户端的请求
  2. 客户端执行while(scanner.hasNext()) {...}阻塞等待用户键盘输入
  3. 客户端用户在键盘敲下"hello",客户端停止阻塞,执行以下代码
String input = scanner.next();
// 2. 将用户输入内容构造成数据报
DatagramPacket request = new DatagramPacket(input.getBytes(), input.getBytes().length, InetAddress.getByName(serverIP), serverPort);
// 3. 向服务器端发送数据报
socket.send(request);
// 4. 阻塞等待服务器端响应
DatagramPacket response = new DatagramPacket(new byte[4096], 4096);
socket.receive(response);

将用户输入内容构造成DatagramPacket对象,然后执行socket.send(request)向服务器发送请求。然后执行socket.receive(response);进入阻塞状态,等待服务器响应
【Java网络编程02】套接字编程-LMLPHP

  1. 服务器端停止阻塞,开始执行以下代码
// 3. 获得请求后进行处理
String responseMsg = process(request);
// 4. 将响应回传客户端
DatagramPacket response = new DatagramPacket(responseMsg.getBytes(), responseMsg.getBytes().length, request.getSocketAddress());
socket.send(response);

服务器获得请求数据报后开始解析,然后构建响应数据报返回给客户端,即调用socket.send(response);,向客户端发送数据报socket.send(response);,之后再次执行while循环执行socket.receive(request);,阻塞等待下一次的客户端请求
【Java网络编程02】套接字编程-LMLPHP

  1. 客户端接收到服务器端响应,停止阻塞,执行以下代码
// 5. 打印响应内容
String responseMsg = new String(response.getData(), 0, response.getLength());
System.out.println(responseMsg);
System.out.print("->");

将响应内容显示在屏幕上后,继续执行while(scanner.hasNext()) {...}进入阻塞等待下一次用户输入,由此进入闭环。

完整流程图:

【Java网络编程02】套接字编程-LMLPHP

01-22 07:04