云计算百科
云计算领域专业知识百科平台

UDP 和 TCP 网络知识和“Udp协议的回声服务器详细剖析”(5000字详细解析回升服务器的点点滴滴)(网络编程01)

目录

应用层

一、传输层的两个核心协议

二、有链接/无连接(是抽象的概念,是虚拟的/逻辑上的连接),例如要进行网络通信,物理上的连接(网线啥的)

三、可靠传输 vs 不可靠传输

四、面向字节流 vs 面向数据报

五、全双公 vs 半双工

六、回升服务器(重点)


应用层

        操作系统提供的一组 API => socket API (传输层给应用层提供);socket 这个词的意思是插槽的意思,对应在主板上可以理解为主板上的一些接口。

一、传输层的两个核心协议

TCP协议 和 UDP协议这两个差别非常大。

  • TCP 是有连接,可靠传输,面向字节流,全双工
  • UDP 是无连接,不可靠传输,面向数据报,全双工

二、有链接/无连接(是抽象的概念,是虚拟的/逻辑上的连接),例如要进行网络通信,物理上的连接(网线啥的)

  • 为什么TCP 是可靠的传输(有链接),而 UDP 是不可靠传输(无连接)。

对于 TCP 来说,TCP 协议中,就会保存对端的信息,例如 A和B通信,A和B先建立连接,让A保存B的信息,B保存A的信息(彼此之间知道,谁是和它连接的哪个)。

对于UDP来说,UDP协议本身,不保存对方的信息。就是无连接 。

三、可靠传输 vs 不可靠传输

        网络上数据是非常容易出现丢失的情况(丢包),因为光信号/电信号,都可能受到外界的干扰。本来是传输0101,但是因为数据的丢包,其中有些bit位就被修改了,这样乱了的数据就会被识别出来,把这样的数据极可能传输不到原来的地方。

        而且网络世界是通过 路由器/交换机 来进行联络的,路由器/交换机 类似于“十字路口”,它也会发生我们生活中十字路口堵车的情况,(在某个时间点,实际需要转发的数据超过了设备能转发的上线),就会容易发生数据丢包问题。

  •         可靠传输的意思是,不能保证数据包 100% 的到达,而是尽可能的提高传输成功的概率。如果出现了丢包,能够感知到。
  •         不可靠传输,只是把数据发出去,他到底是到了还是没到,他就不管了。

四、面向字节流 vs 面向数据报

面向字节流,读写数据的时候,是以字节为单位的

面向数据报,读写数据的时候,是以一个数据报为单位(不是字符为单位),一次必须读写一个 UDP 数据报,不能是半个。

五、全双公 vs 半双工

全双工:一个通信链路,支持 双向通信 (能读,也能写)

半双工:一个通信链路,只支持单向通信(只能读,或者只能写)

        他们俩就像我们的一条公路,公路上的黄色线,就是车道的分割线。全双工就像是双向公路,这条路可以从A到B,也可以从B到A。半双工就像是单向公路,只能从A到B,或者是从B到A。

接下来就可以使用socket api 进行网络编程了

        在计算机中“文件”通常是一个“广义的概念”,文件还能代指一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的)。就像 网卡 抽象成 socket 文件,操作网卡的时候,流程和操作普通文件差不多, 打开 -> 读写 -> 关闭。

        操作网卡,直接不好操作,把操作网卡当成操作 socket文件,就容易操作了。socket文件就相当于“网卡的遥控器”。

六、回升服务器(重点)

下面我们写一个回声服务器。回声服务器是一个客户端输入什么,服务器端什么任务都不做,直接返回客户端输入的内容

