• 网络编程套接字之二【UDP】


    目录

    1. TCP 和 UDP 的区别

    2. DatagramSocket

    3. DatagramPacket

    4. 写回显—服务器

    5. 回显服务器代码 

    6. 写回显—客户端 

    7. 回显—客户端代码

    8. 回显服务器客户端的,执行流程步骤 

    9. 运行服务器 再运行客户端

    10. 写翻译—服务器


    网络编程套接字,就是研究如何写代码完成网络编程

    套接字(socket):是操作系统给应用程序提供的API,来让应用层和传输层进行交互,实际上API也就是传输层给应用层提供的

    网络传输层中,有很多种协议,其中最常见的就是 TCP 和 UDP,这两种协议的特性差别是比较大的,所以操作系统就提供了两种不同风格的API

    1. TCP 和 UDP 的区别

    TCPUDP解释
    有连接无连接

    打电话是有连接,这是通信双方建立好连接,才能通信(交互数据)

    A打电话给B,B接了,才能说话

    发短信/ 发微信 是无连接

    直接一发就过去了

    可靠传输不可靠传输

    首先明确,可靠传输不是指A发给B的数据,B100%能收到

    而是指 A能够知道B是不是收到了,这才是可靠传输

    面向字节流面向数据报

    面向字节流:TCP和文件操作一样,也是属于 流 的

    面向数据报:这个“数据报”为基本单位

    全双工全双工

    全双工:一个通道,双向通信

    和全双工相对的就是 “半双工”

    半双工:一个通道,单向通信

    UDP 的 socket

    客户端 - 服务器

    2. DatagramSocket

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

    构造方法

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

    方法

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

    3. DatagramPacket

    DatagramPacket 代表了一个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()
    获取数据报中的数据

    4. 写回显—服务器

    服务器  UdpEchoServer

    1.先写DatagramSocket对象

        private DatagramSocket socket = null;

    2.然后再写Udp回显服务器的构造方法

    (参数的端口表示我们自己的服务器要绑定的端口)

    1. public UdpEchoServer(int port) throws SocketException {
    2. socket = new DatagramSocket(port);
    3. }

    3.写一个启动服务器的方法start(),在start中写上while循环来执行具体逻辑

    1. public void start() throws IOException {
    2. System.out.println("服务器启动!");
    3. while(true) {
    4. }
    5. }

       a)读取请求并解析

    1. DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
    2. socket.receive(requestPacket);
    3. // 把这个DatagramPacket 对象转为字符串,方便去打印
    4. // 里面的参数对应的是 取出字节数组,对应的范围
    5. String request = new
    6. String(requestPacket.getData(),0,requestPacket.getLength());

       b)根据请求计算响应(这里写一个process方法)

                String response = process(request);
    1. // 当前写的是回显服务器,响应数据和请求是一样的
    2. public String process(String request) {
    3. return request;
    4. }

       c)把响应写回到客户端

    1. // 以这个对象为单位进行发送。这就是面向数据报,发送和接收都是以这个DatagramPacket为基本单位
    2. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
    3. response.getBytes().length,
    4. requestPacket.getSocketAddress());
    5. //getSockAddress() 此时只是有数据了,还需要知道数据发给谁,要写上客户端的地址的端口
    6. //getBytes() 得到字符串里面包含的字节数组
    7. socket.send(responsePacket);

       d)打印一个日志,记录当前的情况

    1. System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
    2. requestPacket.getPort(), request, response);

    4.写一个mian方法来执行Udp回显服务器

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

     在进行 读取请求并解析  和  把响应写回到客户端 时都分别创建了新的DatagramPacket对象

    里面传的值有什么区别?

     

     在进行 把响应写回到客户端 时,如何确定发给谁的问题?

    在给DataGramPacket对象传参时,传入客户端地址和端口(用getSocketAddress方法)

    5. 回显服务器代码 

    1. import java.io.IOException;
    2. import java.net.DatagramPacket;
    3. import java.net.DatagramSocket;
    4. import java.net.SocketException;
    5. /**
    6. * Created with IntelliJ IDEA.
    7. * Description:
    8. * User: 28463
    9. * Date: 2022—10—12
    10. * Time: 15:17
    11. */
    12. public class UdpEchoServer {
    13. private DatagramSocket socket = null;
    14. //参数的端口表示我们自己的服务器要绑定的端口
    15. public UdpEchoServer(int port) throws SocketException {
    16. socket = new DatagramSocket(port);
    17. }
    18. //通过这个服务器启动服务器
    19. public void start() throws IOException {
    20. System.out.println("服务器启动!");
    21. while(true) {
    22. //循环里面处理一次请求
    23. //1.读取请求并解析
    24. DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
    25. socket.receive(requestPacket);
    26. // 把这个DatagramPacket 对象转为字符串,方便去打印
    27. // 里面的参数对应的是 取出字节数组,对应的范围
    28. String request = new String(requestPacket.getData(),0,requestPacket.getLength());
    29. //2.根据请求计算响应
    30. String response = process(request);
    31. //3.把响应写回到客户端
    32. // 以这个对象为单位进行发送。这就是面向数据报,发送和接收都是以这个DatagramPacket为基本单位
    33. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
    34. response.getBytes().length,
    35. requestPacket.getSocketAddress());
    36. //getSockAddress() 此时只是有数据了,还需要知道数据发给谁,要写上客户端的地址的端口
    37. //getBytes() 得到字符串里面包含的字节数组
    38. socket.send(responsePacket);
    39. //4.打印一个日志,记录当前的情况
    40. System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
    41. requestPacket.getPort(), request, response);
    42. }
    43. }
    44. // 当前写的是回显服务器,响应数据和请求是一样的
    45. public String process(String request) {
    46. return request;
    47. }
    48. public static void main(String[] args) throws IOException {
    49. UdpEchoServer server = new UdpEchoServer(9090);
    50. server.start();
    51. }
    52. }

    6. 写回显—客户端 

    UdpEchoClient

    1. 写一个DatagramSocket对象

    1. DatagramSocket socket = null;
    2. private String serverIP;
    3. private int serverPort;

    2. 再写回显客户端的构造方法

    1. // 两个参数一会会在发送数据的时候用到,暂时先将这两个参数存起来,以备后用
    2. public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
    3. //这里不是说没有端口,而是让系统自动指定一个空闲的端口
    4. socket = new DatagramSocket();
    5. // 假设serverIP 是形如 1.2.3.4 这种点分十进制的表示方式
    6. this.serverIP = serverIP;
    7. this.serverPort = serverPort;
    8. }

    3. .写一个启动客户端的方法start(),在start中写上while循环来执行具体逻辑

    1. public void start() throws IOException {
    2. Scanner scan = new Scanner(System.in);
    3. while(true) {
    4. }
    5. }

       a)从控制台读取用户输入的内容

    1. System.out.println("-> ");
    2. String request = scan.next();

       b)构造一个UDP请求,发送给服务器

    1. DatagramPacket requestpacket = new DatagramPacket(request.getBytes(),
    2. request.getBytes().length,
    3. InetAddress.getByName(this.serverIP),
    4. this.serverPort);
    5. //把数据报通过网络发送出去
    6. socket.send(requestpacket);

       c)从服务器读取UDP响应数据,并解析

    1. DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
    2. //用receive针对空的responsePacket进行填充
    3. socket.receive(responsePacket);
    4. String response = new
    5. String(responsePacket.getData(),0,responsePacket.getLength());

       d)把服务器的响应提示到控制台上

                System.out.println(response);

    4. 最后再写个main方法,进行回显客户端的执行

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

    给服务器发送请求,给DatagramPacket对象传入IP和端口来描述发送给谁 

     

    在写客户端构造的时候,不用写客户端IP

     再看一个DatagramPacket对象的传值,注意里面传的是字节

    客户端给服务器发送一个数据:

    客户端自己的主机IP:源IP

    客户端没有手动的绑定一个端口,操作系统就会自动分配一个空闲的端口

    服务器的主机IP:目的IP

    服务器绑定的端口:目的端口

    服务器需要手动指定指定端口,是为了方便客户端找到服务器在哪。

    客户端不需要手动指定端口,是因为操作系统会自动分配一个空闲的端口,如果你自己手动指定一个端口,可能这个端口别的程序正在使用,就会导致程序无法正确运行了

    总之,服务器是运行在程序员自己的服务器上,这个主机是可控的,而客户端是运行在用户自己的电脑上,是不可控的

    7. 回显—客户端代码

    1. import java.io.IOException;
    2. import java.net.*;
    3. import java.util.Scanner;
    4. /**
    5. * Created with IntelliJ IDEA.
    6. * Description:
    7. * User: 28463
    8. * Date: 2022—10—12
    9. * Time: 15:16
    10. */
    11. public class UdpEchoClient {
    12. DatagramSocket socket = null;
    13. private String serverIP;
    14. private int serverPort;
    15. // 两个参数一会会在发送数据的时候用到,暂时先将这两个参数存起来,以备后用
    16. public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
    17. //这里不是说没有端口,而是让系统自动指定一个空闲的端口
    18. socket = new DatagramSocket();
    19. // 假设serverIP 是形如 1.2.3.4 这种点分十进制的表示方式
    20. this.serverIP = serverIP;
    21. this.serverPort = serverPort;
    22. }
    23. public void start() throws IOException {
    24. Scanner scan = new Scanner(System.in);
    25. while(true) {
    26. // 1.从控制台读取用户输入的内容
    27. System.out.println("-> ");
    28. String request = scan.next();
    29. // 2.构造一个 UDP 请求,发送给服务器
    30. DatagramPacket requestpacket = new DatagramPacket(request.getBytes(),
    31. request.getBytes().length,
    32. InetAddress.getByName(this.serverIP),
    33. this.serverPort);
    34. //把数据报通过网络发送出去
    35. socket.send(requestpacket);
    36. // 3.从服务器读取 UDP 响应数据,并解析
    37. DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
    38. //用receive针对空的responsePacket进行填充
    39. socket.receive(responsePacket);
    40. String response = new String(responsePacket.getData(),0,responsePacket.getLength());
    41. // 4.把服务器的响应提示到控制台上
    42. System.out.println(response);
    43. }
    44. }
    45. public static void main(String[] args) throws IOException {
    46. UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
    47. client.start();
    48. }
    49. }

    8. 回显服务器客户端的,执行流程步骤 

    9. 运行服务器 再运行客户端

     

    也可以在idea中设置,允许运行多个客户端

     

     

    10. 写翻译—服务器

    英译汉;

    请求输入 hello,响应返回一个 你好

    请求输入 car,响应返回一个 汽车

    1. import java.io.IOException;
    2. import java.net.SocketException;
    3. import java.util.HashMap;
    4. import java.util.Map;
    5. /**
    6. * Created with IntelliJ IDEA.
    7. * Description:
    8. * User: 28463
    9. * Date: 2022—10—14
    10. * Time: 16:08
    11. */
    12. public class UdpTranslateServer extends UdpEchoServer{
    13. //翻译本质上是 key -> value
    14. private Map dict = new HashMap<>();
    15. public UdpTranslateServer(int port) throws SocketException {
    16. super(port);
    17. dict.put("hello", "你好");
    18. dict.put("car", "汽车");
    19. dict.put("dog", "小狗");
    20. dict.put("cat", "小猫");
    21. //在这里就可以输入很多的内容。
    22. //现在的翻译程序基本都是这样,只不过用一个很大的哈希表,包含了很多单词和翻译
    23. }
    24. //重写 process 方法,实现查询哈希表的操作
    25. @Override
    26. public String process(String request) {
    27. return dict.getOrDefault(request,"词在词典中未找到!");
    28. }
    29. //start 和父类完全一样
    30. public static void main(String[] args) throws IOException {
    31. UdpTranslateServer server = new UdpTranslateServer(9090);
    32. server.start();
    33. }
    34. }

     启动服务器,在客户端输入看看(这里用前面写的回显—客户端就可以)

     一个服务器程序的基本流程,都是和上面一样的

    但是最核心的区别就是“根据请求计算响应”,这个取决于服务器的业务逻辑

    qq服务器,根据请求计算响应:根据用户发送的消息,返回一个“发送成功还是失败”

    淘宝服务器,根据请求计算响应:支付一个订单,根据你的订单信息,完成支付操作

    (扣款,修改订单状态)返回支付是否成功等待状态

    百度服务器,根据请求计算响应:根据用户输入的查询词,找到匹配的网页

    抖音服务器,根据请求计算响应:根据用户的身份特征(用户会话),返回可能感兴趣的视频

  • 相关阅读:
    Vue原理
    vue3错误排查-POST请求的body参数 传参方式form-data和json
    记录一次生产环境MySQL死锁以及解决思路
    关于矿井地面电力综合自动化系统的研究与产品选型
    芯片不是st公司,cmsis-dap调试器的使用
    Qt5开发及实例V2.0-第四章Qt基本对话框
    力扣第572题 另一棵树的子树 c++深度(DFS)注释版
    Graphviz 作图工具
    LeetCode每日一练 —— 203. 移除链表元素
    六、流量监管、流量整形
  • 原文地址:https://blog.csdn.net/m0_58761900/article/details/127280181