网络编程 (UDP 和 TCP 介绍和代码实现) [Java EE]

网络编程基础

网络资源

// 所谓的网络资源, 其实就是在网络中可以获取的各种数据资源, 而所以得网络资源,都是通过网络编程来实现数据传输的

网络编程

// 网络编程, 指网络上的主机, 通过不同的进程, 以编程的方式实现网络通信(网络数据传输)

// 网络编程 就是写一个应用程序, 让这个程序可以使用网络通信, 这里就需要调用传输层提供的api

1. 基本概念

1.1 发送端和接收端

// 在一次网络数据传输时:

1.1.1 发送端: 数据的发送方进程, 称为发送端. 发送端主机即网络通信中的源主机

1.1.2 接收端: 数据的接收方进程, 称为接收端. 接收端主机即网络通信中的目的主机

1.1.3 收发端: 发送端和接收端两端, 也简称为收发端

// 发送端和接收端只是相对的, 只是一次网络数据传输产生数据流向后的概念

1.2 请求和响应

// 一般来说, 获取一个网络资源, 设计到两次网络数据传输:

1.2.1 第一次: 请求数据的发送

1.2.2 第二次: 响应数据的发送

1.3 客户端和服务端

1.3.1 服务端: 在常见的网络数据传输场景下, 把提供服务的一方进程, 称为服务端, 可以提供对外服务

1.3.2 获取服务的一方进程, 称为客户端

// 对于服务来说, 一般提供:

// 客户端获取服务资源

// 客户端保存资源在服务端

1.4 常见的客户端

// 最常见的场景, 客户端是指给用户使用的程序, 服务端是提供用户服务的程序:

1.4.1 客户端先发送请求到服务端

1.4.2 服务端根据请求数据, 执行相应的业务处理

1.4.3 服务端返回响应: 发送业务处理结果

1.4.4 客户端根据响应数据, 展示处理结果

UDP 数据报套接字

API 介绍 (socket api)

// 两个核心类

1. DatagramSocket

// 是一个 Socket 对象

// 操作系统, 通过使用文件这样的概念, 来管理一些软硬件资源

// Java 中的 socket 对象, 就对应着系统里的 socket 文件(最终还是要落到网卡)

// 要进行网络通信, 必须得先有 socket 对象

// DatagramSocket 构造方法:

|--------------------------|--------------------------------------------------|
| 方法签名 | 方法说明 |
| DatagramSocket() | 创建一个 UDP 数据报套接字的 Socket, 绑定到本机任意一个随机端口 (一般用于客户端) |
| DatagramSocket(int port) | 创建出一个 UDP 数据报套接字的 Socket, 绑定到本机指定的端口 (一般用于服务端) |

// DatagramSocket 方法:

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

// 客户端使用哪个端口, 系统自动分配

// 服务器使用哪个端口, 手动指定的

2. DatagramPacket

// 表示了一个 UDP 数据报

// 代表了系统中设定的 UDP 数据报的二进制结构

代码实现 UDP 客户端服务器

1. UDP 的 回显服务器

// 回显服务器: 请求和响应是一样的

// 配合回显客户端使用

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);
            // 3. 把响应写会客户端
            DatagramPacket datagramPacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length, requestPacket.getSocketAddress());
            socket.send(datagramPacket);
            System.out.printf("[%s:%d] req: %s, resp: %s \n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    public String process (String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

2. UDP 的 回显客户端

java 复制代码
public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    // 服务器的 IP 和 服务器的端口
    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        // 这个 new 操作, 就不再指定端口了, 让系统自动分配一个空闲端口
        socket = new DatagramSocket();
    }
    // 让这个客户端反复的从控制台读取用户输入的内容, 把这个内容构造成 UDP 请求,
    // 发给服务器, 再读取服务器返回的 UDP 响应
    // 最终再显示在客户端的屏幕上
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客户端启动!");
        while (true) {
            // 1. 从控制台读取用户输入的内容
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 构造请求对象, 并发给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);
            // 3. 读取服务器的响应, 并解析出响应内容
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive((responsePacket));
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            // 4. 显示到屏幕上
            System.out.println(response);
        }
    }
    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