先说一下大致流程:

  • 创建一个接口,这个接口的端口必须是固定的,为的就是防止这个服务器今天在这个端口,明天在另外一个端口,端口非常随机,我们很难去访问这个流动的服务器,所以我们把端口给固定之后,端口就不会跑了。这是类的创建和构造函数。Datagram是数据报,Socket 是接口,所以 DatagramSocket 是数据报的接口,创建一个数据报的接口。 public class UdpEchoServer01 {
    private DatagramSocket socket = null;

    public UdpEchoServer01(int port) throws SocketException {
    // 指定一个固定端口号,给服务器使用
    socket = new DatagramSocket(port);
    }

    }

  • 创建好接口之后,因为这个接口是给服务器服务的,所以我们需要不断的接收客户端的请求。 public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    }
    }
  • 在不断请求中,我们需要去获取客户端的请求,所以我们需要创建一个数据报,来接收请求的内容 public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    }
    }

  • 上面我们创建好这个数据报之后,去调用接口来给这个数据报读取客户端发来的请求。就能知道客户端要做什么了,如果客户端没有请求的发送,这里也会发生阻塞等待。 我们需要一个 byte 数据来保存请求的数据,所以创建这个请求数据报的时候需要传入一个byte 数组来保存请求。  public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    //////////// 使用接口给 requestPacket 这个数据报中传入客户端的请求
    socket.receive(requestPacket);

    }
    }

  • 经过 requestPacket 这个请求数据报接收客户端发送的请求之后,这个数据报内就保存好了客户端发送的数据报的内容,我们把其中的客户端发送的请求给解析出来放到一个String中。 因为是构造String,所以我们获取到 data 之后,需要从0开始读取,然后构造这个数据报的长度的大小。 public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    socket.receive(requestPacket);
    //////////////////////// 获取 requestPacket 中的 data
    String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

    ////////////// 这里无法使用,如果传入 getData().length 每次构造的长度都会是4096,会造成请求混乱
    // String request = new String(requestPacket.getData(), 0, requestPacket.getData().length);
    }
    }

  • 我们有了这个请求之后,构造一个处理函数,处理一下这个请求,模拟一下真实服务器的运行。因为这里我们写的是回声服务器,你也可以直接使用这个请求字符串来构造响应数据报。这里封装成一个函数来处理这个请求。 public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    socket.receive(requestPacket);
    String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
    //// 使用 response 接收响应
    String response = process(request);

    }
    }
    ///// 服务器处理请求返回响应的函数
    public String process(String request){
    return request;
    }

  • 拿到响应之后我们就可以构造数据报返回给客户端的接口,这时候我们需要知道客户端的IP和端口,这两个内容都在requestPacket接收客户端的请求的时候就保存起来了,这时候我们可以构造一个响应数据报,直接调用 requestPacket 里面的 getAddress() 获取 IP,getPort() 获取端口。或者使用 getSocketAdderss() 直接获取客户端的 IP和端口。 public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    socket.receive(requestPacket);
    String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
    String response = process(request);

    ///////////////// 直接使用 getSocketAdderss()
    DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
    requestPacket.getSocketAddress());
    /////////////// 使用 getAddress() 和 gerPort() 分别获取 IP 和 端口号
    // DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
    // requestPacket.getAddress(), requestPacket.getPort());

    }
    }
    public String process(String request){
    return request;
    }

  • 最后可以把响应数据报返回给客户端,之后就是客户端来接受这个响应了。 还可以打印一下服务器端日志,requestPacket 里面保存了客户端的 IP 和 端口号。 [IP:端口号] req: 请求 resp: 响应 哪个 IP 的 机器,哪个端口的程序 访问了服务器,请求是什么,服务器给的响应是什么 public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
    DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    socket.receive(requestPacket);
    String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
    String response = process(request);
    DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
    requestPacket.getSocketAddress());
    ////////// 把响应数据报返回给客户端
    socket.send(responsePacket);
    ///////////// 打印一下服务器日志
    System.out.printf("[%s:%d] req: %s resp: %s\\n", requestPacket.getAddress(), requestPacket.getPort(),
    request, response);
    }
    }
    public String process(String request){
    return request;
    }

  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » UDP 和 TCP 网络知识和“Udp协议的回声服务器详细剖析”(5000字详细解析回升服务器的点点滴滴)(网络编程01)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!