【Java EE初阶十二】网络编程TCP/IP协议(一)

1. 网络编程

通过网络,让两个主机之间能够进行通信->就这样的通信来完成一定的功能,进行网络编程的时候,需要操作系统给咱们提供一组API,通过这些API来完成编程;API可以认为是应用层和传输层之间交互的路径,其中Socket Api(可以认为是插座)通过这个一套Socket Api可以完成不同主机之间,不同系统之间的网络通信;

传输层提供的网络协议主要有两个tcp、udp:这两个协议的特性(工作原理差异很大,就会导致使用这两种协议进行网络编程,也会存在一定的差异)

1.1 TCP和UDP的区别

1、TCP是有连接的,UDP是无连接的;

这里的连接是抽象的概念,计算机中,这种抽象的连接是很常见的,此处的连接本质上就是建立连接的双方,各自保存对方的信息,即两台计算机建立连接,就是双方彼此保存了对方的关键信息;

TCP想要通信,就需要先建立连接(即计算机保存对方的信息),做完之后,才能后续通信(如果A和B想要建立连接,但是B拒绝了,通信就无法完成建立)

UDP想要通信,就直接发送数据报即可,不需要征得对方的同意,UDP自身也不会保存对方的信息(虽然UDP不知道,但是写程序的人得知道,UDP自己不保存对方的信息,但是调用UDP的socket api的时候就要把对方的位置信息等都传输过去)

2、TCP是可靠传输的,UDP是不可靠传输的;

网络上进行的通信,A->B发送一个信息,首先送到B的信息不可能做到100%送达,

所谓的可靠传输,就是退而求其次,当A->B发送一个消息,但是消息是不是达到B这一方,A这边自己是能感知到的,所以A在消息发送失败的时候就可以采取一定的措施(尝试重传之类的措施)

TCP就内置了可靠的传输机制,但是UDP就没有内置可靠传输;

可靠传输的不足:1、可靠传输所使用的机制更加复杂;2、可靠传输会导致消息的传输效率更加低;

综上所述:

TCP协议:发送方知道传输的数据是否成功的传输给接收方

UDP协议:消息发出去,就不管了,不再考虑消息是否成功发送给接收方;

3、TCP是面向字节流的,UDP是面向数据报的;

TCP也是和文件操作一样,以字节为单位来进行传输;UDP则是按照数据报(UDP有着自己严格的数据报格式)为单位来进行传输的

4、TCP和UDP都是全双工的;

一个信道,允许双向通信,就是全双工;一个信道,只能单向通信,就是半双工;

代码中使用一个socket对象,就可以发送数据也可以接收数据;

2. 关于UDP

2.1 UDP的socket api

Socket其实是操作系统中的一个概念,本质上是一种特殊的文件;Socket就属于是把"网卡"这个设备给抽象成了文件了,往Socket文件里面写数据,就相当于通过网卡发送数据,从Socket文件里面读数据,就相当于通过网卡接收数据,(如此把网络通信和文件操作给统一了)

DatagramSocket :

Java中,就是用DatagramSocket这个类,来表示系统内部的socket文件;

DatagramPacket: 使用这个类,来表示一个UDP数据报;UDP是面向数据报的,每一次传输都是以UDP数据报为基本单位的;

2.2 基于UDP实现通信

写一个简单的UDP的客户端/服务器通信的程序

要求我们写的程序没有具体的业务逻辑,只是调用单纯的socket api,让客户端给服务器发送一个请求,且该请求是一个从控制台输入的字符串,服务器收到字符串之后,就会把这个字符串原封不动的返回给客户端,客户端在显示出来(即该服务器是回显服务器echo server)

1、读取请求并解析

服务器和客户端都需要创建socket对象,但是服务器的socket一般要显示的指定一个端口号,而客户端的socket一般不能显式指定(不显式指定,此时系统就会自动分配一个随机的端口),客户端的端口号是不需要确定的,交给系统进行随机分配即可,如果我们手动指定确定的端口,就可以和别人的程序的端口号冲突;

Q:服务器这边手动指定端口号,难道就不会出现端口号冲突吗?为啥客户端在意这个冲突,而服务器不在意这个冲突?

A:首先服务器是在程序员手里面的,一个服务器上有哪些程序,这些程序都使用哪些端口,程序员都是可控的,且程序员在写代码的时候,就可以指定一个空闲的端口,给当前的服务器使用即可;其次客户端的情况就不一样了,因为客户端是在用户的电脑上的,一方面,用户有成千上万,每一个用户电脑上所装的程序都不一样,占用的短端口也不一样,林外一方面,用户这边如果出现了端口号冲突,用户这方面自己本身不知道是什么原因

所以,客户端的端口号还是交给系统来进行分配,因为系统能保证肯定分配一个空闲的端口号;

服务器一旦启动,就会立即执行到这里的receive这里的方法,此时客户端的请求可能还没有过来,此时遇到这种情况,receive就会直接阻塞,一直阻塞到客户端把请求发送过来为止;


2、根据请求计算响应(一般的服务器都会经历的过程)

这个步骤是一个服务器程序最核心的步骤,我们当先的echo server不涉及到这些流程,只要求当请求过来就把请求当作响应,因为UDP是无连接的,所以udp自身不会保存数据要发送的对象信息,就需要每一次发送的时候,重新指定数据要发送到哪儿去;

