服务器端程序
public class UdpEchoServer {
//对于一个服务器程序来说,核心流程也是要分成两步:
/*
* 1:进行初始化操作(实例化Socket对象)
* 2:进入主循环,接收并处理请求,(主循环就是一个“死循环”,因为服务器需要24小时不间断工作)
* a)读取请求并解析
* b)根据请求计算响应
* c)把响应结果写回到客户端*/
private DatagramSocket socket = null;//使用DatagramSocket类创建一个socket对象,先让它为null
public UdpEchoServer(int port) throws SocketException {//定义一个构造方法,传入端口号
socket = new DatagramSocket(port);//利用传进来的端口实例化socket对象,这是第一步进行初始化操作
/*这个构造方法的作用是:当new这个socket对象的时候,就会让当前的socket对象和一个ip地址以及一个
* 端口号关联起来(绑定端口),我们这个地方没有写ip,就默认是0.0.0.0,这是一个特殊的ip,因为一台主机
* 可能有多个网卡,每个网卡对应一个ip,这个特殊的ip会关联到这个主机的所有的网卡的ip,写服务器一般都是
* 用这个全0的特殊ip,未来客户端就按照这个ip+端口来访问服务器。
* 另外补充一点,socket对象本质上可以看做一个文件,这个文件是网卡的抽象,当我们要对网卡进行
* 操作的时候,可以通过socket来处理,“一切皆文件”*/
}
public void start() throws IOException {//这个方法用来启动服务器
System.out.println("服务器启动");
while (true){//进入主循环,在这个主循环里去处理a,b,c这三个事情
// a)读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//通过DatagramPacket这个类来new一个requestPacket对象,同时关联了一个缓冲区
//准备完缓冲区之后,开始读取请求
socket.receive(requestPacket);
/*这个操作会读取从socket上收到的一切来自客户端请求,程序启动之后马上就能执行到receive操作,
* 服务器启动了之后,客户端也发送请求了吗,显然不一定,客户端啥时候发请求无法确定,大概率的情况是
* ,调用receive的时候,客户端还没谱呢,还没发任何数据,此时receive操作就会阻塞,一直阻塞到真的有请求
* 过来了为止(此处的阻塞时间完全不可预期),当真的有客户端的数据过来了之后,此时receive就会把收到的
* 数据放到DatagramPacket对象的缓存区中*/
String request = new String(requestPacket.getData(),0,requestPacket.getLength()).trim();
/*把存放在缓冲区里的请求数据从byte[]类型,转换为String类型,String方便后续操作,因为用户发送的数据所占空间大小可能远远小于4096,而此处
* getLength得到的长度就是4096,通过trim就可以干掉不必要的空白字符*/
//b)根据请求计算响应
String response = process(request);//使用一个方法来处理响应
//c)把响应结果写回到客户端,响应数据就是response,需要把它包装成DatagramPacket类的一个对象
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
/*response.getBytes():将response的字符串类型转化为字节类型
* response.getBytes().length:得到的是字节数量,这里不能写成response.length,在字符串类型的基础上.length得到的是字符数量,这里传输就是字节类型
* requestPacket.getSocketAddress():这个包要发给谁(目的ip和端口是谁),此处的地址就是客户端的ip和端口,这两个信息就包含在requestPacket内部了*/
socket.send(responsePacket);//把响应结果发送到客户端
//打印一条请求的日志,这一步其实不需要
System.out.printf("[%s:%d] req: %s; resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
}
}
private String process(String request) {
/*这里做一个简单的echo server,请求内容是啥,响应内容就是啥。
* 如果是一个更复杂的服务器,此处就需要包含很多的业务逻辑来进行具体的计算*/
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);//1.进行初始化操作
server.start();//2.进入主循环,接收并处理请求
}
}
客户端程序
public class UdpEchoClient {
//对于一个客户端程序来说,核心流程也是要分成两步
/*1:进行初始化操作
* 2:进入主循环:
* a)从用户这里读取输入的数据
* b)构造请求并发送给服务器
* c)读取服务器的响应
* d)把响应写会显示到桌面上*/
private DatagramSocket socket = null;//使用DatagramSocket类创建一个socket对象,先让它为null
private String serverIp;
private int serverPort;
//需要在启动客户端的时候来指定需要连接哪个服务器
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();
/*客户端创建socket的时候不需要绑定端口号,因为一个端口号通常情况下只能被一个进程绑定,如果客户端绑定了的话,
* 一个主机上就只能启动一个客户端了,所以客户端必须不绑定端口,它会由操作系统自动分配一个空闲端口。
* 服务器必须绑定端口,服务器绑定了端口之后,客户端才能访问*/
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){//进入主循环,在这个主循环里去处理a,b,c,d这四个步骤
//a)从用户这里读取输入的数据
String request = scanner.nextLine();//读取控制台输入的一行数据作为请求
if(request.equals("exit")){
break;//跳出循环
}
//b)构造请求并发送给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort);//客户端构造请求
socket.send(requestPacket);//把请求发送给服务器
//c)读取服务器的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
/*通过DatagramPacket这个类创建一个缓冲区,创建完缓冲区之后再从服务器读取响应,这段代码和服务器读取请求是一模一样的*/
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
//d)把响应写回显示到桌面上
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
/*127.0.0.1这是一个特殊的IP,称为环回IP,自己访问自己,
* 如果服务器和客户端都在同一台主机上,此处的ip就是环回ip
* 如果服务器和客户端不在同一台主机上,此处的ip就要写成服务器的ip
* */
client.start();
}
/*站在客户端角度,理解此处通信的五元组:
* 协议类型:UDP
* 源ip:客户端的ip(客户端所在主机的ip)
* 源端口:客户端的端口(操作系统自动分配的端口)
* 目的ip:服务器的ip(服务器和客户端在同一个主机上,ip就是127.0.0.1)
* 目的端口:9090(服务器启动的时候绑定的端口)*/
}