网络通信(二)

UDP通信

特点:无连不是先接、不可靠通信

不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了

java提供了一个java.net.DatagramSocket类来实现UDP通信

DatagramSocket

DatagramSocket 用于创建客户端和服务器端的 UDP 通信。它提供了发送和接收 DatagramPacket 的方法。

构造器:
  • DatagramSocket(int port): 创建绑定到特定端口的 DatagramSocket

  • DatagramSocket(int port, InetAddress laddr): 创建绑定到特定地址和端口的 DatagramSocket

处理数据包方法:
  • send(DatagramPacket p): 发送 DatagramPacket

  • receive(DatagramPacket p): 接收 DatagramPacket

DatagramPacket

DatagramPacket 是用来封装 UDP 数据包的类。它包含数据、源端口、目标端口和 IP 地址。

构造器:
  • DatagramPacket(byte[] buf, int length): 创建一个空的数据包,数据存储在 buf 数组中,长度为 length

  • DatagramPacket(byte[] buf, int length, InetAddress address, int port): 创建一个数据包,指定数据、目标 IP 地址和端口。

创建数据包:
  1. 首先,你需要一个字节数组来存储你的数据。

  2. 然后,使用适当的构造器创建 DatagramPacket 对象。

  3. 对于发送,你需要设置目标 IP 地址和端口。

  4. 对于接收,DatagramSocket.receive() 方法会填充 DatagramPacket 的源 IP 地址和端口。

示例代码

//服务器端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
​
public class UDPServer {
    public static void main(String[] args) throws Exception {
        DatagramSocket serverSocket = new DatagramSocket(12345);//创建服务端对象,指定服务端端口
        byte[] receiveData = new byte[1024];//指定字节数组大小
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);//创建数据包
​
        serverSocket.receive(receivePacket);//数据包接收数据
        //接收到指定长度的字节数组并转为字符串
        String receivedMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println("Server received: " + receivedMessage);
​
        serverSocket.close();
    }
}
//客户端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
​
public class UDPClient {
    public static void main(String[] args) throws Exception {
        InetAddress IPAddress = InetAddress.getByName("localhost");//创建客户端对象,指定接收主机IP
        int port = 12345;//指定要发送到主机的服务端端口
​
        String sentence = "Hello World!";
        byte[] buf = sentence.getBytes();//封装数据到字节数组中
        DatagramPacket packetToSend = new DatagramPacket(buf, buf.length, IPAddress, port);
​
        DatagramSocket clientSocket = new DatagramSocket();//创建数据包用于接收数据
        clientSocket.send(packetToSend);//数据包发送数据
​
        clientSocket.close();
    }
}

demo1:实现一发一收

  1. 实现客户端、服务端

  2. 创建客户端对象

  3. 创建数据包对象,封装发出的数据

  4. 开始使用数据包发送数据

  5. 释放资源(数据包)

  6. 创建服务端对象(注册端口)

  7. 创建数据包对象,接收数据

  8. 开始使用数据包接收数据

  9. 从字节数组中,把本次接收的数据直接打印出来(注意:可能多次接收数据,字节数组被多次不完全覆盖,因此需要接收多少就倒多少)

  10. (最后,可以通过socket获取客户端的IP地址)

demo2:实现多发多收

直接对demo1进行改造:

  1. 实现客户端时,直接在客户端发送信息的代码块处放入循环中

  2. 注意:此时,客户端只能指定的发一两段信息,因此,直接实现让客户从键盘输入

  3. 实现服务端时,直接在接收处代码块放入死循环中

  4. 注意:服务端的资源一定要在最后关闭,也就是说,资源关闭不包括在循环中,或者说,一般服务端的的资源不会关闭

  5. (最后,可以通过socket获取客户端的IP地址)

TCP通信

特点:面向连接、可靠通信

通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。

java提供了一个java.net.Socket类和ServerSocket类来实现TCP通信

模型:

在 Java 中,Socket 类是用于建立 TCP(传输控制协议)网络连接的。TCP 是一种面向连接的、可靠的、基于字节流的协议,它确保数据以发送顺序到达目的地。

Socket 类

Socket 类位于 java.net 包中,它代表了一个客户端的 TCP 连接端点。

构造方法

Socket 类有几个构造方法,用于以不同的方式创建套接字:

  1. 无参构造方法:

    Socket()

    创建一个新的未连接的套接字。

  2. 指定服务器地址和端口的构造方法:

    Socket(InetAddress address, int port)
    Socket(String host, int port)

    这两个构造方法都会创建一个新的套接字并尝试连接到指定的 IP 地址和端口,或者主机名和端口。

  3. 指定服务器地址、端口和本地端口的构造方法:

    Socket(InetAddress address, int port, InetAddress localAddress, int localPort)
    Socket(String host, int port, InetAddress localAddress, int localPort)

    这些构造方法允许你指定本地绑定的地址和端口,以及远程服务器的地址和端口。

  4. 指定服务器地址、端口、超时时间和操作模式的构造方法:

    Socket(InetAddress address, int port, boolean stream)
    Socket(String host, int port, boolean stream)

    这些构造方法允许你指定是否使用流模式(对于 TCP,通常设置为 true)。