复制代码

3、把响应显示到客户端

4、打印一个日志,将我们进行的数据交互都打印出来

Q:为啥上述所写的代码中,没有写close,因为socket也是文件,不关闭的话就会出现之前我们所讲的文件资源泄露问题嘛?

A: 首先socket是文件描述符表中的一个表项,每一次打开一个文件,就会占用一个位置;文件描述符是在pcb上的(跟随的是进程)

其次这个socket在整个程序的运行中都是需要使用的(不能提前关闭),当socket不需要使用的时候,意味着程序就要结束了,进程结束,此时随之的文件描述符表就会销毁了(pcb也会销毁),被销毁后资源就会被系统进行自动回收;

最后,所谓出现泄露,是指代码中频繁打开文件,但是不会关闭,在一个进程的运行过程中,不断的打开文件,并逐渐消耗掉文件描述符表里面的内容,最终该资源就会被消耗殆尽了;但是,如果进程的生命周期很短,打开一下没多久就关闭了,谈不上所谓的资源泄露;

综上所述,文件资源泄露这样的问题,在服务器这边是比较严重的,在客户端这边其实影响不大;

2.3 代码实现

2.3.1 服务器代码

java 复制代码
package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    // 创建一个 DatagramSocket 对象. 后续操作网卡的基础.
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 这么写就是手动指定端口
        socket = new DatagramSocket(port);
        // 这么写就是让系统自动分配端口
        // socket = new DatagramSocket();
    }

    public void start() throws IOException {
        // 通过这个方法来启动服务器.
        System.out.println("服务器启动!");
        // 一个服务器程序中, 经常能看到 while true 这样的代码.
        while (true) {
            // 1. 读取请求并解析.
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            // 当前完成 receive 之后, 数据是以 二进制 的形式存储到 DatagramPacket 中了.
            // 要想能够把这里的数据给显示出来, 还需要把这个二进制数据给转成字符串.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求计算响应(一般的服务器都会经历的过程)
            //    由于此处是回显服务器, 请求是啥样, 响应就是啥样.
            String response = process(request);
            // 3. 把响应写回到客户端.
            //    搞一个响应对象, DatagramPacket
            //    往 DatagramPacket 里构造刚才的数据, 再通过 send 返回.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 打印一个日志, 把这次数据交互的详情打印出来.
            System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

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

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

2.3.2 客户端代码

java 复制代码
package network;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp = "";
    private int serverPort = 0;

    public UdpEchoClient(String ip, int port) throws SocketException {
        // 创建这个对象, 不能手动指定端口.
        socket = new DatagramSocket();
        // 由于 UDP 自身不会持有对端的信息. 就需要在应用程序里, 把对端的情况给记录下来.
        // 这里咱们主要记录对端的 ip 和 端口 .
        serverIp = ip;
        serverPort = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 从控制台读取数据, 作为请求
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 把请求内容构造成 DatagramPacket 对象, 发给服务器.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            socket.send(requestPacket);
            // 3. 尝试读取服务器返回的响应了.
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 4. 把响应, 转换成字符串, 并显示出来.
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

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

2.3.3 客户端和服务器交互逻辑

分析如下:

1、服务期先启动,启动之后就开始进行循环,执行到receive这里并阻塞(此时还没有客户端过来)

2、客户端开始启动,也会先进入while循环,执行scanner.next,并且在这里进行阻塞,当用户在控制台输入字符串后,next就会返回,并且构造请求数据病发出来

3、客户端发出数据之后:

服务器:就会从rfeceive中返回,进一步的执行解析请求为字符串,执行process操作,执行send操作

客户端:继续往下执行,执行到receive服务器的响应;

4、客户端收到从服务器返回的数据之后,就会从receive中返回,执行这里的打印操作,也就把响应给显示出来了;

5、服务器这边完成过一次循环之后,又执行到receive这里;客户端这边完成一次循环之后,又执行到scanner.next这里,双双进入阻塞;

ps:本篇内容主要讲解关于UDP时间简单通信的过程,如果大家感兴趣的话就请一键三连哦!!!

相关推荐
白总Server6 小时前
Bash和Zsh在处理大文件时差异
网络·websocket·网络协议·udp·ssh·ssl·shell
路由侠内网穿透8 小时前
本地部署资源聚合搜索神器 Jackett 并实现外部访问
linux·运维·服务器·网络协议·tcp/ip
学习嵌入式的小羊~9 小时前
视频图像刷新到HTTP的原理
网络·网络协议·http
码叔义10 小时前
X509TrustManager信任SSL证书
python·网络协议·ssl
WoTrusSSL12 小时前
小程序SSL证书过期怎么办?
网络协议·小程序·ssl
没资格抱怨12 小时前
Http和Https的区别
网络协议·http·https
黑风风12 小时前
详解了解websocket协议
网络·websocket·网络协议
老马识途2.012 小时前
工作中,当遇到要把http请求变成https时 怎么处理
网络协议·http·https
Albert XUU13 小时前
nettrace rtt分析器
linux·运维·网络·网络协议·网络安全·腾讯云·运维开发
桃酥40313 小时前
17、UDP怎么实现可靠传输【中高频】
网络·网络协议·udp