文章目录
1.数据报套接字(UDP)
1.1特点
- 无连接:数据在传输前无需建立专用通道,每一个数据包独立发送,并自行选择路径到达目的地,好比发送信息,你只要发送了信息就行,不需要对方接收。
- 不可靠传输:在传输数据的时候,不需要关注对方是否收到了,发送了就可以。
- 面向数据流:传输数据的基本单位是一个个的UDP数据报,一次读写,只能读写一个完整的UDP数据报
- 全双工:一条链路中,能够进行双向通信。
1.2编码
Socket api都是系统提供的,不同的系统提供的api是不一样的,但是java中对于系统的这些api进一步封装了。
UDP中的socket api 重点是两个类:DatagramSocket 和DatagramPacket
1.2.1DatagramSocket
这个类的作用可以看作"操作网卡"的遥控器,也就是对网卡进行读写操作,可以理解成像文件那样读写的形式。
提供了几个方法:
1.2.2DatagramPacket
这个类是描述UDP数据报,一个DatagramPacket对象相当于一个UDP数据报,一次发送一次接收就是传输了一个DatagramPacket对象。
1.2.3实现一个UDP(24*7)回显服务器
回显(Echo):客户端给服务器发送不同的请求,服务器就会返回不同的响应。但在此处回显就是客户端给服务器请求啥,服务器就响应啥,不做任何计算和业务逻辑。
java
public class udpEchoServer {
public DatagramSocket socket = null;
public udpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!!!");
while (true) {
//接受客户端请求
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//为了更好处理数据并方便打印,将接受的数据转化为字符串操作
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
//处理客户端的请求(算术计算或者业务逻辑)
String response = this.process(request);
//将处理完的请求返还给客户端
DatagramPacket resposePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(resposePacket);
//打印客户端的ip地址端口号和接收客户端的数据以及处理完客户端后的数据
System.out.printf("[%s:%d] req = %s,resp = %s\n",requestPacket.getAddress(),requestPacket.getPort(),
request,response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
udpEchoServer udpEchoServer = new udpEchoServer(9999);
udpEchoServer.start();
}
}
DatagramPacket对象表示的就是一个数据报,UDP数据报是由报头和载荷组成的,报头中的有IP地址和端口号这些都是DatagramPacket类的属性,而载荷这个类没有提供,所以需要程序猿自己去提供,所以就像和尚化缘一样,你需要自己提供一个化缘的碗,然后各家将食物放到碗中,由于数据报的本质就是一个二进制数据,所以我提供了一个字节数组来作为这个载荷存储数据。
从客户端接收过来的数据,经过服务器处理完之后要返还给客户端,所以需要在创建一个DatagramPacket对象来存储由服务器处理完之后的数据,再服务器发送给客户端。此时的DatagramPacket对象就不需要提供一个空白的空间,将处理完的数据放入这个对象就行,由于在接收数据的时候就记录了客户端的ip地址和端口号,所以在发送给客户端的时候只需要通过requestPacket.getSocketAddress()来获取客户端的IP地址和端口号。
由于服务器是固定的,所以可以直接自己去设置端口号,当服务器启动的时候端口号就被创建了。
为什么需要端口号IP地址?
要实现双方通信成功,必须要有这个四个核心指标:
源端口,源IP,目的端口,目的IP
通过这四个核心指标就可以实现双方网络通信,此处没有服务器的IP地址,是因为客户端和服务器在同一个主机上。我们可以用一个IP来表示172.0.0.1也称环回IP。
1.2.4实现一个UDP客户端
java
public class udpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverProt;
public udpEchoClient(String serverIp, int serverProt) throws SocketException {
socket = new DatagramSocket();
this.serverIp = serverIp;
this.serverProt = serverProt;
}
public void start() throws IOException {
System.out.println("客户端启动!");
//1.从控制台获取字符串
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("请输入要发送的请求: ");
String request = scanner.nextLine();
//2.将输入的字符串发送给服务器,还要将发送的目标确定,IP和端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), 0, request.getBytes().length,
InetAddress.getByName(serverIp), serverProt);
socket.send(requestPacket);
//3.从服务器读取到经过处理的响应
DatagramPacket responsePackt = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePackt);
//将响应打印在控制台
String response = new String(responsePackt.getData(), 0, responsePackt.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
udpEchoClient udpEchoClient = new udpEchoClient("127.0.0.1",9999);
udpEchoClient.start();
}
}
从控制台接收到的字符串,requestPacket对象获取到字符串对象的引用以及长度,还有自己的IP和Prot,然后通过socket的send方法发送到服务器上。
从服务器接收的响应,需要创建一个responsePackt对象,并且提前创建好一个空白的字节数组来接收从服务器接收的响应。
对一些方法区分一下:
在对方法区分前,我们需要理解字节,字符,编码形式的概念
字节与字符本质上都是二进制,就是0101这种数据,因为计算机只认识0101。
字节:字节是计算机存储数据的基本单位,1个字节就是8个bit,每个位可以是0或1,表示一个字节可以表示256个不同的值.
字符:字符是表示文本信息的基本单位,根据不同的编码形式,就会有一个字符对应多少个字节就有所不同,通过编码形式可以将字符转化为字节再通过字节来表示数据,传输数据和存储数据。
编码形式:
- ASCII码:用于表示英文字符。它使用7位来表示128个字符(包括大小写字母、数字、标点符号和一些控制字符),就是不支持中文字符,这里就是一个字符对应一个字节
- Unicode(UTF8):UTF-8是一种变长的编码形式,包括全球所有书写系统的字符,兼容ASCII字符集,并且支持中文字符,包括全球所有书写系统的字符,一个字符对应1-4字节。
- GBK:采用变长编码,主要用于中文字符编码,一个字符对应1-2字节
方法区别一下:
- getBytes():得到字节数组的引用,对象是字符,这里就是将字符转化为字节来存储数据
- getBytes().length:得到字节数组的长度,对象是字符
- getData():得到的是由字符组成的字符串的引用,对象是字节,这里就是将字节转化为字符来使用数据
- getLength():得到的字符串的长度,对象是字节
- getAddress():获取IP地址,对象是DatagramPacket
- getPort():获取Prot端口号,对象是DatagramPacket
- getSocketAddress():获取到IP地址和Prot端口号,对象是DatagramPacket
- InetAddress.getByName():这个方法可以解析主机名并返回该主机名的 IP 地址,或者直接返回给定 IP 地址的 InetAddress 对象,得到的InetAddress 对象传给服务器,服务器通过得到这个对象就可以解析出IP地址。
1.2.5实现一个UDP字典翻译服务器
这里我们可以应用继承与多态的思想来实现这个服务器,因为这个字典翻译服务器与回显服务器本质的区别就是对响应的处理,所以我们对重复的代码继承复用,扩展需要的代码即可。
java
public class udpDictServer extends udpEchoServer{
HashMap<String,String> map = null;
public udpDictServer(int port) throws SocketException {
super(port);
map = new HashMap<>();
map.put("server","服务");
map.put("client","客户端");
map.put("cat","🐱");
map.put("dog","🐕");
map.put("pig","🐖");
}
@Override
public String process(String request) {
return map.getOrDefault(request, "该词汇没有查询到");
}
public static void main(String[] args) throws IOException {
udpDictServer udpDictServer = new udpDictServer(9999);
udpDictServer.start();
}
}
实现这个字典翻译,还需要数据结构中的map,利用map中的键值对来存储数据。
然后具体的业务操作就只需要重写process方法即可。
在执行到这里的时候虽然这个this指的是父类的udpEchoServer对象,但是由于udpDictServer子类继承了udpEchoServer父类有重写了process方法,由于多态的特性执行的process方法其实是子类的process方法。
2.客户端与服务器的流程
- 从控制台获取数据
- 客户端将数据发送到服务器
- 服务器接收客户端发送过来的数据
- 计算响应
- 服务器将计算好的响应发送给客户端
- 客户端接收服务器发送的响应
- 将从客户端接收服务器发送的响应打印在控制台