1. C/S架构通信原理
- 角色与交互模式:客户端(client)主动发起通信请求,服务器(server)被动接收通信并给予响应。如同生活中顾客向商家咨询商品信息,顾客主动询问,商家被动应答 。常见交互为一问一答模式,不过也存在其他模式。
- 多客户端服务:服务器通常要为多个客户端提供服务,类似一个大型超市要服务众多顾客。
2. 简单网络程序示例(回显服务器Echo Server)
- 服务器端:
- 核心任务三步:从客户端读取请求内容、依据请求计算响应(回显服务器是直接将请求作为响应)、把响应返回给客户端。
- 代码中使用 DatagramSocket 创建socket对象,若端口被占会抛 SocketException 异常 。通过死循环持续处理客户端请求, receive 方法用于接收数据报,若没收到会阻塞等待。
- 客户端:
- 主要操作四步:从控制台读取用户输入内容、通过网络发给服务器、从服务器读取响应、将响应显示到控制台。
- 创建客户端时需指定服务器的IP和端口,构造 socket 对象时操作系统自动分配空闲端口。
3. 网络通信关键要素
- Socket API:以UDP为例, DatagramSocket 用于创建socket对象,关联网络通信的五元组(源IP、源端口、目的IP、目的端口、协议类型) 。端口号范围0 - 65535 ,0 - 1024为知名端口号,一般给系统预留协议服务器使用,自定义代码最好避开。可通过尝试绑定或 netstat -ano 命令查看端口占用情况。
- 跨主机通信准备:要完成跨主机通信,后续涉及把服务器程序打成jar包、Linux服务器操作、开启云服务器防火墙等内容。
- UDP特性 :
- 无连接:不像TCP要先建立连接,可直接收发数据。
- 面向数据报:数据传输以数据报为单位。
- 全双工:可同时进行数据的发送和接收。
4. 代码相关注意事项
- 服务器端绑定端口时,一个端口同一时刻只能被一个进程绑定,若冲突会失败抛异常。
- 客户端指定固定端口易与其他程序冲突,所以通常由系统自动分配端口。
服务器端关键代码片段
// 创建socket对象
private DatagramSocket socket;
// 构造方法
public EchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
while (true) {
// 1. 读取请求并解析
// 1) 创建一个空白的DatagramPacket对象
DatagramPacket reqPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(reqPacket);
// 3) 把DatagramPacket中的数据解析成字符串. 只需要从DatagramPacket取到有效的数据即可.
String request = new String(reqPacket.getData(), 0, reqPacket.getLength());
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回到客户端
// 1) 把响应构造成DatagramPacket对象
DatagramPacket respPacket = new DatagramPacket(response.getBytes(), response.getBytes().length, reqPacket.getSocketAddress());
// 2) 把DatagramPacket写回到客户端.
socket.send(respPacket);
// 4. 打印日志.
System.out.printf("[%s:%d] req: %s, resp: %s\n", reqPacket.getAddress(), reqPacket.getPort(), request, response);
}
客户端关键代码片段
public EchoClient(String serverIp, int serverPort) {
this.serverIp = serverIp;
this.serverPort = serverPort;
}
socket = new DatagramSocket();
while (true) {
// 1. 从控制台读取用户输入的内容
// 2. 构造成UDP请求, 并发送. 不光要填内容, 还要填服务器的地址和端口.
DatagramPacket reqPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort);
socket.send(reqPacket);
// 3. 读取服务器的响应.
DatagramPacket respPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(respPacket);
// 4. 把响应显示到控制台.
String response = new String(respPacket.getData(), 0, respPacket.getLength());
System.out.println(response);
}