网络初始2:网络编程--基于UDP和TCP实现回显器

news2025/3/24 13:03:01

基础概念

1.发送端与接受端

在通过网络传输信息时,会有两个进程,接收端和发送端。

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

接收端:数据的接收方进程,即网路通信中的目的主机。

2.Socet套接字

Socket套接字,是由系统提供用于网络通信的技术。基于Socket套接字的网络程序开发就是网络编程。

Socket主要分为流套接字和数据报套接字,还有原始套接字(应用很少,不介绍)

流套接字:使用传输层协议Tcp,Tcp特点:1.有连接。2.面向字节流。3.可靠传输。4.有接受缓冲区,也有发送缓冲区。5.传输信息大小不限。6.全双工。

数据报套接字:使用传输层协议Udp,Udp特点:1.无连接。2.面向数据报。3.不可靠传输。4.有接受缓冲区,无发送缓冲区。5.一次最多传输64KB。6.全双工。

学习网络编程,本质上就是学习传输层提供给应用层的API,通过API来实现客户端和服务器。

UDP socketAPI 的使用

由于Udp是面向数据报的,所以核心的类都是和数据报有关的。

1.DategramSocket

分为两个版本,1.DategramSocket(),创建一个Udp数据报套接字的Socket,绑定到主机上任意一个空闲端口,一般用于客户端。2.DategramSocket(int port) :用于指定绑定的端口, 通过port传入,一般用于服务端。

2.send(DategramPacket p) 和 receive(DategramPacket p)

send:将数据打包为DategramPacket类型后发送出去,不会阻塞等待。

receive:从另一方接受DategramPacket类型的数据,如果还没收到会阻塞等待。

3.DategramPacket

由于UDP面向数据报,所以每次发送和接受都是一个DategramPacket类型的数据报,DategramPacket是UDP发送和接收的基本单位。在Udp中,传入的参数是1.byte[]数组(可以通过调用String.Byte()来转化);2.byte[]数组的长度,也可以通过String.Byte().length来传入;3.端口号,如果是客户端还需要传入服务器的IP地址。这个需要注意二者的不同。

实例:基于UDP实现一个简单的回显器

一.客户端

1.首先创建客户端要连接的服务器的端口号和IP地址,并在构造函数中将它们传入

package netWork;

import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;

public class UDPEchoClient {
private int Serverport = 0;
private String ServerIp = "";
DatagramSocket socket = null;

    public UDPEchoClient(int port, String Ip) throws SocketException {
        Serverport = port;
        ServerIp = Ip;
        this.socket = new DatagramSocket();
    }

}

2.创建start方法,通过Scannner函数读取用户传入的信息。

public void start(){
    System.out.println("客户端启动!");
    Scanner in = new Scanner(System.in);

}

3.创建一个while循环,将用户传入的信息进行打包为DategramPacket类型,然后发送给服务器。由于我们传入的IP号是String类型,所以需要通过InetAddres.getByName()方法来转换一下。

while (true){
        System.out.println("请输入:");
        String request = in.next();
        DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
        socket.send(requestPacket);
    }

同时注意一下异常的抛出,这里由于涉及到了IO的输入与输出,异常抛出是在所难免的。

4.发送完数据后,我们还需要创建一个DategramPacket类型的数据报来接受服务器传来的数据,同时将传来的数据转换为String类型并打印。这里我创建了一个大小为4096的byte[]数组来接收。

public void start() throws IOException {
    System.out.println("客户端启动!");
    Scanner in = new Scanner(System.in);
    while (true){
        System.out.println("请输入:");
        String request = in.next();
        DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
        socket.send(requestPacket);
        DatagramPacket responesPacket = new DatagramPacket(new byte[4096],4096);
        socket.receive(responesPacket);
        String respones = new String(responesPacket.getData(),0,responesPacket.getLength());
        System.out.println(respones);
    }
}

5.到这里差不多就写完了,我们还需要创建主类,调用stat方法就行了。(127.0.0.1为本机IP地址,由于实例是一个机器同时扮演服务器和客户端,所以用本机地址,而9999为随意指定的服务器端口号,只要是空闲的,不是知名端口号即可(>1024))

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

整体代码

package netWork;

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

public class UDPEchoClient {
private int Serverport = 0;
private String ServerIp = "";
DatagramSocket socket = null;