数据处理方法

Socket 类提供了几个方法来处理数据:

  1. 获取输入流:

    InputStream getInputStream()

    返回一个输入流,用于从套接字读取数据。

  2. 获取输出流:

    OutputStream getOutputStream()

    返回一个输出流,用于向套接字写入数据。

  3. 关闭套接字:

    void close()

    关闭套接字,释放系统资源。

  4. 连接服务器:

    void connect(SocketAddress endpoint)
    void connect(SocketAddress endpoint, int timeout)

    这些方法用于建立到指定套接字地址的连接,可选地指定连接超时时间。

  5. 设置套接字选项:

    • setKeepAlive(boolean on): 设置 TCP 保活机制。

    • setReuseAddress(boolean on): 允许本地地址被重用。

    • setSoLinger(boolean on, int linger): 设置套接字的 SO_LINGER 选项,控制关闭套接字时的行为。

示例代码

客户端:

import java.io.*;
import java.net.*;
​
public class TCPClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 1234)) {
            OutputStream output = socket.getOutputStream();
            output.write("Hello Server!".getBytes());
​
            InputStream input = socket.getInputStream();
            byte[] buffer = new byte[1024];
            int bytesRead = input.read(buffer);
            System.out.println(new String(buffer, 0, bytesRead));
​
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
服务器端:

import java.io.*;
import java.net.*;
​
public class TCPServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(1234)) {
            Socket socket = serverSocket.accept();
            InputStream input = socket.getInputStream();
            byte[] buffer = new byte[1024];
            int bytesRead = input.read(buffer);
            System.out.println(new String(buffer, 0, bytesRead));
​
            OutputStream output = socket.getOutputStream();
            output.write("Hello Client!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这些示例中,客户端创建了一个 Socket 对象来连接服务器,并使用输入输出流来发送和接收数据。服务器端使用 ServerSocket 来监听连接请求,并接受客户端的连接,然后也使用输入输出流来处理数据。

demo1:实现一发一收

  1. 创建客户端的Socket对象(套接字对象),请求与 服务端的连接

  2. 使用socket对象调用getOutputStream()方法得到字节输出流

  3. 使用数据输出流(其他高级流也行,这个最适合)完成数据的发送

  4. 释放资源:关闭socket管道

  5. 创建ServerSocket的对象,同时为服务端注册端口

  6. 使用serverSocket对象,调用accept方法,等待客户端的连接请求得到socket对象

  7. 从socket通信管道中得到一个字节输入流

  8. 把原始的字节输入流包装为数据输入流(其他高级流也行,这个最适合)

  9. 使用数据输入流读取客户端发送的消息

    注意:接收数据时数据输入流对象调用的方法和数据输出流对象调用的方法相对应

    (最后,可以通过socket获取客户端的IP地址)

demo2:实现多发多收

改造demo1:

  1. 将客户端写数据部分代码块放入死循环

  2. 将死循环内改造为用户键盘输入(发送后,调用flush方法刷新立即写出)

  3. 将服务端读取客户端消息放入死循环,并把关闭套接字去掉,实现多收

  4. 注意:如果客户端离线,服务端处会出现异常,因此在服务端捕获异常,就可以知道客户端的在线离线状态

支持服务端与多个客户端同时通信【客户端多开】

改造demo2:

  1. 由于需要每一个套接字都需要一个线程负责,所以需要专门写一个服务端的读取线程类,用于接收并处理套接字

  2. 主要核心的接收信息功能就在run方法中写了

  3. 主线程中拿到套接字时可以适当的输出客户端上线状态

  4. run方法中也可以添加客户端的下线状态(客户端的下线会使得服务端的接收消息处一直等待发生异常,因此需要捕获异常----提示客户端下线)

问题:

为什么UDP通信可以单线程实现多客户端收发信息,但是TCP通信不可以单线程实现多客户端收发信息?

答:

UDP(用户数据报协议)和TCP(传输控制协议)在设计上有着本质的区别,这导致了它们在处理多客户端通信时的不同方式。

UDP通信单线程模型

UDP 是无连接的协议,它的主要特点是简单和高效。在 UDP 中,数据以数据报的形式发送,每个数据报都是独立的,不保证数据报的顺序、可靠性或完整性。因此,UDP 通信可以很容易地使用单线程模型来处理多个客户端:

  1. 无状态:UDP 服务器不需要维护任何关于客户端状态的信息,每个数据报都是独立的,服务器可以简单地接收和发送数据报,而不需要跟踪任何特定的客户端。

  2. 并发处理:由于 UDP 是无连接的,服务器可以同时接收来自多个客户端的数据报,并且不需要为每个客户端创建独立的连接或线程。

  3. 简单性:UDP 的简单性使得它很容易实现一个单线程的服务器,该服务器可以循环接收数据报,并根据需要广播或转发给其他客户端。

TCP通信多线程或非阻塞模型

TCP 是面向连接的协议,它提供了数据的顺序、可靠性和完整性保证。TCP 连接需要经过三次握手过程来建立,并且在连接期间,服务器需要维护每个客户端的状态信息:

  1. 连接状态:TCP 服务器需要为每个客户端维护一个连接状态,这包括序列号、确认号、窗口大小等信息。

  2. 顺序保证:TCP 保证数据的顺序,因此服务器需要确保数据按照正确的顺序发送和接收。

  3. 流量控制和拥塞控制:TCP 需要处理流量控制和拥塞控制,这需要服务器为每个连接维护额外的状态信息。

  4. 资源消耗:由于 TCP 连接需要更多的资源和处理,因此当客户端数量增加时,单线程模型可能会成为性能瓶颈。

  5. 并发限制:单线程处理多个 TCP 连接可能会导致性能问题,因为每个连接都需要进行数据的读取和写入,这可能会阻塞线程,影响其他连接的处理。

因此,为了有效地处理多个 TCP 客户端,通常需要使用多线程模型或非阻塞 I/O(如 Java 的 NIO):

  • 多线程模型:为每个客户端连接创建一个新线程,这样可以并行处理多个连接,但可能会消耗大量资源。

  • 非阻塞 I/O:使用非阻塞 I/O 和事件驱动的方法,如 Java 的 NIO(New I/O)库,可以更高效地处理多个连接,而不需要为每个连接创建一个线程。

总之,UDP 的无连接特性和简单性使其适合于单线程模型,而 TCP 的面向连接特性和对可靠性的保证使其更适合于多线程或非阻塞 I/O 模型。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第13章-循迹功能 循迹小车讲解 原理分析 STM32智能小车循迹教程 红外对管使用 PID循迹算法分析

讲解一下我们小车里面的循迹部分,包括红外基础使用,无PID循迹和有PID循迹。 第13章-循迹功能 13.1-非PID循迹功能完成 先红外对管调试 我们这里学习一下,如何实现循迹功能 如何才能让小车沿着黑线运动、要让小车感知到黑线的位置&#x…

【SpringBoot】SpringBoot中防止接口重复提交(单机环境和分布式环境)

📝个人主页:哈__ 期待您的关注 目录 🌼前言 🔒单机环境下防止接口重复提交 📕导入依赖 📂项目结构 🚀创建自定义注解 ✈创建AOP切面 🚗创建Conotroller 💻分布…

[CISCN 2024] Crypto部分复现

文章目录 OvOez_rsacheckin浅记一下 迟来的文章 OvO 题目描述: from Crypto.Util.number import * from secret import flagnbits 512 p getPrime(nbits) q getPrime(nbits) n p * q phi (p-1) * (q-1) while True:kk getPrime(128)rr kk 2e 65537 kk …

3d打印问题总结

1.打印拉丝:https://zhuanlan.zhihu.com/p/152221550 解决方案:温度过高,PLA材料材料喷嘴温度一般设置为200度比较合适。

string OJ题

下面分享一下string做题心得 1. 明白字符串中存储的数字为0 8 9与0 8 9 完全不同,字符0其实在串中存储的是48,要有意识的转化。字符串中如果存数字8,意味着存了BS(退格) 例如1: 算出结果为5,存…

网上打印试卷的步骤是什么

对于学生和家长来说,打印试卷是日常学习中的一项重要需求。那么,如何在网上方便地打印试卷呢?下面,就让我来为您介绍琢贝云打印的试卷打印步骤。 一、选择琢贝云打印的原因 支持多种文件格式打印,包括图片、PPT、PDF、…

20.SkyWalking

一.简介 SkyWalking用于应用性能监控、分布式链路跟踪、诊断: 参考连接如下: https://github.com/apache/skywalking https://skywalking.apache.org/docs/ 二.示例 通过官网连接进入下载页面:https://archive.apache.org/dist/skywalkin…

2024年【T电梯修理】考试内容及T电梯修理新版试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年【T电梯修理】考试内容及T电梯修理新版试题,包含T电梯修理考试内容答案和解析及T电梯修理新版试题练习。安全生产模拟考试一点通结合国家T电梯修理考试最新大纲及T电梯修理考试真题汇总,…

k8s中yaml文件配置指定私有镜像仓库

1. yaml文件介绍 2. 如何快速编写yaml文件 1)如果有已存在的pod时可以 kubectl get pod xxxxxx -oyaml 2)直接假跑一次并查看 kubectl run xxxxxx --image镜像名 --dry-run -oyaml 3)查看pod相关描述信息 kubectl explain pod 3. 编写…

