使用 JAVA 自带的api
目录
一、回显服务器 UdpEchoServer
服务器代码
客户端代码
二、翻译功能 UdpDictServer
在UdpDictServer里重写process方法
一、回显服务器 UdpEchoServer
/** * 回显服务器 * 写一个简单的UDP的客户端/服务器 通信的程序 * 这个程序没有啥业务逻辑,只是单纯的调用socket api * 让客户端给服务器发送一个请求,从控制台输入的字符串 * 服务器收到字符串后,会把这个字符串原封不动的返回给客户端,客户端再显示出来 运行过程 * 1.先启动服务器 ,程序会进入while循环,执行到receive()阻塞,等待客户端的请求 * 2.再启动客户端,程序也会进入while循环,执行到scanner 等待用户输入 * 当用户输入字符串之后,next就会返回,构造请求数据,并发送给服务器 * 3.客户端发送数据之后 * 服务器就会从receive中返回,进一步执行解析请求UDP报,执行process计算响应操作,最后执行send操作,发送响应结果 * 客户端接着执行到receive,等待服务器的响应 * 4.客户端接收到服务器响应的数据之后,从receive中返回,执行打印操作,把响应的内容显示出来了 * 5. 服务器执行完后,重新进入while循环,又执行到receive这里阻塞 * 客户端执行完后,也重新进入while循环,执行到scanner这里阻塞 * */
服务器代码
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//创建一个DatagramSocket对象,后续操作网卡的基础
//socket 就是把网卡抽象成了文件
//往socket文件中 写数据 ,就相当于通过网卡 发送数据
//从socket文件中 读数据 ,就相当于通过网卡 接收数据
//把网络通信和文件操作给统一了
//在Java中就使用这个类,来表示系统内部的 socket 文件了
//常用的socket的方法:receive(DatagramPacket P) ,send(DatagramPacket P), close
//DatagramPacket 这个类,用来表示一个UDP数据报
//UDP是面向数据报的
//每次进行传输,都要以UDP数据包为基本单位
private DatagramSocket socket = null;
//构造方法
//服务器需要手动 显示指定端口号,可以避免端口号冲突
//客户端的socket 一般不能显示指定(系统会随机分配一个端口)
//传入参数为 int port
public UdpEchoServer(int port) throws SocketException {
//手动指定端口号
socket = new DatagramSocket(port);
//不带参数就是系统自动分配端口号
// socket = new DatagramSocket();
}
//服务器启动方法
//通过这个方法来启动服务器
public void start() throws IOException {
//服务器启动提示
System.out.println("服务器启动!");
//服务器一般开了就不关了,7*24小时不停歇运转,所以是while(true)形式
while(true){
//1.读取请求并解析
//创建UDP数据报 DatagramPacket对象 接收 客户端的数据
//收到的数据需要用内存空间保存,而DatagramPacket 内部不能自行分配内存空间
//于是需要程序员手动把空间创建好,交给DatagramPacket 进行处理,使用字节数组创建
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//保存请求信息
socket.receive(requestPacket);//接收请求信息。数据没来的时候,会先阻塞,直到客户端把请求发来为止
//接收到的数据,是以二进制的形式存储到DatagramPacket中了
//要想显示出来,需要将这个二进制数据转换成字符串
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
//参数为 二进制数据,偏移量从0开始到requestPacket.getLength()这个数据结尾
//2.根据请求计算响应
//实际开发项目中,服务器中最核心的步骤,绝大数程序都是在完成这一部分
String response = process(request);
//3.把响应写回到客户端
// 创建响应对象,DatagramPacket
// 往这个对象里构造刚才的数据,再通过send返回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
//传入的参数 获取 数据的起始地址response.getBytes(),数据的字节长度response.getBytes().length,响应给 客户端的ip地址requestPacket.getSocketAddress()
//指定字节数组缓冲区,同时指定 客户端UDP请求报中的网卡信息requestPacket.getSocketAddress()(其中包含了ip和端口号)
//网络传输,使用字节,以字节为单位
socket.send(responsePacket);
//4.打印一个日志,把这次数据交互的详情打印出来
System.out.printf("[%s:%d] request=%s response=%s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
//打印的内容:客户端的ip:客户端的端口号 请求内容 响应内容
}
}
//响应程序
//这里只是简单回显
//这里写public才能被重写
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
客户端代码
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;//对网卡进行操作,接收或发送数据报,或者关闭
//因为UDP报不会建立链接,也不会存储对端的信息
//所以在应用程序里把它存储起来
private String serverIp = "";
private int serverPort = 0;
public UdpEchoClient(String ip , int port) throws SocketException {
//客户端不能指定端口号码,会端口冲突
socket = new DatagramSocket();//客户端 系统随机分配端口,不写参数就是系统随机分配
//因为UDP报不会建立链接,也不会存储对端的信息
//所以在应用程序里把它存储起来
//这里主要记录对端的 ip 和 端口
serverIp = ip;
serverPort = port;
}
//启动客户端程序
public void start() throws IOException {
//启动客户端提示
System.out.println("客户端启动!");
//用户输入字符串
Scanner scanner = new Scanner(System.in);
while(true){
//1.从控制台读取数据,作为请求
System.out.print("->");//输入提示符
String request = scanner.next();//请求信息的内容
//2.把请求内容构造成一个DatagramPacket对象,发送给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
//request.getBytes(),request.getBytes().length指定字节数组缓冲区,同时指定 ip 和 端口号 InetAddress.getByName(serverIp),serverPort
socket.send(requestPacket);//发送UDP数据报
//3.尝试读取客户端的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);//创建一个空的UDP数据报来接收服务器的响应数据
socket.receive(responsePacket);
//4.把接收的响应数据 由二进制 转换成 字符串 显示出来
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);//该参数为对端(服务器)的ip和端口
client.start();
}
}
运行结果:
* 1.先启动服务器 ,程序会进入while循环,执行到receive()阻塞,等待客户端的请求
* 2.再启动客户端,程序也会进入while循环,执行到scanner 等待用户输入
* 当用户输入字符串之后,next就会返回,构造请求数据,并发送给服务器
* 3.客户端发送数据之后
* 服务器就会从receive中返回,进一步执行解析请求UDP报,执行process计算响应操作,最后执行send操作,发送响应结果
* 客户端接着执行到receive,等待服务器的响应
* 4.客户端接收到服务器响应的数据之后,从receive中返回,执行打印操作,把响应的内容显示出来了
* 5. 服务器执行完后,重新进入while循环,又执行到receive这里阻塞 * 客户端执行完后,也重新进入while循环,执行到scanner这里阻塞 * */
二、翻译功能 UdpDictServer
在回显服务器的基础上,创建一个新的类,继承回显服务器
在UdpDictServer里重写process方法
package network;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class UdpDictServer extends UdpEchoServer{
private Map<String,String> dict = new HashMap<>();
//重写构造方法
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("猫","cat");
dict.put("狗","dog");
dict.put("今天","today");
dict.put("cat","猫");
dict.put("dog","狗");
dict.put("today","今天");
}
//重写父类 UdpEchoServer 的process方法
//完成翻译逻辑,翻译本质上是“查表”
//刚才再dict哈希表里添加了一些键值对元素
//刚才在重写的地方报错
//原因是 父类的process方法之前用的是private
//而private封装特性,不能被重写
//改成public之后就可以了
@Override
public String process(String request) {
// return dict.get(request);
return dict.getOrDefault(request,"该单词目前不在表中,无法查看!!");//差不到表中信息,可以给默认值
}
public static void main(String[] args) throws IOException {
//如果端口号也是9090就与父类的端口号冲突了
UdpDictServer dictServer = new UdpDictServer(6666);//指定这个翻译功能 的端口是6666
dictServer.start();//此时,这里调用start的是子类UdpDictServer,所以后面调用的process也是子类重写的方法
//这里就是之前学到的多态
}
}
注意:
翻译服务器里main方法里的端口要与父类不一样,避免端口号冲突
同时客户端开启的时候,要把端口号修改过来6060,不然服务器就连错了
运行结果