    public UDPEchoClient(int port, String Ip) throws SocketException {
        Serverport = port;
        ServerIp = Ip;
        this.socket = new DatagramSocket();
    }
public void start() throws IOException {
    System.out.println("客户端启动!");
    Scanner in = new Scanner(System.in);
    while (true){
        System.out.println("请输入:");
        String request = in.next();
        DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
        socket.send(requestPacket);
        DatagramPacket responesPacket = new DatagramPacket(new byte[4096],4096);
        socket.receive(responesPacket);
        String respones = new String(responesPacket.getData(),0,responesPacket.getLength());
        System.out.println(respones);
    }
}
public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient(9999,"127.0.0.1");
        udpEchoClient.start();
}
}

二、服务端

1.与客户端类似,先创建DatagramSocket类型变量,再通过构造函数来绑定端口号。

public class UDPEchoServer {
    DatagramSocket socket = null;

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

2.创建start方法,不过与客户端不同的是服务器是先接收,处理后再发送出去,其他的与客户端大同小异。

 public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String spones = process(request);
            DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
            socket.send(sponesPacket);
        }
    }

3.同时为了显示出数据的发送信息,我在最后加了一句打印信息的代码。而process方法则就是返回原字符串,印证了回显器的功能。

public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String spones = process(request);
            DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
            socket.send(sponesPacket);
            System.out.printf("[%s : %d],req: %s,spon: %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,spones);
        }
    }

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

4.最后创建主类,调用方法即可。

package netWork;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPEchoServer {
    DatagramSocket socket = null;

    public UDPEchoServer(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String spones = process(request);
            DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
            socket.send(sponesPacket);
            System.out.printf("[%s : %d],req: %s,spon: %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,spones);
        }
    }

    private String process(String request) {
        return request;
    }
public static void main(String[] args) throws IOException {
        UDPEchoServer udpEchoServer = new UDPEchoServer(9999);
        udpEchoServer.start();
}
}

效果:

TCP socketAPI 的使用

1.ServerSocket 

Socket类,对应到网卡,但是只可以给服务器使用。

2.Socket

也是对应到网卡,既可以给服务器使用,又可以给客户端使用。

在开发中二者我们都要用到。

3.accept()方法

Socket类中一个重要的方法,由于TCP是有连接的,所以我们需要accept来确认连接。如果没有客户端连接过来,accept也会进入阻塞状态。

实例:同样基于TCP实现一个回显器

一、服务端

1.同UDP服务器一样,先绑定端口号。

ServerSocket socket = null;

    public TcpServer(int port) throws IOException {
        socket = new ServerSocket(port);
    }

2.再使用Socket来创建一个变量,用来在建立连接后与客户端的通信,同时为了能与多个客户端同时通信,而且避免较大的线程开销,我们使用线程池来为每个客户端进程创建一个线程。

 public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket = socket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnectin(clientSocket);
                }
            });
        }
    }

3.实现processConnection方法:首先打印连接到的客户机的信息,并提醒其上线了,由于TCP是面向字节流的,所以我创建了InputStream和OutputStream对象来接收和传输数据。这里在try括号里创建,好处是try-catch的结尾自动调用close方法,避免了占用过多的文件描述符。

private void processConnectin(Socket clientSocket) {
        System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner in = new Scanner(inputStream);
                if(!in.hasNext()) {
                    System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                String request = in.next();
                String sponse = process(request);
                
            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }

4.对于要发送信息的处理,我们可以使用OutputStream的方法,但是更好的方法是使用PrintWrite来封装一下outputstream,一方面是好处理换行,另一方面是因为TCP有发送缓存,如果我发送的信息太少的话,它是会先存到缓存中而不去发送,而PrintWrite的flush方法可以强制将缓冲区中的数据立即写入到关联的输出流中。同时不要忘记关闭clientsocket。

 private void processConnectin(Socket clientSocket) {
        System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner in = new Scanner(inputStream);
                if(!in.hasNext()) {
                    System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),
                            clientSocket.getPort());
                    break;
                }
                String request = in.next();
                String sponse = process(request);
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(sponse);
                writer.flush();
                System.out.printf("[%s : %d]req: %s,spon: %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,sponse);
            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }finally {
            try{
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

5.最后创建主类,调用方法即可。

package Tcp;

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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class TcpServer {
    ServerSocket socket = null;

    public TcpServer(int port) throws IOException {
        socket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket = socket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnectin(clientSocket);
                }
            });
        }
    }
    private void processConnectin(Socket clientSocket) {
        System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner in = new Scanner(inputStream);
                if(!in.hasNext()) {
                    System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),
                            clientSocket.getPort());
                    break;
                }
                String request = in.next();
                String sponse = process(request);
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(sponse);
                writer.flush();
                System.out.printf("[%s : %d]req: %s,spon: %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,sponse);
            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }finally {
            try{
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpServer tcpServer = new TcpServer(9998);
        tcpServer.start();
    }
}

