网络编程需要依靠Socket API,在java标准库中有两种风格: 1.(UDP)DatagramSocket:面向数据报(发送接收数据,必须以一定的数据报为单位进行传输) 2.(TCP)ServerSocket:面向字节流
UDP和TCP就是传输层的两个最重要的协议
UDP
实现一个最简单的服务器(回显服务器 echo server),客户端给服务器发送一个字符串,服务器把这个字符串返回显示出来
对于一个服务器程序,核心流程分成两步
1.进行初始化操作 2,进入主循环,接收并处理请求(主循环就是死循环) a)读取数据并解析 b)根据请求计算响应 c)把响应结果写回到客户端
服务器:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* UDP 服务器
*/
public class UdpEchoServer {
//对于一个服务器程序,核心流程分成两步
//1.进行初始化操作
//2,进入主循环,接收并处理请求(主循环就是死循环)
// a)读取数据并解析
// b)根据请求计算响应
// c)把响应结果写回到客户端
private DatagramSocket socket = null;
//DatagramSocket 本质上是一个文件,这个文件是网卡的抽象
//构造方法
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
//new的时候就会让socket对象和一个端口号和一个IP地址关联在一起(绑定端口)
//未来的客户端就按照这个IP和端口号来访问服务器
//如果在构造socket的时候没有写IP,就是 0.0.0.0(会关联到这个主机的所有网卡IP)
//IP是决定互联网的某个主机的位置,port是决定数据交给这个主机的哪个位置
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
// a)读取数据并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
//new byte[4096],4096 相当于关联了缓冲区
// DatagramPacket是发送和接收数据的基本单位
socket.receive(requestPacket);
//程序启动之后马上就能执行到receive
//大多时候调用receive的时候,客户端还没有发送请求,这时receive就会阻塞,当真的有客户端数据过来之后,就会把收到的数据放入缓冲区
String request = new String(requestPacket.getData(), 0,requestPacket.getLength()).trim();
//此处需要把请求数据转成string(本来是byte[])
//requestPacket.getData()获取到缓冲区,也就是byte数组,然后从0开始,到缓冲区长度处结束
//.trim():用户实际发送的数据可能远远小于4096,此时getLength()获取到的都是4096,此时就可以通过trim来去掉一些空白空间
// b)根据请求计算响应
String response = process(request);
// c)把响应结果写回到客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length, requestPacket.getSocketAddress());
//response.getBytes():响应数据就是response,需要包装成一个Packet对象
//request.getBytes().length:获取长度,得到的是字符数
//requestPacket.getSocketAddress():指定当前数据发给谁,这个方法就把IP端口号全部获取到,就设置到了responsePacket里面
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) {
//由于此处是一个回显服务器,所以只需要原分不动的返回
//但是如果是其他复杂的服务器,就需要在这里有更多的逻辑
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
对于一个客户端程序,核心流程分成两步
1.从用户这里读取输入的数据 2.构造一个请求发送给服务器 3.从服务器读取响应 4.把响应写回给客户端
客户端:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* UDP 客户端
*/
public class UdpEchoClient {
//客户端的主要流程分为4步
//1.从用户这里读取输入的数据
//2.构造一个请求发送给服务器
//3.从服务器读取响应
//4.把响应写回给客户端
private 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){
//1.读取用户输入的数据
System.out.print("->");//提示符:提示用户输入字符
String request = scanner.nextLine();//作为请求
if (request.equals("exit")){
//结束
break;
}
//2.构造一个请求发送给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIP), serverPort);
//InetAddress.getByName(serverIP), serverPort: 要把数据报发给哪个服务器:指定IP和端口号
socket.send(requestPacket);
//3.从服务器读取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength()).trim();
//4.把响应写回给客户端
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,因为此时客户端和服务器在一台主机上
//9090:这个端口要和服务器绑定的端口相匹配
client.start();
}
}
如果想要完成较为复杂的逻辑,就可以通过继承,重写process方法实现 例如现在想要完成词典翻译的服务器:
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("find","找到");
}
@Override
public String process(String request) {
return dict.getOrDefault(request,"这超出了我的知识范围");
}
public static void main(String[] args) throws IOException {
UdpDictServer server = new UdpDictServer(9090);
server.start();
}
}