3. 翻译服务器

// 翻译服务器的请求是一些英文单词, 响应则是对应的中文翻译

// 因为我们的翻译服务器和之前的回显服务器很多逻辑都很相似, 所以直接继承 (复用)

java 复制代码
public class UdpDictServer extends UdpEchoServer{
    private Map<String, String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);

        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("pig","小猪");
        dict.put("fuck","卧槽");
    }
    // 是要复用之前的代码, 但是又要做出一些调整
    public String process(String request) {
        // 把请求对应单词的翻译给返回回去
        return dict.getOrDefault(request, "该词没有查询到");
    }

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

TCP 数据报套接字

// TCP 分量要比 UDP 更重, 用更多协议

// 字节流, 一个字节一个字节进行传输的, 一个 TCP 数据报, 就是一个 字节数组 byte[]

API 介绍

1. ServerSocket

// 给服务请求使用的 socket

2. Socket

// 既会给服务器使用, 也会给客户端使用

代码实现 TCP 版本的客户端服务器

1. TCP 回显服务器
java 复制代码
public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    private ExecutorService service = Executors.newCachedThreadPool();

    // 这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            Socket clientSocket = serverSocket.accept();
              service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }
    // 通过这个方法来处理一个连接的逻辑
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        // 接下来就可以读取请求, 根据请求计算响应,返回响应三步走了
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream =clientSocket.getOutputStream()) {
            while (true) {
                // 1. 读取请求并解析, 为了方便, 直接使用 Scanner
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 读取完毕, 客户端下线
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                // 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割 (比如换行这种)
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回个客户端
                PrintWriter writer = new PrintWriter(outputStream);
                //  使用 PrintWriter 的 println 方法, 把响应返回给客户端
                //  用 println, 而不用 print, 就是为了在结尾加一个 \n, 方便客户端读取响应, 使用 scanner.next 读取
                writer.println(response);
                //  这里还需要加入一个 "刷新缓冲区" 操作
                writer.flush();
                //  日志, 打印当前的请求详情
                System.out.printf("[%s:%d] req: %s, resp: %s\n ", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            clientSocket.close();
        }
    }

    public String process(String request) {
        return request;
    }

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

2. TCP 回显客户端

java 复制代码
public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        socket  = new Socket(serverIp, serverPort);

    }

    public void start() {
        System.out.printf("客户端启动!\n");
        Scanner scannerConsole = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            while (true) {
                // 1. 从控制台输入字符串
                System.out.printf("->");
                String request = scannerConsole.next();
                // 2. 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();
                // 3. 从服务器读取响应
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();
                // 4. 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

UDP 和 TCP 特点对比

// UDP : 无连接, 不可靠传输, 面向数据报, 全双工

// TCP : 有连接, 可靠传输, 面向字节流, 全双工

相关推荐
哞哞不熬夜1 分钟前
JavaEE--计算机是如何工作的
java·开发语言·学习·java-ee
火烧屁屁啦2 分钟前
【JavaEE进阶】图书管理系统 - 柒
java·java-ee
星星岛屿2 小时前
网络原理----TCP/IP(3)
服务器·网络·tcp/ip
予安灵7 小时前
《白帽子讲 Web 安全:点击劫持》
前端·网络·安全·web安全·网络攻击模型·安全威胁分析·点击劫持
屁股割了还要学8 小时前
【计算机网络入门】初学计算机网络(七)
网络·学习·计算机网络·考研·青少年编程
cdprinter9 小时前
信刻光盘安全隔离与信息交换系统让“数据摆渡”安全高效
网络·安全·自动化
萧瑟其中~10 小时前
计算机网络:Socket网络编程 Udp与Tcp协议 第一弹
服务器·网络·计算机网络
Lojarro10 小时前
JavaEE基础之- 过滤器和监听器Filter and Listener
java·java-ee
土豆炒马铃薯。10 小时前
【Java 基础(人话版)】Java SE vs Java EE
java·开发语言·spring boot·java-ee·java基础·java-se