二、客户端

1.首先创建Socket和构造函数,值得注意的是TCP是有连接的,所以Socket可以记住ip和port,不需要在类中创建这些变量。

private Socket socket = null;

    public TcpClient(int port,String ip) throws IOException {
        this.socket = new Socket(ip,port);
    }

2.start方法:首先创建InputStream和OutputStream,然后创建用于发送和接收的Scanner(因为TCP是面向字节流的,所以要用Scanner来接收数据),最后用PrintWrite来对发送的数据进行处理即可。

 public void start(){
        System.out.println("客户端启动!"); 
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            Scanner sendFile = new Scanner(System.in);
            Scanner receiveFile = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                System.out.println("请输入:");
                String request = sendFile.next();
                writer.println(request);
                writer.flush();
                String spones = receiveFile.next();
                System.out.println(spones);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

3.创建主类,调用方法。

package Tcp;

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 TcpClient {
private Socket socket = null;

    public TcpClient(int port,String ip) throws IOException {
        this.socket = new Socket(ip,port);
    }
    public void start(){
        System.out.println("客户端启动!");
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            Scanner sendFile = new Scanner(System.in);
            Scanner receiveFile = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                System.out.println("请输入:");
                String request = sendFile.next();
                writer.println(request);
                writer.flush();
                String spones = receiveFile.next();
                System.out.println(spones);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) throws IOException {
        TcpClient tcpClient = new TcpClient(9998,"127.0.0.1");
        tcpClient.start();
    }
}

测试效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2300215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vtkCamera类的Dolly函数作用及相机拉近拉远

录 1. 预备知识 1.1.相机焦点 2. vtkCamera类的Dolly函数作用 3. 附加说明 1. 预备知识 要理解vtkCamera类的Dolly函数作用,就必须先了解vtkCamera类表示的相机的各种属性。  VTK是用vtkCamera类来表示三维渲染场景中的相机。vtkCamera负责把三维场景投影到二维平面,如…

车载音频架构图详解(精简)

目录 上图是车载音频架构图,对这个图我们进行详细的分析 左边第一层 是 app 常用的类有MediaPlayer和MediaRecorder, AudioTrack和AudioRecorder 第二层 是framework提供给应用的多媒体功能的AP

使用神经网络对驾驶数据进行道路类型分类

摘要 道路分类,了解我们是在城市、农村地区还是在高速公路上驾驶,可以提高现代驾驶员辅助系统的性能,并有助于了解驾驶习惯。本研究的重点是仅使用车速数据来普遍解决这个问题。已经开发了一种数据记录方法,用于为 On-board Diagn…

S4D480 S4HANA 基于PDF的表单打印

2022年元旦的笔记草稿 SAP的表单打印从最早的SAPScripts 到后来的SMARTFORM,步入S4时代后由于Fiori的逐渐普及,更适应Web的Adobe Form成了SAP主流output文件格式。 目录 一、 基于PDF表单打印系统架构Interface 接口Form 表单ContextLayout 二、表单接…

qt QOpenGLTexture详解

1. 概述 QOpenGLTexture 是 Qt5 提供的一个类,用于表示和管理 OpenGL 纹理。它封装了 OpenGL 纹理的创建、分配存储、绑定和设置像素数据等操作,简化了 OpenGL 纹理的使用。 2. 重要函数 构造函数: QOpenGLTexture(const QImage &image,…

Deepseek-R1推理模型API接入调用指南 ChatGPT Web Midjourney Proxy 开源项目接入Deepseek教程

DeepSeek-R1和OpenAI o1模型都属于推理任务模型,两个模型各有优点:DeepSeek-R1 在后训练阶段大规模使用了强化学习技术,在仅有极少标注数据的情况下,极大提升了模型推理能力。在数学、代码、自然语言推理等任务上,性能…

蓝耘智算携手DeepSeek,共创AI未来

🌟 各位看官号,我是egoist2023! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习如何通过蓝耘智算使用DeepSeek R1模型 👍 如果觉得这篇文章有帮助,欢迎您一键三连&a…

【网络编程】之数据链路层

【网络编程】之数据链路层 数据链路层基本介绍基本功能常见协议 以太网什么是以太网以太网协议帧格式数据链路层的以太网帧报文如何封装/解封装以及分用以太网通信原理传统的以太网与集线器现代以太网与交换机碰撞域的概念 Mac地址基本概念为什么要使用Mac地址而不是使用IP地址…

EasyExcel 复杂填充

EasyExcel ​Excel表格中用{}或者{.} 来表示包裹要填充的变量,如果单元格文本中本来就有{、}左右大括号,需要在括号前面使用斜杠转义\{ 、\}。 ​代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。 …

通过VSCode直接连接使用 GPT的编程助手

GPT的编程助手在VSC上可以直接使用 选择相应的版本都可以正常使用。每个月可以使用40条,超过限制要付费。 如下图对应的4o和claude3.5等模型都可以使用。VSC直接连接即可。 配置步骤如下: 安装VSCODE 直接,官网下载就行 https://code.vis…

【算法与数据结构】并查集详解+题目

目录 一,什么是并查集 二,并查集的结构 三,并查集的代码实现 1,并查集的大致结构和初始化 2,find操作 3,Union操作 4,优化 小结: 四,并查集的应用场景 省份…

Java 集合数据处理技巧:使用 Stream API 实现多种操作

​ 在 Java 开发中,对集合数据进行处理是非常常见的需求,例如去重、排序、分组、求和等。Java 8 引入的 Stream API 为我们提供了一种简洁、高效的方式来处理集合数据。本文将详细介绍如何使用 Stream API 实现多种集合数据处理操作,并给出相…

OSI 参考模型和 TCP/IP 参考模型

数据通信是很复杂的,很难在一个协议中完成所有功能。因此在制定协议时经常采用的思路是将复杂的数据通信功能由若干协议分别完成,然后将这些协议按照一定的方式组织起来。最典型的是采用分层的方式来组织协议,每一层都有一套清晰明确的功能和…

SD NAND 的 SDIO在STM32上的应用详解(上篇)

目录 上篇: 一.SDIO简介 二.SD卡简介/内部结构 1.SD卡/SD NAND引脚 2.SD卡寄存器 3.FLASH存储器 三.SDIO总线拓扑 中篇: 四.SDIO功能框图(重点) 1.SDIO适配器 2.控制单元 3.命令通道(重点) 4.数…

基于图像处理的裂缝检测与特征提取

一、引言 裂缝检测是基础设施监测中至关重要的一项任务,尤其是在土木工程和建筑工程领域。随着自动化技术的发展,传统的人工巡检方法逐渐被基于图像分析的自动化检测系统所取代。通过计算机视觉和图像处理技术,能够高效、精确地提取裂缝的几何特征,如长度、宽度、方向、面…

执行pnpm run dev报错:node:events:491 throw er; // Unhandled ‘error‘ event的解决方案

vite搭建的vue项目,使用pnpm包管理工具,执行pnpm run dev,报如下错误: 报错原因: pnpm依赖安装不完整,缺少esbuild.exe文件,导致无法执行启动命令。 解决方案: 根据错误提示中提到…

「软件设计模式」建造者模式(Builder)

深入解析建造者模式:用C打造灵活对象构建流水线 引言:当对象构建遇上排列组合 在开发复杂业务系统时,你是否经常面对这样的类:它有20个成员变量,其中5个是必填项,15个是可选项。当用户需要创建豪华套餐A&…

uniapp 安卓10+ 选择并上传文件

plus.io.chooseFile({title: 选择文件,filetypes: [mp3], // 允许的文件类型multiple: false, // 是否允许多选}, (res) > {console.log(虚拟路径666:, res);var arr[{name: files,uri: res.files[0],}]let obj {"tableName": "mingmen_daily_mi…

【第1章:深度学习概览——1.6 深度学习框架简介与选择建议】

嘿,各位老铁们,今天咱们来一场深度学习框架的深度探索之旅。在这个充满无限可能的深度学习时代,深度学习框架就像是连接理论与实践的桥梁,帮助我们从算法设计走向实际应用。随着技术的飞速发展,深度学习框架的选择变得越来越多样化,每一种框架都有其独特的优势和适用场景…

网页制作02-html,css,javascript初认识のhtml的文字与段落标记

用一首李白的将进酒,对文字与段落标记进行一个简单的介绍演示: 目录 一、标题字 1、标题字标记h 2、标题字对其属性align 二、文本基本标记 1、字体属性face 2、字号属性size 3、颜色属性 Color 三、文本格式化标记 1、粗体标记 b ,strong 2、…