【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 服务器代码

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 客户端代码

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时间简单通信的过程,如果大家感兴趣的话就请一键三连哦!!!

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值