自学内容网 自学内容网

JavaEE-网络编程(1)

目录

1.TCP和UDP

1.1 有连接/无连接

1.2 可靠传输/不可靠传输

1.3 面向字节流/面向数据报

1.4 全双工/半双工

2. socket

3.UDP的 socket api

3.1DatagramSocket

3.2 DatagramPacket

4. 写一个UDP回显服务器

5.写一个UDP客户端



1.TCP和UDP

TCP/IP的五层网络模型(从下到上):物理层->网络链路层->网络层->传输层->应用层

传输层中,有两个重要协议:一个是TCP,一个是UDP

这两个协议的差别非常大,编写代码的时候也是不同的风格

TCP:有连接,可靠传输,面向字节流,全双工

UDP:无连接,不可靠传输,面向数据报,全双工

1.1 有连接/无连接

此处的有连接/无连接是一个抽象的概念,指的是逻辑上的连接,而不是物理上的连接(网线)

有连接:

对于TCP协议来说,TCP协议中,就保存了对端的信息。

A 和 B 通信,A 和 B 先建立连接,让A 保存 B 的信息,B 保存 A 的信息

(彼此之间知道,谁是和他建立连接的那个)

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

1.2 可靠传输/不可靠传输

网络上,数据是非常容易出现丢失的情况的(丢包),

光信号/电信号,都有可能受到外界的干扰

可靠传输:不是保证数据包 100% 到达,而是尽可能提高传输的成功率,如果出现丢包情况,能够感知到

不可靠传输:只是把数据包发了,然后就不管了

直观感受,可靠传输也许更好?

事实上,可靠传输也是需要付出代价的:可靠传输的效率比不可靠传输的效率要低~~

1.3 面向字节流/面向数据报

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

优点:支持任意长度    缺点:粘包问题

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

优点:不存在粘包问题   缺点:有长度限制

1.4 全双工/半双工

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

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

举个例子:

马路上,单行车道=>半双工

双车道/四车道=>全双工

2. socket

在计算机中,“文件” 通常是一个“广义的概念”,文件可以代指电脑上的软件,也能代指一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的)

而电脑的网卡,就可以抽象为是一个文件=>socket文件

操作网卡的时候,流程和操作普通文件差不多:打开->读写->关闭

操作网卡,直接操作硬件不好操作

于是,把操作网卡转换成操作 socket 文件,socket 文件就相当于“网卡的遥控器”

简而言之就是:电脑的网卡被操作系统当作文件来管理

既然被操作系统当作文件来管理,操作系统就提供了一套socket api 供程序员进行网络编程

UDP 和 TCP 都各有一套 socket api

3.UDP的 socket api

3.1DatagramSocket

Java中使用 DatagramSocket类 对socket api进行了封装

构造方法:打开文件

接收数据报/发送数据报的方法

关闭文件

3.2 DatagramPacket

Java用 DatagramPacket类 来表示一个完整的数据报

UDP 数据报的载荷数据,就可以通过构造方法来指定:

4. 写一个UDP回显服务器

回显服务器:

客户端给服务器发送一个数据(请求),服务器返回一个数据(响应),请求是啥,响应就是啥

服务器运行的步骤如下:

1.接收请求

2.解析并处理请求

3.响应客户端

4.打印日志

代码如下:

public class UdpEchoServer {
    private DatagramSocket socket=null;

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

    public void start() throws IOException {
        System.out.println("服务器启动!");

        //进行不断的接收请求
        while (true) {
            //1.接收请求
            DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request=new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.解析并处理请求
            String response=precess(request);
            //3.响应客户端
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //4.打印日志
            System.out.printf("[%S:%d] req:%s  resp:%s",requestPacket.getAddress(),requestPacket.getPort(),
                    request,response);
        }

    }
    private String precess(String request) {
        return request;
    }

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

【注意】

