文章目录
- 前言
- 一、理论准备
- Socket套接字是什么
- UDP协议的特点
- 二、UDP数据报套接字 提供的API
- DatagramSocket API
- DatagramPacket API
- 三、代码实现请求响应式 客户端服务器
- 服务器
- 客户端
- 疑惑解答
- 为什么服务器进程需要手动指定端口号而客户端进程不需要
- 为什么用空的数据报接收网卡中的数据
- 为什么客户端中的服务器IP与端口号是"127.0.0.1" 与 9090
- 为什么客户端与服务器都没有调用close方法
- 总结
前言
本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!
今天分享的内容是UDP数据报套接字实现的客户端与服务器,一定要理解 DatagramSocket,DatagramPacket 这两个类的作用以及方法,十分有助于你理解服务器,客户端代码。
一、理论准备
Socket套接字是什么
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
程序猿👨💻编写网络程序,主要编写的是 应用层的程序代码 ,但是真正想要发送或接收数据,都是要 通过应用层调用传输层 。
因此传输层就为应用层(为我们编写代码)提供了一组api统称为
Socket api。
简单来说,这一组api是提供给咱们 编写网络程序使用的接口 , 用来发送 / 接收网络数据使用的接口 。
Socket套接字主要针对传输层协议划分为如下三类:
1️⃣ 数据报套接字:使用传输层UDP协议 (本文重点讲解)
2️⃣ 流套接字:使用传输层TCP协议 (下篇文章重点讲解)
3️⃣原始套接字(不做介绍)
UDP协议的特点
特点 | 说明 |
---|---|
无连接 | 不刻意保存对端的相关信息 |
不可靠传输 | 不关心对端是否收到数据,不关注传输数据是否成功 |
面向数据报 | 以一个UDP数据报为基本单位 |
大小受限 | 一次最多传输64k |
全双工 | 一条通信路径,双向通信。(可以同时发送和接收数据) |
二、UDP数据报套接字 提供的API
DatagramSocket API
DatagramSocket 是UDP Socket, 用于发送和接收UDP数据报 。
Socket对象可以理解为一个遥控器,指挥网卡设备实施各种操作
DatagramSocket构造方法 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
DatagramSocket(intport) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口号(intport)(一般用于服务端) |
DatagramSocket方法 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacketp) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramPacket API
DatagramPacket 是UDP Socket发送和接收的 数据报 。
DatagramPacket对象是 数据报
DatagramPacket构造方法 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
DatagramPacket(byte[]buf, int offset, int length,SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从offset(第二个参数offset)下标开始,数据报中的数据大小即共接收length个字节(第三个参数length)。address(第四个参数address)指定目的主机的IP和端口号 |
DatagramPacket方法 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
三、代码实现请求响应式 客户端服务器
服务器
// 服务器
public class UdpEchoServer {// 需要先定义一个socket对象// 网络通信,必须要使用socket对象private DatagramSocket socket = null;// 绑定一个端口号不一定会成功// 如果该端口号已被别的进程占用或绑定,此时的绑定操作就会抛出异常// 同一个主机,一个端口号,同一时刻,只能被一个进程绑定public UdpEchoServer(int port) throws SocketException {// 构造socket的同时指定要关联/绑定的端口号// socket对象==遥控器,与网卡进行交互的遥控器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);System.out.println("客户端上线");// 为了方便处理请求,将请求转换为字符串String request = new String(requestPacket.getData(),0,requestPacket.getLength());// 2.根据请求计算响应String response = process(request);// 3.把响应结果写回到客户端// 根据response字符串构造一个数据报// 和请求 packet 不同, 此处构造响应数据报的时候, 需要指定这个包要发给谁.DatagramPacket responsePacker = new DatagramPacket(response.getBytes(),response.getBytes().length,// requestPacket 是从客户端这里收来的. getSocketAddress 就会得到客户端的 ip 和 端口requestPacket.getSocketAddress());// 发送到客户端socket.send(responsePacker);// 分别打印:ip,端口号,请求内容,响应内容System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),requestPacket.getPort(), request, response);}}// 这个方法是根据请求计算响应// 有具体业务,就写相关逻辑// 这是服务器中的重要环节public String process(String request) {// 测试客户端与服务器是否网络通信成功// 并无逻辑需求// 直接将请求作为响应返回// 证明通信成功return request;}// 运行main方法,启动服务器public static void main(String[] args) throws IOException {// 实例化服务器对象UdpEchoServer udpEchoServer = new UdpEchoServer(9090);// 启动主逻辑udpEchoServer.start();}
}
客户端
// 客户端
public class UdpEchoClient {// 需要先定义一个socket对象// 网络通信,必须要使用socket对象private DatagramSocket socket = null;private String serverIP;// 服务器IPprivate int serverPort;// 服务器端口号public UdpEchoClient(String serverIP, int serverPort) throws SocketException {// 对于客户端来说, 不需要显示关联端口.// 不代表没有端口, 而是系统自动分配了个空闲的端口.this.socket = new DatagramSocket();this.serverIP = serverIP;this.serverPort = serverPort;}// 客户端主逻辑public void start() throws IOException {Scanner scanner = new Scanner(System.in);// 通过这个客户端可以多次和服务器进行交互.while (true) {// 1. 先从控制台, 读取一个字符串过来// 先打印一个提示符, 提示用户要输入内容System.out.print("-> ");String request = scanner.next();// 2. 把字符串构造成 UDP packet, 并进行发送.DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,// 数据报的目的IP与目的端口号InetAddress.getByName(serverIP),serverPort);// InetAddress.getByName这个静态方法可以将代表IP的字符串转换为IP// 向服务器发送数据报socket.send(requestPacket);// 3. 客户端尝试读取服务器返回的响应// 构造一个空的数据报DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);// 从网卡中读取数据存入 手动构造的空的数据报// 如果网卡中没有数据则会阻塞等待socket.receive(responsePacket);// 4. 把响应数据转换成 String 显示出来.String response = new String(responsePacket.getData(),0,responsePacket.getLength());System.out.printf("req: %s, resp: %s\n", request, response);}}// 运行main方法,启动客户端public static void main(String[] args) throws IOException {// 实例化客户端对象UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);// 启动客户端主逻辑udpEchoClient.start();}
}
通信结果:
疑惑解答
为什么服务器进程需要手动指定端口号而客户端进程不需要
服务器的功能是用来处理其他客户端发来的请求,因此需要为客户端提供自己的端口号,方便客户端进行访问。
虽然服务器要给客户端一个响应,但是客户端的IP地址与端口号都可以在客户端发来请求的数据报中获得,因此客户端不需要手动指定端口号
为什么用空的数据报接收网卡中的数据
一般情况下,一般是使用 参数 作为 “输入”,用返回值作为方法的 “输出”。
而用来读取数据的reveve方法却没有返回值。
因此需要传入一个引用类型的参数,通过这个方法改变这个参数,此时这个参数就是返回值,这个参数也被称为 输出型参数 。
因此这个方法就是 以参数为返回值 。
为什么客户端中的服务器IP与端口号是"127.0.0.1" 与 9090
127.0.0.1 是主机环回地址。主机环回是指地址为 127.0.0.1 的任何数据包都不应该离开计算机(主机),发送它——而不是被发送到本地网络或互联网,它只是被自己“环回”,并且发送数据包的计算机成为接收者。
端口号是9090是因为是随意指定的,当然也有一些特殊端口号被指定分配给了一些牛逼的程序。
为什么客户端与服务器都没有调用close方法
DatagramSocket对象也会产生文件描述符,如果如果文件描述符表满了会产生文件资源泄露的严重bug,那么为什么此处没有调用close方法???
原因是,无论客户端进程还是服务器进程, 在他的生命周期中,只需要用到一个DatagramSocket对象 ,而他需要调用close方法的时候也正是他进程结束的时候,此时会自动释放对象也就相当于调用了close方法,因此可以手动调用close方法,但是也可以不调用。
总结
以上就是今天要分享的内容,本文介绍了Socket套接字,以及使用UDP协议的特点以及UDP数据报套接字实现的客户端与服务器。网络编程让我愈发感觉到了编程的魅力,也让我领略到了科技的神奇。各位加油!
路漫漫不止修身,也养性。