【JavaEE】网络原理:UDP数据报套接字编程和TCP流套接字编程

news/2024/5/18 12:36:12 标签: 网络, udp, tcp/ip, java-ee, java, 开发语言, 服务器

目录

1.UDP数据报套接字编程

1.1 DatagramSocket

1.2 DatagramPacket

1.3 InetSocketAddress

1.4 基于UDP实现回响服务器

2.TCP流套接字编程

2.1 ServerSocket

2.2 Socket

2.3 基于TCP实现回响服务器


1.UDP数据报套接字编程

API 介绍

1.1 DatagramSocket

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket 的构造方法:

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

DatagramSocket 的方法:

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

1.2 DatagramPacket

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

DatagramPacket 构造方法:

方法签名方法说明
DatagramPacket(byte[] buf, int length)构造⼀个DatagramPacket以⽤来接收数据报,接收的 数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造⼀个DatagramPacket以用来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定长度(第⼆个参数length)。address指定目的主机的IP 和端口号

DatagramPacket 方法:

方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

构造UDP发送的数据报时,需要传入SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

1.3 InetSocketAddress

InetSocketAddress ( SocketAddress 的子类 )构造方法:

方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建⼀个Socket地址,包含IP地址和端⼝号

1.4 基于UDP实现回响服务器

服务器端代码:

java">import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);   //port:端口号
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            //每次循环,就是一个请求-响应过程
            //1.读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            //基于字节数组构造String
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            //2.根据请求计算响应(对于回显服务器来说,这一步啥都不做)
            String response = process(request);
            //把响应返回客户端
            // 构造一个 DatagramPacke 作为相应对象
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    0, response.getBytes().length,
                    requestPacket.getSocketAddress()); //得到 INetAddress 对象, 这个对象包含了 ip 和端口号
            socket.send(responsePacket);

            //打印日志
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateTime = sdf.format(calendar.getTime());
            System.out.printf("[%s:%d time:%s] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(),dateTime, request, response);
        }
    }

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

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

客户端代码

java">import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    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 {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("->"); //提示用户接下来要输入内容
            //1.从控制台读取要发送的数据
            if (!scanner.hasNext()) {
                break;
            }
            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);
            //4.把响应显示到控制台上
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

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

运行效果:

 编写⼀个英译汉的服务器. 只需要重写 process

java">import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;

//词典服务器
public class UdpDictServer extends UdpEchoServer {
    private HashMap<String, String> hashMap = new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
        super(port);
        hashMap.put("cat", "小猫");
        hashMap.put("dog", "小狗");
        hashMap.put("chicken", "坤坤");
        hashMap.put("aaaaaaaaaa", "测试");
    }

    //start 方法完全从父类中继承下来即可
    //process 方法要进行重写,加入另外的业务逻辑,进行翻译
    @Override
    public String process(String request) {
        return hashMap.getOrDefault(request, "您查的单词不存在!");
    }

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

 运行效果

2.TCP流套接字编程

API 介绍

2.1 ServerSocket

ServerSocket 是创建TCP服务端Socket的API。

ServerSocket 构造方法:

方法签名方法说明
ServerSocket(int port)创建⼀个服务端流套接字Socket,并绑定到指定端⼝

ServerSocket 方法

法签名法说明
Socket accept()开始监听指定端⼝(创建时绑定的端⼝),有客户端 连接后,返回⼀个服务端Socket对象,并基于该 Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

2.2 Socket

Socket 是客户端 Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服 务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket 构造方法:

方法签名方法说明
Socket(String host, int port)创建⼀个客户端流套接字Socket,并与对应IP的主机 上,对应端口的进程建立连接

Socket 方法:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

2.3 基于TCP实现回响服务器

服务器端代码:

java">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 {
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        // 创建线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true) {
            // 通过accept方法"接听电话",然后才能进行通信
            Socket clientSocket = serverSocket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    //通过这个方法来处理一次连接, 连接建立过程中涉及到多次请求的响应交互
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //循环的读取客户端的请求并返回响应
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            while(true) {
                if(!scanner.hasNext()) {
                    //读取完毕,客户端断开连接,就会产生读取完毕
                    System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //1.读取请求并解析 next要读到空白符才会结束
                String request = scanner.next();
                //2.根据请求计算响应
                String response =  process(request);
                //3.把响应返回给客户端
                //通过这种方法可以写回, 但是这种方法不方便给返回的响应中添加\n
                    //outputStream.write(response.getBytes(),0,response.getBytes().length);
                //也可以给 outputStream 套上一层,完成更方便的写入
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();

                System.out.printf("[%s:%d] req: %s, resp: %s \n",clientSocket.getInetAddress(),clientSocket.getPort(),
                        request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

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

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

注意: 如果只是单个线程, 无法同时响应多个客户端. 此处给每个客户端都分配⼀个线程. 为了避免频繁创建销毁线程, 引入线程池

 

客户端代码:

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

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        //此处可以把这里的ip和port直接传给socket对象
        //由于tcp是有连接的,因此 socket 里面就会保存这两信息
        socket = new Socket(serverIp,serverPort);
    }

    public void start() {
        System.out.println("客户端启动");

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerConsole = new Scanner(System.in);
            Scanner scannerNetWork = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true) {
                //这里的流程和 UDP 的客户端类似
                //1.从控制台读取输入的字符串
                System.out.print("-> ");
                if(!scannerConsole.hasNext()) {
                    break;
                }
                String request = scannerConsole.next();
                //2.把请求发给服务器, 这里需要使用println来发送, 为了让发送的请求末尾带有 \n
                writer.println(request);
                // 通过flush刷新缓冲区
                writer.flush();
                //3.从服务器读取响应
                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();
    }
}

 使用PrintWrite记得手动刷新缓冲区:

 运行效果


http://www.niftyadmin.cn/n/5349892.html

相关文章

《Python 简易速速上手小册》第8章:Python 网络编程与 Web 开发(基于最新版 Python3.12 编写)

注意&#xff1a;本《Python 简易速速上手小册》 核心目的在于让零基础新手「快速构建 Python 知识体系」 文章目录 <mark >注意&#xff1a;本《Python 简易速速上手小册》<mark >核心目的在于让零基础新手「快速构建 Python 知识体系」 8.1 Python 中的网络通信…

【深度学习】【AutoDL】【SSH】通过VSCode和SSH使用AutoDL服务器训练模型

身边没有显卡资源或不足以训练模型时&#xff0c;可以租赁服务器的显卡。 1、注册AutoDL并配置环境 首先打开AutoDL官网&#xff0c;注册账号并租赁自己期望的显卡资源 点击“租赁”之后&#xff0c;我们要继续选择基础环境。此处&#xff0c;我们让其自动配置好基础的pytor…

GPT-SoVITS 本地搭建踩坑

GPT-SoVITS 本地搭建踩坑 前言搭建下载解压VSCode打开安装依赖包修改内容1.重新安装版本2.修改文件内容 运行总结 前言 传言GPT-SoVITS作为当前与BertVits2.3并列的TTS大模型&#xff0c;于是本地搭了一个&#xff0c;简单说一下坑。 搭建 下载 到GitHub点击此处下载 http…

QGIS编译(跨平台编译)之二十六:giflib编译(Windows、Linux、MacOS环境下编译)

文章目录 1、giflib介绍2、giflib下载3、Windows下编译4、Linux下编译5、MacOS下编译1、giflib介绍 giflib(又称为Libgif)是一个开源的C语言库,用于处理GIF图像格式。它提供了一组函数和工具,使得开发者可以读取、写入和操作GIF图像文件。 GIFlib支持GIF87a和GIF89a两种版…

西瓜书学习笔记——原型聚类(公式推导+举例应用)

文章目录 k均值算法算法介绍实验分析 学习向量量化&#xff08;LVQ&#xff09;算法介绍实验分析 高斯混合聚类算法介绍实验分析 总结 k均值算法 算法介绍 给定样本集 D { x 1 , x 2 , . . . , x m } D\{x_1,x_2,...,x_m\} D{x1​,x2​,...,xm​}&#xff0c;k均值算法针对聚…

无际线复选框

效果演示 实现了一个网格布局&#xff0c;其中每个网格是一个复选框&#xff0c;可以选择是否显示。每个复选框都有一个漂浮的天花板&#xff0c;表示它是一个房间的天花板。每个房间的天花板都有一个不同的形状和颜色&#xff0c;分别对应不同的房间。整个页面的背景是一个由两…

线性代数----------学习记录

线性代数发展历程 &#xff08;1&#xff09;线性方程组&#xff1a;例如二元一次方程组&#xff1b; &#xff08;2&#xff09;行列式&#xff1a;determinant,克莱默&#xff0c;莱布尼兹&#xff1b; &#xff08;3&#xff09;矩阵&#xff1a;方程个数与未知数的个数可…

翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式一

随着 OpenAI 在多模态方面的最新进展&#xff0c;想象一下将这种能力与视觉理解相结合。 现在&#xff0c;您可以在 Streamlit 应用程序中使用 GPT-4 和 Vision&#xff0c;以&#xff1a; 从草图和静态图像构建 Streamlit 应用程序。帮助你优化应用的用户体验&#xff0c;包…