  1. 对于服务器来说,客户端啥时候发请求,发多少个请求,我们无法预测。因此服务器中通常需要一个死循环,持续不断的尝试读取客户端的请求数据(7*24h)
  2.  
    socket的receive方法的返回值是void,此处是把参数作为“输出的结果”,也就是输出型参数。调用前,先构造空的(不是null)数据报,把对象传入 receive 里,receive 就会把数据从网卡中读出来,填充到参数中
  3. 构造一个用于发送的数据报时,需要填入发送的长度,此处的长度指的是有多少个字节,因此使用response.getBytes().length,该方法返回的是String中字节的个数。不可以使用response.length,这个方法返回的是String中字符的个数
  4. 收到请求的 源ip、源端口,就是返回响应的目的ip、目的端口。而这个IP和端口号在socket的receive操作就已经填充到了 request 数据报中,直接调用request.getSocketAddress()就能够直接获取到一个InetSocketAdress对象,该对象中包含了该数据报的ip和端口号
  5. UDP报头里包含了源端口、目的端口IP 报头里包含了源IP、目的IP,而DatagramPacket对象里既包含了UDP的信息,也包含了IP信息,因此可以直接获取到源IP、目的IP,源端口、目的端口
  6. socket 不需要 close 。一个文件是否要关闭,首先要考虑这个文件对象的生命周期。此处的socket 对象,伴随着整个udp服务器,自始至终。如果服务器关闭(进程结束),进程结束时就会自动释放 PCB 的文件描述符表中的所有资源,不需要手动调用 close
  7. 当服务器启动了,客户端没有发送请求,服务器会处于一个阻塞状态。这是由于 receive触发阻塞行为,当客户端发来了请求,receive 才会返回,客户端的请求没来,receive 就一直阻塞

DatagramPacket 有三个方法:

  1. getAddress 只拿到 IP
  2. getPort 只拿到端口
  3. getSocketAddress 同时拿到IP 和端口(IP 和端口通过一个InetAdress 对象来表示)

5.写一个UDP客户端

步骤如下:

1.用户输入请求

2.把请求发送到服务器

3.接收服务器的响应

4.把响应的信息转换成字符串,并打印出来

代码如下:

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

    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        this.socket = new DatagramSocket();
    }

    public void start() throws IOException {
        while (true) {
            //1.请用户输入请求
            System.out.println("请输入要发送的内容:");
            Scanner scanner=new Scanner(System.in);
            String request=scanner.next();
            //2.把请求发送到服务器
            DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            //3.发送数据报
            socket.send(requestPacket);
            //4.接收服务器的响应
            DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            //5.把响应信息转换成字符串,并且打印出来
            String response=new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

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

【注意】

  1. UDP客户端需要定义 severIp 和 severPort 属性来保存目的 IP 和目的端口
  2. 构造 DatagramSocket 时,必须使用无参数版本。原因:如果客户端使用固定端口,很可能客户端运行的时候,这个端口被别的程序占用,就会使得当前这个程序运行失败。因此客户端不推荐使用固定端口,使用无参数版本,操作系统会自动分配空闲端口。(服务器使用固定端口是因为服务器要经常被各种客户端访问,使用随机端口可能会使客户端获取到的目的端口失效,造成损失)

 构造请求的数据报:

1.载荷

2.目的 IP 目的端口

传入 severIp 的时候,需要使用InetAddress.getByName()将 severIp从String类型转换成InetAddress类型


构造服务器对象,设置端口为 9090

构造出客户端对象,传入IP地址:127.0.0.1,和服务器端口 9090

为什么使用127.0.0.1?

127.0.0.1 是一个特殊的 IP,环回 IP ,表示当前这个主机

无论你主机的 IP 是啥,都可以使用 127.0.0.1 代替,类似于 this

分别运行服务器的main方法和客户端的main方法,执行结果如下:

UDP回显服务器和UDP客户端正常运行

【注意】

给客户端设置端口时,可能会出现以下报错:

出现这种情况的原因是,这个端口号已经被其他客户端、服务器占用了

解决办法:换一个端口


如果哪里有疑问的话欢迎来评论区指出和讨论,如果觉得文章有价值的话就请给我点个关注还有免费的收藏和赞吧,谢谢大家


原文地址:https://blog.csdn.net/NoobNo2/article/details/143830944

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!