linux 安装redis 并设置开机启动

个人实测 流程 1、第一步 先下载redis ** redis地址 https://download.redis.io/releases/选择你想要的版本 我下载的是 如下图 2、第二步:把下载的包放到linux里面 我用的是 XSHELL 和XFTP 放到/usr/local/java路径下 你可以随便放 3、第三步: ** 执行 以下命令 进行解压 t…

js之图表使用

今天为了给大家演示图表的使用,今天展示下切换图形的修改属性快速修改 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script src"./js/jquery-3.7.1.js"></script><script src…

Llama 3没能逼出GPT-5!OpenAI怒“卷”To B战场,新企业级 AI 功能重磅推出!

Meta 是本周当之无愧的AI巨星&#xff01;刚刚推出的 Llama 3 凭借着强大的性能和开源生态的优势在 LLM 排行榜上迅速跃升。 按理说&#xff0c;Llama 3在开源的状态下做到了 GPT-3.7 的水平&#xff0c;必然会显得用户&#xff08;尤其是企业用户&#xff0c;他们更具备独立部…

flash-linear-attention中的Chunkwise并行算法的理解

这里提一下&#xff0c;我维护的几三个记录个人学习笔记以及社区中其它大佬们的优秀博客链接的仓库都获得了不少star&#xff0c;感谢读者们的认可&#xff0c;我也会继续在开源社区多做贡献。github主页&#xff1a;https://github.com/BBuf &#xff0c;欢迎来踩 0x0. 前言 …

