[JAVAEE] 网络编程

目录

[一. 什么是socket套接字](#一. 什么是socket套接字)

[二. socket套接字](#二. socket套接字)

[2.1 socket套接字根据传输层协议分类](#2.1 socket套接字根据传输层协议分类)

[2.2 TCP流套接字 UDP数据报套接字主要特点](#2.2 TCP流套接字 UDP数据报套接字主要特点)

[三. UDP数据报套接字编程](#三. UDP数据报套接字编程)

[3.1 DatagramSocket 是UDP socket, 用于发送和接受数据报](#3.1 DatagramSocket 是UDP socket, 用于发送和接受数据报)

[3.2 DatagramPacket 是UDP socket 发送和接收的数据报](#3.2 DatagramPacket 是UDP socket 发送和接收的数据报)

[3.3 练习案例](#3.3 练习案例)

回显服务器

回显客户端

[四. TCP字节流套接字编程](#四. TCP字节流套接字编程)

[4.1 ServerSocket](#4.1 ServerSocket)

[4.2 Socket](#4.2 Socket)

[4.3 练习案例](#4.3 练习案例)

回显服务器

回显客户端

注意:


一. 什么是socket套接字

socket套接字是由系统提供用于网络通信的技术, 是基于TCP/IP协议的网络通信的基本操作单元.

基于Socket套接字的网络程序开发就是网络编程.

简单来说, socket就是网络编程套接字, 用来对网卡进行操作.

读网卡就是从网上上收数据, 写网卡就是让网卡发数据.


二. socket套接字

2.1 socket套接字根据传输层协议分类

socket套接字根据传输层协议分类:

  1. 字节流套接字 使用传输层TCP协议 Transmission Control Protocol(传输控制协议)

  2. 数据报套接字 使用传输层UDP协议 User Datagram Protocol(⽤⼾数据报协议)

2.2 TCP流套接字 UDP数据报套接字主要特点

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

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

  1. 有连接 vs 无连接

TCP, 保存通信双方信息 (例如: A与B通信, A与B建立连接)

UDP, 不保存通信双方信息, 但是可以自己实现代码来保存双端信息.

  1. 可靠传输 vs 不可靠传输

TCP, 尽可能提高传输成功的概率, 如果出现丢包, 可以感知到.

UDP, 只要把数据发送了, 就不管了.

  1. 面向字节流 vs 面向数据报

TCP, 以字节为单位, 读写数据.

UDP, 以数据报为单位, 读写数据.

  1. 全双工 vs 半双工

一个通信链路中, 支持双向通信(能读也能写) => TCP/UDP

~, 支持单向通信(要么读, 要么写)

三. UDP数据报套接字编程

3.1 DatagramSocket 是UDP socket, 用于发送和接受数据报

构造方法

成员方法

3.2 DatagramPacket 是UDP socket 发送和接收的数据报

构造方法

成员方法

3.3 练习案例

回显服务器

java 复制代码
package UDPNet;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class UDPEchoServer {
    DatagramSocket socket = null;
    public UDPEchoServer(int serverPort) throws IOException {
        socket = new DatagramSocket(serverPort); // 指定服务器端口号
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            executorService.submit(() -> {
                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 responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length,
                            requestPacket.getSocketAddress());
                    socket.send(responsePacket);
                    // 打印日志
                    System.out.printf("[%s:%d] req:%s res:%s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response);
                }
            });
        }
    }
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException{
        UDPEchoServer udpEchoServer = new UDPEchoServer(3306);
        udpEchoServer.start();
    }
}

回显客户端

java 复制代码
package UDPNet;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UDPEchoClient {
    DatagramSocket socket = null;
    String serverIP;
    int serverPort;
    UDPEchoClient(String serverIP, int serverPort) throws IOException {
        this.serverIP = serverIP;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 接收用户输入
            String request = scanner.nextLine();
            // 2. 将请求发送到客户端
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), 0, 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());
            System.out.println("服务器的响应为: " + response);
        }
    }

    public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1", 3306);
        udpEchoClient.start();
    }
}

四. TCP字节流套接字编程

4.1 ServerSocket

用于创建TCP服务端socket.

构造方法

成员方法

4.2 Socket

用于创建TCP客户端socket, 或者是服务端与客户端建立连接(accept)后返回的服务端socket.

构造方法

成员方法

4.3 练习案例

回显服务器

java 复制代码
package TCPNet;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TCPEchoServer {
    // ServerSocket 服务器使用
    // socket 服务器和客户端都使用
    private ServerSocket serverSocket = new ServerSocket();
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port); // 给服务器分配端口号
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService executors = Executors.newCachedThreadPool();
        while (true) { // 服务器可能与多个客户端建立连接
            Socket socket = serverSocket.accept(); // 服务器与客户端建立连接, 返回的是服务端socket
            // socket.getInetAddress 返回的是与服务器连接的客户端的IP地址.
            // socket.getPort 返回的是与服务器建立连接的客户端的Port端口号
            System.out.printf("[%s:%d] 客户端上线!\n", socket.getInetAddress(), socket.getPort());
            executors.submit(() -> {
                try {
                    processConnection(socket); // 处理一次连接
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
    private void processConnection(Socket socket) throws IOException {
        try (socket; InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter writerNet = new PrintWriter(outputStream);
            while (true) { // 一次连接中可能会有多次请求
                if (!scannerNet.hasNextLine()) { // 从网卡中读不到数据了
                    System.out.printf("[%s:%d] 客户端下线!\n", socket.getInetAddress(), socket.getPort());
                    break;
                }
                // 1. 接收请求并解析
                String request = scannerNet.nextLine();
                // 2. 计算响应
                String response = process(request);
                // 3. 返回响应
                writerNet.println(response); // 将response放在内存缓冲区中, 并没有让网卡发送数据到客户端
                // 为什么要放在内存缓冲区中呢?
                // 因为TCP是面向字节流的, 数据是零散的, 放在缓冲区中等数据达到一定数量再发送.
                // 平衡了IO速度, 减少了发送次数, 提高了程序效率
                writerNet.flush(); // 刷新缓冲区, 让网卡发送数据到客户端
                // 打印日志
                System.out.printf("[%s:%d] req:%s res:%s\n", socket.getInetAddress(), socket.getPort(), request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 一个客户端断开连接, 关闭客户端
        // 没有使用finally, 而是使用try with
    }
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException{
        TCPEchoServer tcpEchoServer = new TCPEchoServer(9999);
        tcpEchoServer.start();
    }
}

回显客户端

java 复制代码
package TCPNet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPEchoClient {
    Socket socket = null;
    public TCPEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }
    public void start() throws IOException{
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter writerNet = new PrintWriter(outputStream);
            while (true) { // 一次连接中多次请求
                // 1. 用户输入请求
                String request = scanner.nextLine();
                // 2. 将请求发送到服务器
                writerNet.println(request);
                writerNet.flush();
                // 3. 接收响应
                String response = scannerNet.nextLine();
                // 打印响应
                System.out.println("服务器的响应为: " + response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException{
        TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1", 9999);
        tcpEchoClient.start();
    }
}

注意:

(1) 问题

如果只是单线程, 则服务器无法连接多个客户端, 这是因为服务端中的scannerNet.hasNextLine()阻塞, 导致不能进入外层循环, 服务器无法与其他客户端建立连接(accept).

(2) 解决

引入多线程和线程池, 把processConnection操作作为一个任务提交到线程池中. 就意味着一个客户端的连接相当于一个任务, 任务之间互不冲突.

相关推荐
陶然同学4 分钟前
RabbitMQ轻松构建高效可靠的消息队列系统
java·网络·分布式·rabbitmq·mq
蝌蚪HTTP12 分钟前
干货分享之Python爬虫与代理
爬虫·python·网络协议·tcp/ip·ip
帝恩思科技21 分钟前
网络游戏安全现状及相关应对方案
运维·服务器·网络·安全·web安全
可怜的Tom被玩弄于股掌之中27 分钟前
Bugku CTF_Web——字符?正则?
网络·安全·web安全·网络安全
考试宝1 小时前
新能源汽车关键技术技能大赛理论知识竞赛题库
大数据·网络·职场和发展·汽车·学习方法·业界资讯
催催121 小时前
领夹麦克风哪个品牌好,手机领夹麦克风哪个牌子好,选购推荐
网络·人工智能·经验分享·其他·5g·智能手机
hgdlip1 小时前
两部手机的IP地址:是否会相同?全面探讨
网络协议·tcp/ip·智能手机
李小白661 小时前
二叉树的练习题(中)
java·数据结构·算法
骑鱼过海的猫1231 小时前
【redis】redis
java·数据库·redis
漫天转悠1 小时前
Java21和Java8性能优化详细对比
java