• UDP数据报套接字编程


    UDP数据报套接字编程

    DatagramSocket API

            DatagramSocket,是UDP Socket,用于发送和收 UDP 数据报。使用这个类,表示一个 socket 对象。一个 socket 对象只能跟一台主机进行通信。在操作系统中,把这个 socket 对象当成一个文件来处理的,相当于是 文件描述符表 上的一项。

    构造方法:

    方法签名

    方法说明

    DatagramSocket()

    创建一个UDP数据报套接字的Socket,随机分配一个空闲端口(一般用于客户端)

    DatagramSocket(int port)

    创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

    普通方法:

    方法签名

    方法说明

    void receive(DatagramPacket p)

    从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)

    void send(DatagramPacket p)

       从此套接字发送数据报包(不会阻塞等待,直接发送)

       void close()

       关闭此数据报套接字

            此处传入的 DatagramPacket p 相当于是一个空的对象。receive 方法内部会对这个空对象进行内容填充,从而构造出结果数据。这个参数是一个“输出型参数”。

    DatagramPacket API

            DatagramPacket是UDP Socket发送和接收的数据报。表示 UDP 中传输的一个报文,构造这个对象,可以指定一些具体的数据进去。 

    构造方法:

    方法签名

    方法说明

    DatagramPacket(byte[] buf, int length)

    构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)

    DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)

    构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数

    length)。address指定目的主机的IP和端口号

    普通方法:

    方法签名

    方法说明

    InetAddress getAddress()

    从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址

    int getPort()

    从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号

    byte[] getData()

    获取数据报中的数据

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

    InetSocketAddress API

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

    方法签名

    方法说明

    InetSocketAddress(InetAddress addr, int port)

    创建一个Socket地址,包含IP地址和端口号

    示例一:回显服务器

            一个普通的服务器包括:收到请求,根据请求计算响应(业务逻辑),返回响应。这里就直接省略了业务逻辑。以下为一个客户端一次数据发送,和服务端多次数据接收(一次发送一次接收,可以接收多次),即只有客户端请求,但没有服务端响应的示例:

    服务器端 

    服务器的工作流程:

    1. 读取请求并解析

    2. 根据请求计算响应

    3. 构造响应并写回给客户端 

    1. import java.io.IOException;
    2. import java.net.DatagramPacket;
    3. import java.net.DatagramSocket;
    4. import java.net.SocketException;
    5. // UDP 版本的回显服务器
    6. public class UdpEchoServer {
    7. // 网络编程, 本质上是要操作网卡.
    8. // 但是网卡不方便直接操作. 在操作系统内核中, 使用了一种特殊的叫做 "socket" 这样的文件来抽象表示网卡.
    9. // 因此进行网络通信, 势必需要先有一个 socket 对象.
    10. private DatagramSocket socket = null;
    11. // 对于服务器来说, 创建 socket 对象的同时, 要让他绑定上一个具体的端口号.
    12. // 服务器一定要关联上一个具体的端口的!!!
    13. // 服务器是网络传输中, 被动的一方. 如果是操作系统随机分配的端口, 此时客户端就不知道这个端口是啥了, 也就无法进行通信了!!!
    14. public UdpEchoServer(int port) throws SocketException {
    15. socket = new DatagramSocket(port);
    16. }
    17. public void start() throws IOException {
    18. System.out.println("服务器启动!");
    19. // 服务器不是只给一个客户端提供服务就完了. 需要服务很多客户端.
    20. while (true) {
    21. // 只要有客户端过来, 就可以提供服务.
    22. // 1. 读取客户端发来的请求是啥.
    23. // receive 方法的参数是一个输出型参数, 需要先构造好个空白的 DatagramPacket 对象. 交给 receive 来进行填充.
    24. DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); // 客户在空白纸条上写着要吃东北大饼
    25. socket.receive(requestPacket); // 客户让商家做大饼
    26. // 此时这个 DatagramPacket 是一个特殊的对象, 并不方便直接进行处理. 可以把这里包含的数据拿出来, 构造成一个字符串.
    27. String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
    28. // 2. 根据请求计算响应, 由于此处是回显服务器, 响应和请求相同.
    29. String response = process(request); // 商家做好了
    30. // 3. 把响应写回到客户端. send 的参数也是 DatagramPacket. 需要把这个 Packet 对象构造好.
    31. // 此处构造的响应对象, 不能是用空的字节数组构造了, 而是要使用响应数据来构造.
    32. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
    33. requestPacket.getSocketAddress()); //DatagramPacket 这个只认字节,因此必须得response.getBytes() 再获取长度
    34. socket.send(responsePacket); //商家把大病给客户
    35. // 4. 打印一下, 当前这次请求响应的处理中间结果.
    36. System.out.printf("[%s:%d] request: %s; response: %s\n", requestPacket.getAddress().toString(),
    37. requestPacket.getPort(), request, response);
    38. }
    39. }
    40. // 这个方法就表示 "根据请求计算响应"
    41. public String process(String request) {
    42. return request;
    43. }
    44. public static void main(String[] args) throws IOException {
    45. // 端口号的指定, 大家可以随便指定.
    46. // 1024 -> 65535 这个范围里随便挑个数字就行了.
    47. UdpEchoServer server = new UdpEchoServer(6666);
    48. server.start();
    49. }
    50. }
    客户端

            服务器的 端口 是要固定指定的:目的是为了方便客户端找到服务器程序。

            客户端的 端口 是由系统自动分配的:如果手动指定,可能会和客户端其他程序的端口有冲突。服务器不怕冲突是因为服务器上面的程序可控,通过命令能看到哪些端口是空闲的;客户端是运行在客户电脑上的,不确定性太大。

    1. import java.net.DatagramSocket;
    2. import java.net.SocketException;
    3. // UDP 版本的 回显客户端
    4. public class UdpEchoClient {
    5. private DatagramSocket socket = null;
    6. private String serverIp = null;
    7. private int serverPort = 0;
    8. // 一次通信, 需要有两个 ip, 两个端口.
    9. // 客户端的 ip 是 127.0.0.1 已知.
    10. // 客户端的 port 是系统自动分配的.
    11. // 服务器 ip 和 端口 也需要告诉客户端. 才能顺利把消息发个服务器.
    12. public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
    13. socket = new DatagramSocket();
    14. this.serverIp = serverIp;
    15. this.serverPort = serverPort;
    16. }
    17. public void start() {
    18. System.out.println("客户端启动!");
    19. while (true) {
    20. // 1. 从控制台读取要发送的数据
    21. // 2. 构造成 UDP 请求, 并发送
    22. // 3. 读取服务器的 UDP 响应, 并解析
    23. // 4. 把解析好的结果显示出来.
    24. }
    25. }
    26. }

    客户端启动后会发送一个"hello world!" 的字符串到服务端,在服务端接收后,控制台输出内容如下:

    从以上可以看出,发送的UDP数据报(假设发送的数据字节数组长度为M),在接收到以后(假设接收

    的数据字节数组长度为N):

    1. 如果N>M,则接收的byte[]字节数组中会有很多初始化byte[]的初始值0,转换为字符串就是空白

    字符;

    2. 如果N

    数组长度更短)。

    要解决以上问题,就需要发送端和接收端双方约定好一致的协议,如规定好结束的标识或整个数据的长度。

    示例二:请求响应

    示例一只是客户端请求和服务端接收,并没有包含服务端的返回响应。以下是对应请求和响应的改造:

    构造一个展示服务端本地某个目录(BASE_PATH)的下一级子文件列表的服务

    (1)客户端先接收键盘输入,表示要展示的相对路径(相对BASE_PATH的路径)

    (2)发送请求:将该相对路径作为数据报发送到服务端

    (3)服务端接收并处理请求:根据该请求数据,作为本地目录的路径,列出下一级子文件及子文件夹

    (4)服务端返回响应:遍历子文件和子文件夹,每个文件名一行,作为响应的数据报,返回给客户端

    (5)客户端接收响应:简单的打印输出所有的响应内容,即文件列表。

    为了解决空字符或长度不足数据丢失的问题,客户端服务端约定好统一的协议:这里简单的设计为

    ASCII结束字符 \3 表示报文结束。

    以下为整个客户端服务端的交互执行流程:

    ---------------------------------------------------

    等待接收UDP数据报...

    客户端IP:127.0.0.1

    客户端端口号:57910

    客户端发送的原生数据为:[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33,

    0, 0, 0, ...此处省略很多0]

    客户端发送的文本数据为:hello world!           

    ---------------------------------------------------

    等待接收UDP数据报...客户端

    服务端

    ①服务端监听端口

    Q W E R T Y U I O P

    A S D F G H J K L

    Z X C V B N M

    space

    .?123

    return

    ②等待接收UDP数据报

    ③客户端输入要发送的内容

    ④发送数据报

    ⑥返回响应:发送响应的数据报

    ⑤接收到UDP数据报,解析处理

    ⑦接收到响应UDP数据报,解析

    约定好统一的请求协议:

    \3作为结束符

    客户端发送和服务端解析数据

    约定好统一的响应协议:

    \3作为结束符

    服务端发送和客户端解析数据

    以下为服务端和客户端代码:

    UDP服务端

               System.out.println("等待接收UDP数据报...");

               // 3.等待接收客户端发送的UDP数据报,该方法在接收到数据报之前会一直阻塞,接收到数

    据报以后,DatagramPacket对象,包含数据(bytes)和客户端ip、端口号

               socket.receive(requestPacket);以上服务端运行结果和示例一是一样的:

    UDP客户端

               

    客户端启动后会等待输入要展示的路径:方法签名

    方法说明

    ServerSocket(int port)

    创建一个服务端流套接字Socket,并绑定到指定端口

    方法签

    方法说明

    Socket

    accept()

    开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket

    对象,并基于该Socket建立与客户端的连接,否则阻塞等待

    void

    close()

    关闭此套接字

    在输入想查看的目录路径后,会接收并打印服务端响应的文件列表数据:

    此时服务端也会打印接收到的客户端请求数据:

  • 相关阅读:
    1043 输出PATest
    从固定管线到可编程管线:十段代码入门OpenGL
    PCL点云处理之Failed to find match for field ‘intensity‘问题的解决方法 (二百一十四)
    荧光染料BDP FL ceramide,BDP-FL神经酰胺
    tomcat使用指南_入门级教学
    Mybatis-Plus 使用技巧与隐患
    HTML5+CSS3+JS小实例:网格图库
    【Shell 脚本速成】07、Shell 流程控制——if 判断语句
    网站使用谷歌登录 oauth java nuxt auth
    山西电力市场日前价格预测【2023-10-05】
  • 原文地址:https://blog.csdn.net/weixin_43497876/article/details/138197668