老外卖27刀每月的教程已经更新

用了两天半的时间&#xff0c;边学习&#xff0c;边整理了一份老外的视频教程&#xff0c;涉及Facebook&#xff0c;YouTube&#xff0c;tiktok等大的流量平台&#xff0c;有案例&#xff0c;有分析&#xff0c;有如何做。 这个教程是老外讲的&#xff0c;没有什么玄乎的塑造价…

mysql 函数 GROUP_CONCAT 踩坑记录,日志:Row 244 was cut by GROUP_CONCAT()

mysql 函数 GROUP_CONCAT 踩坑记录&#xff0c;报错&#xff1a;Row 244 was cut by GROUP_CONCAT 结论&#xff1a;个人建议还是放在内存中拼接吧~db日志信息&#xff1a;Row 244 was cut by GROUP_CONCAT())根本原因&#xff1a;拼接的字符串长度超过 group_concat_max_len […

【LLM多模态】多模态LLM在图表处理的应用

note 在真实场景下&#xff0c;我们进行测试&#xff0c;多模态大模型在处理显著文本时表现尚可&#xff0c;但在处理细粒度文本时往往效果并不太好&#xff0c;why? ​具体原因如下&#xff1a; 首先&#xff0c;视觉编码器的分辨率对于多模态大模型的性能影响较大&#x…

Jenkins 还可以支持钉钉消息通知?一个插件带你搞定!

Jenkins 作为最流行的开源持续集成平台&#xff0c;其强大的拓展功能一直备受测试人员及开发人员的青睐。大家都知道我们可以在 Jenkins 中安装 Email 插件支持构建之后通过邮件将结果及时通知到相关人员。 但其实 Jenkins 还可以支持钉钉消息通知&#xff0c;其主要通过 Ding…

IIC通信(STM32)

一、IIC概念 &#xff11;、两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff09;、SDA&#xff08;Serial Data&#xff09; 同步&#xff0c;半双工 2、带数据应答 3、支持总线挂载多设备&#xff08;一主多从、多主多从&#xff09;一般使用一主多从。一主多从的…

clone方法总结Java

Java中Object类当中有许多方法&#xff0c;如图所示&#xff1a; clone方法就是其中一种&#xff0c;分为浅拷贝&#xff0c;深拷贝举一个例子&#xff1a; 浅拷贝&#xff1a; 在Person类当中右键鼠标然后&#xff0c;选中Generate&#xff1a; 然后重写clone方法 protecte…

Linux-应用编程学习笔记(三、文件属性和目录)

一、文件类型 1、普通文件&#xff08;ls -l 文件&#xff0c;权限前边第一个"-"代表普通文件&#xff1b;stat 文件&#xff09; 文本文件&#xff1a;ASCII字符 二进制文件&#xff1a;数字0/1 2、目录文件&#xff08;‘’d&#xff09;&#xff1a;文件夹 3…