Socket 套接字可以理解为是操作系统提供给程序员的一组用于网络编程的API (接口)——传输层的接口,传输层给应用层提供的一组 API,统称为 Socket API 。网络通信的底层逻辑都已经被操作系统封装好了,开发人员就可以根据接口开发实现网络通信。
Socket 套接字主要针对传输层协议分为如下三类:
- 字节流套接字:使用传输层TCP协议
- 数据报套接字:使用传输层UDP协议
- 原始套接字:用于自定义传输层协议
本篇博客主要讲述面向数据报的网络编程。
一、Java基于UDP数据报套接字通信模型
UDP 协议的特点:
- 面向无连接:传输数据之前,通信双方不依赖于建立连接,只需要知道谁发给谁即可。
- 不可靠传输:只负责发送,不关注数据是否传输成功,即使没发送成功啥也不干,也没有反馈
- 面向数据报:使用 UDP 数据报的形式传输,数据报可以理解为数据是一块一块的传输。编辑
- 有缓存区的概念:接收缓冲区,发送缓冲区:在进行网络数据通信时,用于存储待发送或已接收数据的缓冲区。
- 数据传输大小受限制:一个UDP 数据报最大占 64k, 1k = 1024字节
- 全双工通信:通信双方都可以同时进行信息交互
总结:UDP 协议,具有面向无连接,面向数据报特点,即使通信双方没有建立连接,也会传输数据,并且一次性发送全部的数据报,一次性接受全部的数据报。
Java 中基于UDP协议通信,主要使用 DatagramSocket 类来创建数据报套接字,并使用
DatagramPacket 作为发送或接收的UDP数据报。
针对一个客户端对服务器发出一次请求,服务器针对该请求给予响应流程如下:
编辑在真实的网络环境中,一个服务器往往会给多个客户端提供响应。
举个实际的例子根据以上流程图理解一下,张三老铁使用手机百度浏览器搜索 “美女图片”,此时前端页面就会将这条搜索记录 “美女图片” 拿到后台打包成UDP 数据报,以请求的形式发给百度服务器,百度服务器拿到请求后,解析数据报查看:哦~ 要美女啊,我这有很多,于是将“本地”存储的美女图片作为 UDP 数据报的形式响应给请求端,百度浏览器拿到百度服务器响应的数据后,通过解析,将图片展示在张三的手机界面上。
二、UDP 数据报套接字编程
2.1 DatagramSocket API
DatagramSocket 是Socket 套接字基于UDP 协议来发送和接受数据报的类.
Datagram : 数据报
Socket : 说明这个对象是一个 Socket 对象
Socket 对象相当于操作系统一个特殊的文件,这个文件并非对应对应的硬盘上的某个数据存储区域,而是对应到,网卡这个硬件!!!
往 socket 对象中写数据,相当于通过网卡发送消息
从 socket 对象中读数据,相当于通过网卡接收消息
所以想要进行网络编程使得两个设备相互通信,就需要有 socket 文件这样的对象,借助这个 socket 文件对象来间接的操作网卡,操作系统基本设计思想:一切皆文件,为了简化系统内核的逻辑设计,socket 对象面向字节流读写。
编辑
此处Socket 对象可以被客户端 / 服务端使用,服务器这边的 Socket 必须要关联一个具体的端口号,每个联网的设备在启动时或随机或指定都会绑定一个端口号,作为该应用程序的唯一标识,就相当于数据被你的设备接收,经解析需要知道这些数据需要交给那个应用程序处理。例如,qq 、微信没见过消息发串了的情况吧。
客户端这边可以不需要手动指定,系统会自动随机分配,端口号的取值范围是 [0, 65535]。
小于等于1024 的端口号,会提供给知名的服务器使用,不建议使用这一类。
编辑
socket 也是文件,用完了要关闭资源。
2.2 DatagramPacket API
DatagramPacket 类是用于基于UDP 协议的Socket 发送和接收的数据报。
编辑
只有两个参数的版本,不需要设计地址,通常用来接收消息。另一个多参数的版本需要显式的设置地址进去,通常要用来发送消息。
编辑
构造UDP 发送的数据报时,需要传入 socketAddress对象,这个对象可以使用 InetSocketAddress 来创建 。
InetSocketAddress API 的构造方法:
编辑
三、基于UDP Socket 实现客户端,服务器程序
分析客户端的程序的功能:
1.发出请求(消息)
2. 等待服务器响应(回应)
3. 解析服务器的响应
分析服务端的程序的功能:
1. 尝试读取客户端的请求并解析
2. 根据客户端的请求,统筹响应的数据
3. 把响应返回到客户端
3.1 服务端程序设计
首先我们定义一个服务端的类:UdpEchoServer
定义一个 Socket 对象文件,与网卡交互。
//需要线定义一个 socket 对象 //通过网络通信,必须要使用 socket 对象 private DatagramSocket socket = null;
利用 UdpEchoServer类 的构造方法为 socket 构造,并指定需要绑定的端口号
public UdpEchoServer(int serverPort) throws SocketException { //构造 socket 的同时,指定要的关联/ 绑定的端口 socket = new DatagramSocket(serverPort); }
绑定一个端口,不一定能成功,如果某个端口已经被别的进程占用了,此时这里的绑定操作就会出错,同时一个主机上,一个端口,同一时刻,只能被一个进程绑定。所以这里会抛出异常。
定义一个启动方法:start()
方法内部采用循环的方式,每次循环,做三件事:
1. 读取请求并解析,2. 根据请求计算响应,3. 把响应的结果写回客服端
public void start() throws IOException { System.out.println("服务器启动!"); while (true) { // 每次循环,要做三件事情: // 1. 读取请求并解析 // 构造接受请求的“空盒子”,指定盒子的大小 4096 字节 DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); // 数据从网卡上来的 socket.receive(requestPacket);// 获取客户端的请求、 //为了方便处理这个请求,把二进制数据转换成 String String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); // 2. 根据请求计算响应 调用方法 String response = process(request); /* 3. 把响应结果写回客服端 根据response 字符串,构造一个 DatagramPacket 和请求 packet 不同,此处构造响应的时候,需要指定这个包要发给谁 */ DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); // response.getBytes() 转换为 byte二进制流 // requestPacket 是从客户端这里收来的,getSocketAddress 就会得到客户端的 IP 和 端口 socket.send(responsePacket);// 响应 // 可以保存以下日志 String now3 = df.format(System.currentTimeMillis()); // 返回当前系统时间 try(Writer writer = new FileWriter("outPut.txt", true)) { writer.write(requestPacket.getAddress().toString() + requestPacket.getPort() + " " + now3 + " req: " + request + " resp: " + response + "\n"); } // 打印一下本次客户端与服务端的交互 System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response); } }
public static void main(String[] args) throws IOException { UdpEchoServer udpEchoServer = new UdpEchoServer(9090); udpEchoServer.start(); }
2. 根据客户端的请求,统筹响应的数据,我们将第二步计算响应以函数的方式封装实现,参数 request 请求我们已经转换为字符串了,所以我们就可以依据请求做出对应的处理了,如果是需要搜索什么,就可以根据请求的内容将服务器的中存储的被搜索内容作为响应返回给客户端,如果是互相发消息,那就需要根据请求判断需要发给谁,此时需要双方约定在请求中添加接收方的IP 和 端口,这样才能找到接收方,作为服务器端来讲就需要使用多线程的思想,每一个客户端发来请求都会分配一个线程来处理。
public String process(String request) { //这里直接将客户端的请求,作为响应返回 return "服务器响应:" + request; }
这个部分可以依据客户端的请求,进行对应的业务处理。
3.2 客户端程序设计
首先定义一个客户端的类:UdpEchoClient
定义一个 Socket 对象文件,与网卡交互。
定义 serverIP 的成员变量,用于存储服务器端的IP 地址
定义 serverPort 的成员变量,用于存储服务器的端口号
private DatagramSocket socket = null; private String serverIP = null; // IP private int serverPort = 0; // 端口号 —— 代表进程的标识
利用 UdpEchoClient 类 的构造方法为 socket 构造,并为成员方法关联服务器的 IP 地址和端口
//客户端启动,需要知道服务器在哪儿!! public UdpEchoClient(String serverIP, int serverPort) throws SocketException { //对于客户端来说,不需要显示关联端口 // 不代表没有端口,而是系统自动分配了个空闲的端口 socket = new DatagramSocket(); //获取随机端口号 this.serverIP = serverIP; this.serverPort = serverPort; }
定义一个启动方法:start()
方法内部采用循环的方式,每次循环,做4件事:
1. 从控制台获取用户输入的信息
2. 将获取的信息构造成 UDP 数据报,并向服务器发送请求
3. 客户端尝试获取读取服务器返回的响应
4. 将获取的的响应根据业务需求做进一步的处理
public void start() throws IOException { // 通过这个客户端可以多次和服务器进行交互 System.out.println("客户端启动!"); Scanner in = new Scanner(System.in); while (true) { //1.先从控制台,读取一个字符串过来 //先打印一个提示符,提示用户要输入内容 System.out.print("->"); String request = in.next(); //2. 把字符串构造成 UDP packet, 并进行发送 DatagramPacket requestPacket = new DatagramPacket( request.getBytes(), request.getBytes().length, //----如果参数为null,获得的是本机的IP地址 InetAddress.getByName(serverIP),// 确定主机的 IP 地址----在给定主机名的情况下确定主机的IP地址 serverPort); socket.send(requestPacket); //响应 //3. 客户端尝试读取服务器返回的响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096); socket.receive(responsePacket); //4. 把响应的数据转换成 String 显示出来,利用 String 的构造方法转换 String response = new String(responsePacket.getData(), 0, responsePacket.getLength()); System.out.printf("req: %s, resp: %s\n", request, response); } }
public static void main(String[] args) throws IOException { UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090); udpEchoClient.start(); // 启动客户端 }
3.3 总结:
以上客户端服务器代码使用了 Socket “ 文件流”, 但是没有调用 close() 方法释放资源,是因为文件流会随着进程的关闭而关闭,但是在实际开发中一定要记得随手关闭哦~
启动客户端和服务器展示效果:
编辑
编辑
编辑
以上操作流程执行图。编辑
一定是先启动服务器,再启动客户端,
UDP(User Datagram Protocol)是一个无连接的传输层协议,它不保证数据包可靠传输。UDP比TCP更快,而且在对网络延迟要求较低、对数据准确性要求不高的情况下使用。本文将讨论基于UDP Socket的客户端服务器开发总结。
UDP Socket
Socket是一种用于在计算机之间进行通信的 API(应用程序接口)。UDP Socket是基于UDP协议进行通信的套接字,它提供了一种快速发送和接收数据包的方法,但是与TCP不同,它不会重复数据或者检查是否有缺失的数据包。
编写UDP客户端的基本步骤如下:
- 创建一个UDP socket。
- 将要发送的数据打包到UDP数据包中。
- 使用UDP socket发送数据包到特定的IP地址和端口号。
编写UDP服务器的基本步骤如下:
- 创建一个UDP socket并监听特定的端口号。
- 接收客户端发送的数据包。
- 处理数据包,然后将响应打包到另一个UDP数据包中。
- 使用UDP socket向客户端发送响应数据包。
总结:
在使用UDP Socket进行客户端和服务器开发时,需要注意以下几点:
- UDP不会保证数据包的可靠传输,因此需要在应用程序中处理数据包的丢失或者损坏情况。
- 由于UDP没有连接状态,因此可以同时与多个客户端通信。
- 在UDP通信中,需要指定目标地址和端口号。