🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(94平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- [1. 网络编程中的基本概念](#1. 网络编程中的基本概念)
-
- [1.1 请求与响应](#1.1 请求与响应)
- [1.2 服务器与客户端](#1.2 服务器与客户端)
- [2. Socket套接字](#2. Socket套接字)
-
- [2.1 概念](#2.1 概念)
- [2.2 分类](#2.2 分类)
- [3. UDP协议套接字编程](#3. UDP协议套接字编程)
-
- [3.1 API介绍](#3.1 API介绍)
-
- [3.1.1 DatagramSocket](#3.1.1 DatagramSocket)
- [3.1.2 DatagramPacket](#3.1.2 DatagramPacket)
- [3.2 代码示例](#3.2 代码示例)
-
- [3.2.1 回显服务器](#3.2.1 回显服务器)
- [3.2.2 回显客户端](#3.2.2 回显客户端)
- [3.2.3 字典服务器(继承于回显服务器)](#3.2.3 字典服务器(继承于回显服务器))
1. 网络编程中的基本概念
1.1 请求与响应
之前在MySQL章节中提到过,不在赘述.
https://blog.csdn.net/2301_80050796/article/details/137350014
1.2 服务器与客户端
之前在MySQL章节中提到过,不在赘述.
https://blog.csdn.net/2301_80050796/article/details/137350014
2. Socket套接字
2.1 概念
Socket套接字,是由操作系统提供的用于网络编程的API,也称为Socket API,只不过Java对这种网络编程Socket套接字封装为了一个一个的类.使得Java程序员可以方便地直接调用,不必单行不同系统所提供的API不同.
2.2 分类
Socket套接字主要分为如下的两类:(实际上是三类,只不过第三类的实用性不强,我们就不在这里叙述)
- 流套接字:使用传输层的TCP协议
TCP协议有以下的特点:
-
有连接
-
可靠传输
-
面向字节流
-
全双工
- 数据报套接字:使用传输层UDP协议
UDP协议有以下的特点:
-
无连接
-
不可靠传输
-
面向数据报
-
全双工
接下来我们对上面的一些专业术语进行一定地解释:
- 有连接 vs 无连接
首先什么叫连接?就是要建立联系的双方各自保存自己的信息.这是一种抽象的连接,并不是真的有一根什么绳子一类的东西,把它们两个绑在一起.所谓断开连接就是双方把建立连接的对方的信息都抹除掉.
举例说明:结婚和离婚
所谓结婚,就是一种抽象的连接,结婚的时候,两个人一定会有结婚证,结婚证上写的就是xxx是xxx的丈夫,xxx是xxx的妻子.这样就是在各自的结婚证上登记着对方的名字 .离婚就是双方在离婚之后把双方结婚证上的信息都摸除掉.
之后什么叫有连接 :还是遵循上面连接的定义,就是双方都要保存对方的信息,才可以进行通信.比如我们打电话,我们先要拨号,等待对方接通之后才可以通话,对方可以接受,也可以拒绝 .
什么又是无连接 :就是双方不会保存对方的信息,就可以进行通信.比如我们发微信,只要发送的一方点击发送按钮的时候,信息就会发送,对方只能接受,不能拒绝.
- 可靠传输 vs 不可靠传输
可靠传输 :要传输的数据,尽可能传输给对方,只是尽可能,不是100%.这种方式可以对抗网络丢包.
什么是网络丢包?
在数据传输的过程中,数据要经过好几层路由器和交换机之间的转发之后,才可以到达目的地,在转发的时候,可能会遇到路由器或者交换机繁忙的时候,这时候就有可能造成一部分的数据丢失,就叫做网络丢包.
不可靠传输 :传输数据的时候,不关心对方是否收到数据,也不管对方收到的数据是否完整,只要把数据发送出去就算完事.
虽然说,TCP协议的可靠传输可以对抗网络丢包,但是也会有一定的代价,这就使得UDP协议中的不可靠传输要比TCP协议中的可靠传输速度快.
- 面向字节流 vs 面向数据报流
这里与文件里的字节流和字符流非常相似.
面向字节流 ,就是传输数据的时候,使用的是字节为单位进行传输 .
面相数据报流 ,就是传输数据的时候,使用的是整个数据报为单位进行传输 ,每次传输的时候,只能传输一个完整的数据报,不可以传输半个,或者1.5个.
UDP数据报传输我们在上一篇文章的末尾中也叙述过.
https://blog.csdn.net/2301_80050796/article/details/138972120
- 全双工
两种协议的传输方式都是全双工的传输特点.通俗一点来讲,就是一条通行链路上,可以进行双向通信,既可以接收,也可以发送.
与全双工相对的是半双工.半双工就是一条通信链路上,只可以进行单线的通信,要不只能接收,要不只能发送.
3. UDP协议套接字编程
3.1 API介绍
3.1.1 DatagramSocket
- 概念
这是一个类,这个类其实就是对操作系统中Socket概念的封装,可以理解为一种文件,这种文件就是对网卡这个硬件设备的抽象表现 ,针对Socket文件进行读写,就相当于对网卡进行读写.总的来说,在对网卡进行读写的时候,就是DatagramSocket这个类发送和接收UDP数据报,Java不是直接对网卡进行操作,而是针对这个类进行操作从而间接操作网卡,类似与网卡的"遥控器".
具有像上述DatagramSocket这个类类似于"遥控器"这样作用的概念,我们把他叫做"句柄"(handle).
- 构造方法
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端口(⼀般⽤于客户端) |
DatagramSocket(int port) | 创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端口(⼀般⽤于服务端) |
- DatagramSocket成员方法
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该⽅法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
每次发送或者接收,就传输了一个数据报,就是我们下面要讲的DatagramPacket
.
3.1.2 DatagramPacket
DatagramPacket就是UDPSocket每次发送和接收的数据报.
- 构造方法
方法签名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf,int length) | 构造⼀个DatagramPacket以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length) |
DatagramPacket(byte[] buf,int offset,int length,SocketAddress address) | 构造⼀个DatagramPacket以⽤来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定⻓度(第⼆个参数length).address指定⽬的主机的IP和端⼝号 |
- 成员方法
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端⼝号;或从发送的数据报中,获取接收端主机端⼝号 |
byte[] getData() | 获取数据报中的数据 |
InetSocketAddress getSocketAddress() | 从接收的数据报中获取发送端的IP地址和端口号,经常和第二个构造方法配合使用 |
int getLength() | 获取数据报的长度 |
3.2 代码示例
3.2.1 回显服务器
java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket datagramSocket = null;
//传入端口号,构造句柄
public UdpEchoServer(int port) throws SocketException {
this.datagramSocket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
DatagramPacket request = new DatagramPacket(new byte[4090],4090);
//利用数据报获取请求
datagramSocket.receive(request);
//拆解数据报,获取其中的请求字符串
String s = new String(request.getData(),0,request.getLength());
//对数据进行处理
String s1 = process(s);
//把返回的响应字符串通过传输层封装成数据报,并指定返回响应的IP地址和端口号
DatagramPacket response = new DatagramPacket(s1.getBytes(),0,s1.getBytes().length
,request.getSocketAddress());
//发送响应
datagramSocket.send(response);
//打印日志
System.out.printf("[%s,%d]:request = %s,response = %s\n",request.getAddress(),request.getPort(),
s,s1);
}
}
//回显服务器,直接返回请求
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(4000);
udpEchoServer.start();
}
}
[解释]
- 上面获取请求的方式和前面文件的输入流获取字符或字节流的方式相同,要提前准备一个空的数据报,之后利用这个数据报去获取请求.UDP数据报中包含报头和载荷,报头就是类的属性 ,载荷就是传递的数据.
- 如果想要把上述的代码理解的更形象,可以结合我们之前的TCP/IP五层协议的数据报封装分用过程来理解.
- 这里的回显指的是,请求是什么,响应就是什么.
- 我们指定端口号的时候有两个条件,第一,必须在1~65535之间,第二,不可以和其他进程的端口号重复.
- 服务器程序在启动的时候,必须把端口号定下来 ,因为客户端要知道服务器的位置,此时由于客户端和服务器的IP地址相同,所以我们重点关注端口号,一个主机上,有多个进程要进行网络通信,就要把进程使用的是哪个端口号给明确下来.
- 使用死循环不停地接收请求,服务器需要7*24小树不停地工作.
- request.getData()的时候就相当于Java应用层拆解掉了UDP的报头.
- 在send之前创建数据报的时候,就相当于给String对象加上了报头.
- 注意在发送的时候,要通过之前的数据报获取到发送端的IP地址和端口号.
3.2.2 回显客户端
java
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 UdpEchoClient {
private DatagramSocket socket = null;
private int port = 0;
private String IP = null;
public UdpEchoClient(String IP,int port) throws SocketException {
this.socket = new DatagramSocket();//让系统随机给客户端分配端口号
this.port = port;
this.IP = IP;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("客户端启动");
String s = scanner.next();
//构造数据报,并指定发送到服务器的IP地址和端口号
DatagramPacket request = new DatagramPacket(s.getBytes(),0,s.getBytes().length
, InetAddress.getByName(IP),port);
//发送请求
socket.send(request);
//接收返回的响应
DatagramPacket response = new DatagramPacket(new byte[4090],4090);
socket.receive(response);
String s1 = new String(response.getData(),0,response.getLength());
//打印响应
System.out.println(s1);
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",4000);
udpEchoClient.start();
}
}
运行结果:
3.2.3 字典服务器(继承于回显服务器)
这里就是模拟一个查字典的过程
- 我们需要有一个HashMap来保存词库中的单词
- 其次重写process方法来返回翻译后的意思
代码实例:
java
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
public class UdpDictServer extends UdpEchoServer{
private HashMap<String,String> hashMap = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
hashMap.put("cat","猫");
hashMap.put("dog","狗");
hashMap.put("pig","猪");
hashMap.put("cow","牛");
}
@Override
public String process(String request) {
return hashMap.getOrDefault(request,"没有找到该词");
}
public static void main(String[] args) throws IOException {
UdpDictServer udpDictServer = new UdpDictServer(4000);
udpDictServer.start();
}
}
运行结果: