当前位置: 首页 > news >正文

JavaEE 初阶第十九期:网络编程“通关记”(一)

专栏:JavaEE初阶起飞计划

个人主页:手握风云

目录

一、网络编程

1.1. 为什么需要网络编程

1.2. 什么是网络编程

二、Socket套接字

2.1. 分类

2.2. UDP数据报套接字

2.3. 字典服务器


一、网络编程

1.1. 为什么需要网络编程

        我们在浏览器中,打开B站或者油管等视频网站,实质是通过网络,获取到网络上的⼀个视频资源。与本地打开视频文件类似,只是视频文件这个资源的来源是⽹络。 相⽐本地资源来说,网络提供了更为丰富的网络资源,如视频、图片、文本、网页。

1.2. 什么是网络编程

        网络编程是指编写能够在计算机网络中进行数据传输、通信和交互的程序的过程。它涉及到利用网络协议(如 TCP/IP、HTTP 等),让不同设备(如计算机、服务器、移动设备等)之间能够交换信息,实现资源共享、远程控制、数据同步等功能。

        我们写的程序处于应用层,主要工作是调用系统API把数据交给传输层,剩下的都由操作系统、驱动、硬件实现。传输层提供的网络通信API,也称为Socket API(网络编程套接字)。

二、Socket套接字

2.1. 分类

        传输层所涉及到的TCP和UDP协议,这两个协议提供了不同的API。流套接字,使用传输层TCP协议,特点:有链接、可靠传输、面向字节流。数据报套接字,使用传输层UDP协议,特点:无连接、不可靠传输、面向数据报。

2.2. UDP数据报套接字

        UDP,即User Datagram Protocol。DatagramSocket是UDP Socket API,代表了操作系统的API文件。文件是硬盘设备的“抽象”,读写文件的时候,本质是操作硬盘。网卡硬件设备也是通过文件封装的。通过网络发送数据,需要往网卡中写入;通过网络接受数据,需要从网卡中读取。

        Java中创建一个DatagramSocket对象,相当于在操作系统中打开一个Socket文件。这里的Socket文件就代表了网卡。

  • DatagramSocket构造方法
方法签名方法说明
DatagramSocket()
创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端口(⼀般用于客户端)
DatagramSocket(int port)创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端口(⼀般用于服务端)
  • DatagramSocket方法
方法签名方法说明
void receive(DatagramPacket p)
从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报(不会阻塞等待,直接发送)
void close()关闭此数据报套接字
  • DatagramPacket方法
