2023.9.8 基于传输层协议 UDP 和 TCP 编写网络通信程序
目录
UDP
基于 UDP 编写网络通信程序
服务器代码
客户端代码
TCP
基于 TCP 编写网络通信程序
服务器代码
客户端代码
IDEA 打开 支持多客户端模式
UDP
特点:
- 无连接性:发送端和接收端不需要建立连接也可相互通信,且每个 UDP 数据包都是独立的,它们之间没有顺序关系(无序性)
- 不可靠性:不提供重传机制和数据包的确认,如果数据包在传输过程中丢失或损坏,接收端无法得知丢失的数据,并且不会进行重传,从而体现出传输时的不可靠性
- 全双工通信:即发送端和接收端可以同时进行数据的发送和接收,不需要等待对方的响应
- 面向数据报:UDP 将每个数据作为一个独立的数据单元进行处理,每个数据报都有自己的目标地址和源地址,没有关联性,也没有连接的概念
- 适用于实时应用:由于 UDP 的无连接性和较低的延迟,其适用于如音频、视频流媒体、实时游戏等对实时性要求较高的应用场景,对于上述应用,即使有部分数据丢失也可接收,因为其实时性比数据完整性更重要
- 简单高效性:UDP 的头部较小,没有 TCP 那么复杂,从而使得 UDP 在数据传输的开销上相对较小,传输效率更高
- 没有拥塞控制:UDP 不会主动根据网络的拥塞情况来调整其发送速率,可能导致在网络拥堵的情况下,UDP 数据包丢失更多
- 支持广播和多播:广播和多播都是 UDP 协议提供的数据传输方式,可在局域网或广域网中使用,它们都具有一对多的传输能力,能够将数据发送给多个目标主机。广播发送给整个网络,而多播发送给特定的多播组,只有加入该组的主机能够接收到数据
基于 UDP 编写网络通信程序
- 一个服务器包括三部分:收到请求、根据请求计算逻辑(业务逻辑)、返回响应
- 这里我们编写一个简单逻辑的 服务器程序,即无业务逻辑,请求是啥就返回啥
服务器代码
//UDP 版本的服务器 public class UdpEchoServer { // 网络编程,本质上是要操作网卡 // 因此我们可以通过 操作系统内核所提供的一种叫做 “socket” 的文件来抽象表示和操作网卡private DatagramSocket socket = null;// 服务器一定要关联上一个具体的端口号 // 所以在创建 socket 对象的同时,要让他绑上一个具体的端口号 // socket = new DatagramSocket() 这是创建一个 socket 对象,如果括号中不主动传入端口号,系统便会自动分配端口号 // 服务器是网络传输中,被动的一方,如果是操作系统随机分配的端口,此时客户都安就知道这个端口是啥了,也就无法通信了public UdpEchoServer(int port) throws SocketException { // 这里我们主动传一个端口号socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动!"); // 服务器需要服务多个客户端while (true) { // 只要有客户端过来,就可以为其提供服务 // 1.读取客户端发来的请求是什么 // 这里相当于构造一个空白的 DatagramPacket 对象DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); // 这里的 receive 方法的参数是一个输出性参数,我们创建出来的空对象,在这里被其填充socket.receive(requestPacket); // 此时这个 DatagramPacket 是一个特殊的对象,不方便直接进行处理,可以把这里包含的数据拿出来,构造成一个字符串String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); // 2.根据请求计算响应,因为我们仅构造一个简单的服务器例子,所以此处响应和请求相同,也就是不做处理String response = process(request); // 3. 把响应写回到客户端,send 的参数也是 DatagramPacket,需要把这个 Packet 对象构造好DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress()); // 注意这里的 response.getBytes().length 计算的是字节的个数 // response.length 计算的是字符的个数,所以不能填入 response.length ,因为一个汉字由多个字节组成 // 这里将响应数据进行发送socket.send(responsePacket); // 4.打印一下日志,这次请求响应的处理中间结果System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);}}// 这个方法 表示 根据请求计算响应(处理业务逻辑)private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();} }客户端代码
//UDP 版本的客户端 public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;// 通常情况下,端口号用来标识和区分一个进程,由于我们现在是同一个主机上编写服务器和客户端,所以不允许一个端口同时被多个进程使用 // 但是一个进程可以绑定多个端口,即进程只需创建多个 socket 对象 就可以关联不同的端口 // 相比服务器我们需要指定端口,因为是为了方便客户端找到服务器程序 // 而客户端 我们无需指定端口号,只需让系统自动分配空闲端口号即可,自己指定可能因为该端口号已经被其他进程所占用,而导致无法使用该端口号 // 一次通信有 两个ip 两个端口号 分别是 客户端ip 服务器ip 客户端端口号 服务器端口号 // 因为客户端和服务器是在同一个主机上,所以 客户端ip 和 服务器ip 都是 127.0.0.1 // 但我们需要将服务器的 ip 和 端口号 告诉客户端,才能顺利的将消息发送给服务器public UdpEchoClient (String serverIp, int serverPort) throws SocketException {socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;}public void start() throws IOException{System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);while (true) { // 1.从控制台读取要发送的数据System.out.println(">");String request = scanner.next();if(request.equals("exit")) {System.out.println("goodbye");break;} // 2.构造 UDP 请求并 发送 // 构造 packet 的时候 需要把 serverIp 和 port 都传入过来 但是此处 IP 地址需要填写的是一个 32位的整数形式 // // 因为 这里传入的 IP 地址是点分十进制形式字符串所表示的,但计算机内部是以 一个 32 位的整数来表示 IP 地址 // 所以 需要使用 InetAddress.getByName 来将 IP 地址转换为 32 位整数DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);socket.send(requestPacket); // 3.读取服务器的 UDP 响应 并解析DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(),0,requestPacket.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();} }交互过程:
运行效果:
- 客户端发送请求,并收到返回的响应
- 服务器处理请求,并返回响应
TCP
特点:
- 面向连接:在数据传输之前,发送端和接收端需要先建立一个连接,包括三次握手过程,连接的建立确保了双方的通信前提,并且在连接建立期间进行参数协商和初始化
- 可靠性:TCP 使用确认机制和重传机制来确保数据的可靠性,如果接收端在接收每个数据包后会发送确认消息,发送方没有收到确认消息,会重新发送该数据包,保证数据在传输过程中不会丢失或损坏
- 全双工通信:即发送端和接收端可以同时进行数据的发送和接收,不需要等待对方的响应
- 面向字节流:将数据视为一个连续的字节流,并将字节流分割为适当的 TCP报文段,并在传输过程中对它们进行排序和重新组装
- 有序性:每个 TCP报文 都有一个序列号,接收端根据序列号将数据包按照正确的顺序重新排列
- 适用高可靠性要求应用:文件传输、Web浏览、电子邮件等
- 流量控制和拥塞控制:流量控制用来控制发送端发送数据的速率,保证接收端能够及时处理接收到的数据,拥塞控制用于控制网络中的数据流量,避免网络拥塞
基于 TCP 编写网络通信程序
- 一个服务器包括三部分:收到请求、根据请求计算逻辑(业务逻辑)、返回响应
- 这里我们编写一个简单逻辑的 服务器程序,即无业务逻辑,请求是啥就返回啥
服务器代码
// TCP 版本的服务器 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("启动服务器!"); // 此处使用 CachedTreadPool 使用 FixedThreadPool 是不合适的 因为线程数不应该是有固定值的ExecutorService threadPool = Executors.newCachedThreadPool();while (true) { // 使用这个 clientSocket 和 具体的客户端进行交流Socket clientSocket = serverSocket.accept(); // 此处使用多线程处理 // 注意这里为什么要引入多线程 为了让多个客户端能同时跟服务器建立连接,并处理各自客户端的请求 // Thread t = new Thread(() ->{ // try { // processConnection(clientSocket); // } catch (IOException e) { // e.printStackTrace(); // } // }); // t.start(); // 当很多客户端需要进行请求时,当然相比于频繁的创建线程和销毁线程 我们这里直接创建一个线程池效率会高很多// 使用线程池threadPool.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}}});}}private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort()); // 基于上述 socket 对象和客户端进行通信try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){// 概念: // 则客户端建立一次连接,服务器可处理多次请求并返回响应,到客户端短时间内不需要这个连接,便直接断开连接 这属于长连接 // 则客户端建立一次连接,服务器也仅处理一次请求并返回响应,便直接断开连接 这属于短连接// 这里我们写的是长连接,从而需要使用循环 来处理多个请求和响应while (true) { // 1.读取请求Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) { // 没有下个数据 说明读完了 (客户端关闭了连接)System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());break;} // 此处使用 next 是一致读取到 换行符/空格/其他空白符结束,但是最终返回结果里不包含上述 空白符String request = scanner.next(); // 2.根据请求构造响应String response = process(request); // 3.返回响应结果 // OutputStream 相当于对应着一个文件描述符(socket文件) // 通过 OutputStream 就可以往这个 文件描述符 中写数据 // 但 OutputStream 没有 写字符串 这样的功能 但 // 所以我们 将其转化为 字符流 PrintWriter 且其对应的文件描述符还是同一个 从而可以进行写入PrintWriter printWriter = new PrintWriter(outputStream); // 此处使用 println 来写入 让结果中带有 一个 \n 换行 方便对接收端来接收解析 // 因为 TCP 是面向字节流的 所以 一次性读多少个字节都是可以的 // 从而这里我们可以进行一个隐式约定 使用 \n 来作为当前代码的请求或响应分割约定 与上面代码的 scanner.next() 相对应 // 从而该行代码这里使用 println 来相当于 TCP 发送数据,printWriter.println(response); // flush 用来刷新缓冲区 保证当前写入的数据 确实发送出去了printWriter.flush();System.out.printf("[%s:%d] req:%s; resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();}finally { // 每来一个客户端 就会创建一个 clientSocket ,每创建一个就要消耗一个文件描述符 // 因此当完成该客户端的请求后 就需要释放掉其 clientSocketclientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();} }客户端代码
public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp,int serverPort) throws IOException { // Socket 构造方法 能够识别 点分十进制格式的 IP 地址 必 DatagramPacket 更方便 // new 这个对象的同时 就会进行 TCP 连接操作 如果客户端没有该行代码 服务器就会在 accept 堵塞socket = new Socket(serverIp,serverPort);}public void start() {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){while (true) {// 1.先从键盘上读取用户输入的内容System.out.println(">");String request = scanner.next();if(request.equals("exit")) {System.out.println("goodbye");break;}// 2.把读到的内容构造成请求 发送给服务器PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request); // 此处的 flush 是确保数据确实发送出去了printWriter.flush();// 3.读取服务器的响应Scanner respScanner = new Scanner(inputStream);String response = respScanner.next();// 4.把响应内容显示到界面上System.out.println(response);}}catch (IOException e){e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);client.start();} }交互过程:
运行效果:
- 客户端发送请求,并收到返回的响应
- 服务器处理请求,并返回响应
IDEA 打开 支持多客户端模式
操作步骤:
- 完成上述步骤,我们便打开了 IDEA 多客户端模式,此时我们仅需再点击一次客户端启动按钮,便会有两个客户端共存了!
相关文章:
2023.9.8 基于传输层协议 UDP 和 TCP 编写网络通信程序
目录 UDP 基于 UDP 编写网络通信程序 服务器代码 客户端代码 TCP 基于 TCP 编写网络通信程序 服务器代码 客户端代码 IDEA 打开 支持多客户端模式 UDP 特点: 无连接性:发送端和接收端不需要建立连接也可相互通信,且每个 UDP 数据包都…...
单例模式,适用于对象唯一的情景(设计模式与开发实践 P4)
文章目录 单例模式实现代理单例惰性单例 上一章后续的内容是关于 JS 函数闭包的,考虑很多读者已经有了闭包基础或者希望通过实战理解,遂跳过上一章直接开始设计模式篇~ 需要注意的是,代码部分仅供参考,主要关注的内容是…...
C语言实现三子棋游戏(详解)
目录 引言: 1.游戏规则: 2.实现步骤: 2.1实现菜单: 2.2创建棋盘并初始化: 2.3绘制棋盘: 2.4玩家落子: 2.5电脑落子: 2.6判断胜负: 3.源码: 结语&…...
javaee之黑马乐优商城3
异步查询工具axios(儿所以时) vue官方推荐的ajax请求框架 新增品牌页面 如何找到上面这个页面 下面这个页面里面的新增商品弹窗 上面就是请求路径与请求方式 那么请求参数是什么? brand对象,外加商品分类的id数组cids (这里其实不止就是添加…...
Pytorch intermediate(二) ResNet
实现了残差网络,残差网络结构。代码比之前复杂很多 conv3x3:将输入数据进行一次卷积,将数据转换成为,残差块需要的shape大小 ResidualBlock:残差块,也是所谓的恒等块。为什么被称为恒等块,大概…...
【2023集创赛】加速科技杯作品:高光响应的二硫化铼光电探测器
本文为2023年第七届全国大学生集成电路创新创业大赛(“集创赛”)加速科技杯西北赛区二等奖作品分享,参加极术社区的【有奖征集】分享你的2023集创赛作品,秀出作品风采,分享2023集创赛作品扩大影响力,更有丰…...
编写postcss插件,全局css文件px转vw
跟目录下创建plugins文件夹,创建postcss-px-to-viewport.ts文件 文件内代码: // postcss 的插件 vite内置了postCss插件 无需安装 import { Plugin } from postcss;interface Options {viewportWidth: number }const Options {viewportWidth: 375, // …...
精品SpringCloud的B2C模式在线学习网微服务分布式
《[含文档PPT源码等]精品基于SpringCloud实现的B2C模式在线学习网站-微服务-分布式》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具: 开发语言:Java 框架:springcloud JDK版本…...
解决vue项目导出当前页Table为Excel
解决vue项目中导出当前页表格为Excel表格的方案 用到的技术: Vue2Element-uifile-saverxlsx 1、创建vue项目,安装element-ui 2、创建一个组件,组件内放入表格,和导出按钮 <template><div><!-- 导出的按钮 -->…...
C++设计模式_04_Strategy 策略模式
接上篇,本篇将会介绍C设计模式中的Strategy 策略模式,和上篇模板方法Template Method一样,仍属于“组件协作”模式,它与Template Method有着异曲同工之妙。 文章目录 1. 动机( Motivation)2. 代码演示Stra…...
目标检测YOLO实战应用案例100讲-基于YOLOv3多模块融合的遥感目标检测(中)
目录 2.2.3 YOLO 2.3 目标检测算法分析 2.3.1 目标检测结果评价指标...
element 表格fixed列高度无法100%
下文提到的滚动条皆为横向滚动条错误方法(旧方法,点击查看旧博客) 一下代码虽然能解决fixed列高度无法100%问题,但是会出现fixed列下面的滚动条无法被点击的问题(被fixed列遮挡),所以该方法并不…...
【接口自动化测试】Eolink Apilkit 安装部署,支持 Windows、Mac、Linux 等系统
Eolink Apikit 有三种客户端,可以依据自己的情况选择。三种客户端的数据是共用的,因此可以随时切换不同的客户端。 我们推荐使用新推出的 Apikit PC 客户端,PC 端拥有线上产品所有的功能,并且针对本地测试、自动化测试以及使用体…...
解决sass问题:npm ERR! node-sass@9.0.0 postinstall: `node scripts/build.js`
目录 一、遇到问题 解决办法 二、 再次遇到问题 解决办法 题外话 一、遇到问题 1.运行这个项目的适合,遇到了没有sass的问题 解决办法 然后就用命令下载sass npm install node-sass 二、 再次遇到问题 2.下载sass的时候又发现了一个这样的问题 npm ER…...
Python技巧---tqdm库的使用
文章目录 一、tqdm基本知识二、在pytorch中使用tqdm 提示:以下是本篇文章正文内容,下面案例可供参考 一、tqdm基本知识 “tqdm” 是一个 Python 库,用于在命令行界面中创建进度条。 基本使用如下: from tqdm import tqdm impor…...
linux-线程条件变量(cond)
概述 与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用 。 条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制&a…...
面试算法6:排序数组中的两个数字之和
题目 输入一个递增排序的数组和一个值k,请问如何在数组中找出两个和为k的数字并返回它们的下标?假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。例如,输入数组[1,2,4,6&…...
【智能家居-大模型】构建未来,聆思大模型智能家居交互解决方案正式发布
LISTENAI 近日,国内11家大模型陆续通过《生成式人工智能服务管理暂行办法》备案,多家大模型产品已正式开放,激发了新一轮大模型热潮。大模型在自然语言理解方面的巨大突破,实现了认知智能的技术跃迁,带来了时代的智慧…...
通讯网关软件002——利用CommGate X2HTTP-U实现HTTP访问OPC UA Server
本文介绍利用CommGate X2HTTP-U实现HTTP访问OPC UA Server。CommGate X2HTTP是宁波科安网信开发的网关软件,软件可以登录到网信智汇(wangxinzhihui.com)下载。 【案例】如下图所示,实现上位机通过HTTP来获取OPC UA Server的数据。 【解决方案】设置网关机…...
模拟经营类游戏是怎么开发的?
模拟经营类游戏开发是一个充满挑战但也充满乐趣的领域。下面是一些步骤和关键考虑因素,可以帮助您开始开发自己的模拟经营游戏: 明确游戏概念: 确定游戏开发的主题和类型,例如城市建设、农场经营、餐厅经营等。 制定一个引人入胜…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...










