• TCP VS UDP


    程序员写网络程序,主要编写的应用层代码!

    真正要发这个数据,需要上层协议调用下层协议,应用层要调用传输层,则传输层给应用层提供一组api,统称为:soket api

    基于UDP的api

    基于TCP的api

    这两个协议差别很大!!提供的api的差异也很大!!

    UDP:

    • 无连接:使用UDP通信的双方,不需要可以得存对端的相关信息
    • 不可靠传输:消息发了就发了,不关注结果(投递简历)
    • 面向数据报:以一个UDP数据报为基本单位
    • 全双工:一条路径,双向通信

    TCP:

    • 有链接:使用TCP通信的双方,需要刻意保存对方的相关信息
    • 可靠传输:不是说发了就100%能够到达对方,尽可能的传输过去(自己知道没成功)(打电话)
    • 面向字节流:以字节为传输的基本单位,读写方式非常灵活
    • 全双工:一条路径,双向通信

    在上述所说的连接:并不是拿一根绳子,把两个设备绑一块,而是一个”抽象的连接“,可以理解成通信双方,各自记录了对方的信息;比如:民政局领结婚证!!(男女双方建立连接)!!

    全双工 VS 半双工

    那么,有了上述的基础知识,我们先来了解一下UDP的api吧(比TCP的要简单!)

    两个重要的方法:DatagramSocket()   DatagramPacket()

    DatagramPacket() :这个对象是一个UDP数据报

    DatagramSocket()  :Datagram:就是数据报;Socket说明这个对象是一个socket对象

    socket对象相当于对应到系统中一个特殊的文件(socket文件)

    socket文件并非对应到硬盘上的某个数据存储区域,而是对应到网卡,这个硬件设备!!

    要想进行网络通信,就需要有socket文件这样的对象,借助这个socket文件对象,才能够间接的操作网卡!(遥控器)

    往这个socket对象中写数据,相当于通过网卡发送数据

    从这个socket对象中读数据,相当于通过网卡接收数据

    那么,我们来看一下DatagramSocket()  的构造方法:

    1. DatagramSocket() :创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
    2. DatagramSocket(int port):创建一个UDP数据报套接字的Socket,绑定到本机指定的端口下(一般用于服务端)

      这两个构造方法中,一个带参数,一个不带参数

      服务器这边的socket往往要关联一个具体的端口号!(必须要不变)

      客户端这边则不需要手动指定,系统自动分配即可!(不要求)

    3. void  receive(DatagramPacket  p)从此套接字接收数据报(如果没有接受到数据报,该方法就会阻塞等待)
    4. void  send(DatagramPacket  p)从此套接字发送数据报(不会阻塞等待,直接发送)
    5. void  close()关闭此数据报套接字(一定是socket/文件,确定不用了,才能使用该close())

    socket也是文件,文件用完了就得记得关闭,否则会出现文件资源泄露的问题!!

    那么,我们来看一下DatagramPacket() 的构造方法:

    1. DatagramPacket(byte[]  buf , int length) 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
    2. DatagramPacket(byte[]  buf , int offset , int length , SocketAddress address) 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length),address指定目的主机的IP和端口号!!
      这个版本,需要显式的设置地址进去,通常要用来发送信息!!

    那么,我们来基于UDP  Socket来写一个最简单的客户端服务器程序吧!!

    回显服务器(echo server):客户端发了个请求,服务器返回一个一模一样的响应!

    一个服务器:主要要做三个核心工作:

    1. 读取请求并解析
    2. 根据请求计算响应(省略)
    3. 把响应返回到客户端

    主要代码:

    服务器(读取请求,发送响应)

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

    客户端:发请求,接收响应

    1. package network;
    2. import java.io.IOException;
    3. import java.net.*;
    4. import java.util.Scanner;
    5. // UDP 版本的 回显客户端
    6. public class UdpEchoClient {
    7. private DatagramSocket socket = null;
    8. private String serverIp = null;
    9. private int serverPort = 0;
    10. // 一次通信, 需要有两个 ip, 两个端口.
    11. // 客户端的 ip 是 127.0.0.1 已知.
    12. // 客户端的 port 是系统自动分配的.
    13. // 服务器 ip 和 端口 也需要告诉客户端. 才能顺利把消息发个服务器.
    14. public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
    15. socket = new DatagramSocket();
    16. this.serverIp = serverIp;
    17. this.serverPort = serverPort;
    18. }
    19. public void start() throws IOException {
    20. System.out.println("客户端启动!");
    21. Scanner scanner = new Scanner(System.in);
    22. while (true) {
    23. // 1. 从控制台读取要发送的数据
    24. System.out.print("> ");
    25. String request = scanner.next();
    26. if (request.equals("exit")) {
    27. System.out.println("goodbye");
    28. break;
    29. }
    30. // 2. 构造成 UDP 请求, 并发送
    31. // 构造这个 Packet 的时候, 需要把 serverIp 和 port 都传入过来. 但是此处 IP 地址需要填写的是一个 32位的整数形式.
    32. // 上述的 IP 地址是一个字符串. 需要使用 InetAddress.getByName 来进行一个转换.
    33. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
    34. InetAddress.getByName(serverIp), serverPort);
    35. socket.send(requestPacket);
    36. // 3. 读取服务器的 UDP 响应, 并解析
    37. DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
    38. socket.receive(responsePacket);
    39. String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
    40. // 4. 把解析好的结果显示出来.
    41. System.out.println(response);
    42. }
    43. }
    44. public static void main(String[] args) throws IOException {
    45. UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
    46. // UdpEchoClient client = new UdpEchoClient("42.192.83.143", 9090);
    47. client.start();
    48. }
    49. }

    对上述代码进行分析:

    对于客户端:

    1. 服务器先启动,执行到receive进行阻塞
    2. 客户端运行之后,从控制台读取到数据,并进行send
    3. 客户端这边send之后,继续往下走,走到receive读取响应,会阻塞等待
    4. 客户端这边真正收到服务器send回来的数据之后,就会接解除阻塞,执行下面的打印操作
    5. 客户端继续进入下一轮循环,阻塞在Scanner.next这里,等待用户输入新的数据

    对于服务器:

    1. 服务器先启动,执行到receive进行阻塞
    2. 客户端运行之后,从控制台读取到数据,并进行send
    3. 服务器这边,就从reserve返回,读取到请求数据(客户端发来的),往下走到process,生成响应,再往下走,到send,并且打印日志
    4. 进入下一轮循环,在此阻塞在receive,等待客户端下一个请求

    小结一下瞬间开心:

    客户端:

    读取用户输入

    构造请求并发送

    客户端读取服务器响应

    客户端把响应转成字符串并显示出来

    服务器:

    读取用户的请求

    根据请求计算响应

    把响应写回到客户端

    因此:总的大致过程为:

  • 相关阅读:
    C++人生重开模拟器
    IDEA2023.1版本新建Web项目并配置本地Tomcat
    Packet Tracer的使用介绍
    【C语言】指针作为参数传值常见问题
    PaddleOCR 更换模型
    Java时间处理---Java8中时区相关类库介绍
    文件分卷压缩和压缩的区别是什么
    JS测试出最小支持字体,以及修复PDFJS的文本错误偏移
    微服务项目:尚融宝(41)(核心业务流程:借款额度审批)
    AttributeError: 'NoneType' object has no attribute 'int4WeightExtractionHalf'
  • 原文地址:https://blog.csdn.net/weixin_64308540/article/details/133622290