方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()
从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据包中的数据

          网络通信的基本流程:客户端(client),主动发起通信;服务器(server),被动接受通信。客户端向服务器发起一个请求,等待客户端一个响应,这就是最简单的“一问一答”模型。

            一个最简单的网络程序,客户端读取用户输入的内容,通过网络发送给服务器,服务器从客户端读取到请求内容,然后根据请求计算响应,接着把响应返回到客户端,客户端读到相应之后,最后把响应结果显示到控制台上。

    import java.net.DatagramSocket;
    import java.net.SocketException;public class EchoServer {// 创建Socket对象private DatagramSocket socket;public EchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}
    }

            接下来的网络通信都是依赖于上面定义的socket对象。当这个对象创建好之后,此时这个socket与就关联到了一个端口号port,也称为“绑定端口号”,通过端口号来区分程序。服务器和客户端启动的时候,都会绑定一个端口号,假设服务器端口号为9090,客户端端口号为1234。当客户端给发送请求时,源端口就是1234,目的端口是9090;当服务器返回给客户端响应时,源端口是9090,目的端口就是1234。端口号在网路协议中,是使用两个字节表示的无符号整数。

            我们需要指定的是空闲端口,一个端口同一时刻,只能被一个端口绑定。如果在绑定的时候改端口被其他进程占用,就会绑定失败并抛出异常。要想知道哪些端口号被占用,我们既可以尝试绑定,也可以使用一些命令来查看。比如Windows中的cmd窗口中输入"netstat"命令。

    // 启动服务器,完成业务逻辑
    public void start() throws IOException {while (true) {// 1、读取请求并解析DatagramPacket reqPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(reqPacket);// 处理数据// 发送数据}
    }

            这里创建了一个DatagramPacket实例,它是UDP通信中用于接收或发送数据包的容器。当数据包到达receive时,它的内容会被存储在reqPacket的缓冲区中;同时,数据包的源地址和端口信息也会被存储在reqPacket对象中。

            如果服务器一启动,就会执行到receive,如果没有客户端发送任何请求,receive就会阻塞。

    DatagramPacket resPacket = new DatagramPacket(response.getBytes(), response.getBytes().length);
    socket.send(resPacket);

            将字符串response转换为字节数组。因为在网络传输中,数据是以字节形式传输的。还要获取获字节数组的长度,表示数据包的大小。send()方法将数据包通过网络发送出去。

            当进行到send()方法时,要把resPacket返回给谁?因为一个服务器对应多个客户端,原则上,谁发送的请求就得返回给谁。UDP socket里面是不保存对方的信息的。服务器收到客户端的请求,不光是一个UDP数据包,UDO外面有IP,IP外面有以太网数据帧,这是receive收到的完整数据报,虽然以太网报头和IP报头都会被操作系统解析出来,但仍然可以通过DatagramPacket获取到完整信息。

    // 在构造方法里面加上一个参数
    DatagramPacket resPacket = new DatagramPacket(response.getBytes(), response.getBytes().length, reqPacket.getSocketAddress());

            完整代码实现:

    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;public class EchoServer {// 创建Socket对象private DatagramSocket socket;public EchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}// 启动服务器,完成业务逻辑public void start() throws IOException {System.out.println("服务器启动");while (true) {// 1、读取请求并解析DatagramPacket reqPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(reqPacket);// 把DatagramPacket里面的数据解析成字符串String request = new String(reqPacket.getData(), 0, reqPacket.getLength());// 根据请求计算响应String response = process(request);// 把响应写回到客户端DatagramPacket resPacket = new DatagramPacket(response.getBytes(), response.getBytes().length, reqPacket.getSocketAddress());socket.send(resPacket);System.out.printf("[%s:%d] 请求: %s 响应: %s\n", reqPacket.getAddress(), reqPacket.getPort(), request, response);}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {EchoServer echoServer = new EchoServer(9090);echoServer.start();}
    }

            以上就是服务器的编写,下面要完成客户端的编写:

    import java.net.DatagramSocket;
    import java.net.SocketException;public class EchoClient {private DatagramSocket socket;private String severIP;private int serverPort;public EchoClient(String serverIP, int serverPort) throws SocketException {socket = new DatagramSocket();this.severIP = serverIP;this.serverPort = serverPort;}
    }

            客户端在创建socket对象的时候,不需要指定端口号,操作系统会自动分配一个端口。对于服务器来说,服务器在程序员手里,如果出现冲突会很容易处理;对于客户端来说,是在用户自己的电脑上,很可能指定的端口会与用户电脑上的其他进程产生冲突。

            完整代码实现:

    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.util.Scanner;public class EchoClient {private DatagramSocket socket;private String severIP;private int serverPort;public EchoClient(String serverIP, int serverPort) throws SocketException {socket = new DatagramSocket();this.severIP = serverIP;this.serverPort = serverPort;}public void start() throws IOException {Scanner in = new Scanner(System.in);System.out.println("客户端启动:");while (true) {// 1、从控制台输入用户读取的内容System.out.print(">");String request = in.nextLine();// 2、构造成UDP请求,并发送DatagramPacket reqPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(severIP), serverPort);socket.send(reqPacket);// 3、读取服务器的响应DatagramPacket resPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(resPacket);String response = new String(resPacket.getData(), 0, resPacket.getLength());// 4、把响应显示到控制台System.out.println(response);}}public static void main(String[] args) throws IOException {EchoClient client = new EchoClient("127.0.0.1", 9090);client.start();}
    }

            "127.0.0.1"是一个环回IP,当服务器和客户端在同一个主机上时,无论主机真实的IP是啥,都可以通过127.0.0.1访问。

            先运行服务器在运行客户端,运行结果如下:

            但这只是自己的主机上进行通信,我们还得进行跨主机通信。如果是上面这个服务器,只有在同一局域网连接下,才能实现别的主机连接。也可以把这个程序放在云服务器上,就可以不受同一局域网的限制了。

            上面的逻辑,通过多主机完成,本质上引入了更多的“硬件资源”,假如某个程序计算响应的过程非常复杂,非常消耗资源,那我们就可以交给算力更强的服务器来解决。

    2.3. 字典服务器

            编写一个英译汉的服务器,我们新创建一个DictServer类,直接继承EchoServer,里面的逻辑我们都可以直接拿过来用,只不过返回的请求需要修改,也就是需要重写process()方法。

    import java.io.IOException;
    import java.net.SocketException;
    import java.util.HashMap;public class DictServer extends EchoServer {private HashMap<String, String> dict = new HashMap<>();public DictServer(int port) throws SocketException {super(port);dict.put("apple", "苹果");dict.put("banana", "香蕉");dict.put("dog", "小狗");dict.put("cat", "小猫");dict.put("browser", "浏览器");}@Overridepublic String process(String request) {return dict.getOrDefault(request, "该单词没有查到");}public static void main(String[] args) throws IOException {DictServer dictServer = new DictServer(9090);dictServer.start();}
    }

            但是一运行,抛出了如下异常,因为启动了两个服务器,导致端口号被占用,只需要退出其中一个就可以。

    http://www.xdnf.cn/news/1307575.html

    相关文章:

  • 【Java学习】锁、线程死锁、线程安全2
  • 【C++】动态内存管理
  • 代码随想录Day52:图论(孤岛的总面积、沉没孤岛、水流问题、建造最大岛屿)
  • Ubuntu2204server系统安装后的初始化配置报错
  • ubuntu 20.04 安装anaconda以及安装spyder
  • GitHub PR 提交流程
  • 双向SSL认证之Apache实战配置
  • 从“Hello World”到“高并发中间件”:Go 语言 2025 系统学习路线图
  • 系统思考:情绪内耗与思维模式
  • linux服务器查看某个服务启动,运行的时间
  • DAY 46 通道注意力(SE注意力)
  • 【100页PPT】数字化转型某著名企业集团信息化顶层规划方案(附下载方式)
  • termios 线程 poll epoll进化 二叉AVL红黑树
  • 智能工厂生产监控大屏-vue纯前端静态页面练习
  • PowerShell 格式化系统完全掌握(下):自定义列/格式字符串/对齐与宽度 + 实战模板
  • System V通信机制
  • Docker之安装部署——(1)配置国内docker镜像源
  • 【Twincat3】IO的SCAN 不可选中,SCAN中后扫描不到设备
  • 代码随想录二刷之“字符串”~GO
  • 嵌入式开发学习———Linux环境下网络编程学习(二)
  • 科普:Pygame 中,`pg.Surface` v.s. `screen`
  • 电工的基础知识以及仪器的使用
  • 浏览器面试题及详细答案 88道(45-55)
  • 吉他和弦学习:从音程基石到流畅弹奏
  • 机器学习——PCA(主成分分析)降维
  • MySQL快速恢复数据的N种方案完全教程
  • JavaWeb开发_Day12
  • 云原生俱乐部-杂谈2
  • UI-TARS-Desktop 深度解析:下一代智能自动化桌面平台
  • 数据处理与统计分析 —— numpy入门