Java学习笔记@网络编程
笔者:unirithe
日期:11/11/2021
网络编程
- 网络编程可以让程序与网络上的其他设备中的程序进行数据交互
网络通信基本模式:
-
Client/Server (CS)
Client
- 需程序员开发实现
- 用户需安装客户端
Server
- 需程序员开发实现
-
Browser/Server(BS)
Browser
- 无需程序员开发实现
- 用户需要安装浏览器
Server
- 需程序员开发实现(JavaWeb)
相关知识:
-
网络通信3要素IP、端口号、协议:一个消息发送给对方需要哪些关键因素
-
UDP通信: 消息直接发送给对方,不确认对方是否在线,不做消息确认
-
TCP 通信: 基于可靠传输的方式进行的通信模式。解决不同场景的通信需求
-
即时通信:如何实现即时通信,具体如何实现
-
模拟BS系统:WEB系统是如何支持访问到网页的,具体是如何与服务器通信的
网络通信三要素
网络通信三要素主要如下:
-
IP地址:设备在网络中的地址,是唯一的标识
-
端口:应用程序在设备中唯一的标识
-
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
IP地址
- IP(Internet Protocol):全称"互联网协议地址",是分配给上网设备的唯一标志
- 常见的IP分类为:IPv4和IPv6
IPv4
32bit(4字节)点分十进制表示法,比如:192.168.1.1
IPv6
128bit(16字节),号称可以为地球每一粒沙子编号
分为8个整数,每个整数用4个十六进制位表示,数之间用冒号(:)分开
比如:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
IP地址基本寻路

IP地址形式
-
公网地址和私有地址(局域网使用)
-
192.168.开头的是常见的局域网地址,范围为192.168.0.0 - 192.168.255.255,专门为组织机构内部使用
Windows常用命令
ipconfig:查看本机IP地址

ping IP/域名地址: 检查两个网络之间是否连通

特殊IP地址:
本机IP:127.0.0.1 或者 localhost : 称为回送地址也可称为本地回环地址,只会寻找当前所在本机
IP地址操作类 —— InetAddress
java.net.InetAddress类表示Internet协议(IP)地址
常用API
| 方法 | 描述 |
|---|---|
public static InetAddress getLocalHost() |
返回本主机的地址对象 |
public static InetAddress getByName(String host) |
得到指定主机的IP地址对象,参数是域名或IP地址 |
public String getHostName() |
获取此IP地址的主机名 |
public String getHostAddress() |
返回IP地址字符串 |
public boolean isReachable(int timeout) |
在指定毫秒内连通该IP地址对应的主机,连通返回true |
范例:查询本机IP地址
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
范例:获取域名IP对象,根据域名查询百度IP地址
InetAddress baidu = InetAddress.getByName("www.baidu.com");
System.out.println(baidu.getHostAddress());
范例:获取公网IP对象,根据百度IP地址创建
InetAddress baidu = InetAddress.getByName("36.152.44.95");
System.out.println(baidu.getHostName());
范例:判断3秒内是否可连通百度IP地址
InetAddress baidu = InetAddress.getByName("www.baidu.com");
System.out.println(baidu.isReachable(3000));
端口号
端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16的二进制,范围是0~65535 (0 ~ \(2^{16} - 1\))
端口类型
- 周知端口: 0 ~ 1023, 被预先定义的知名应用占用(如:HTTP占用80, FTP占用21, DNS占用53)
- 注册端口:1024 ~ 49151, 分配给用户进程或某些应用程序。(如:Tomcat占用8080,Mysql占用3306)
- 动态端口:49152 ~ 65535, 一般不固定分配某种进程,而是动态分配
PS:自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错
协议
网络通信协议:指连接和通信数据的规则
两种参考模型
- OSI 参考模型: 世界互联协议标准,全球通信规范,由于过于理想化,故未能在因特网上广泛推广
- TCP/IP 参考模型:事实上的国际标准

传输层的两个常见协议
- TCP(Transmission Control Protocol): 传输控制协议
- UDP(User Datagram Protocol):用户数据报协议
TCP协议
TCP协议特点:
- 使用TCP协议,必须双方先建立连接,是一种面向连接的可靠通信协议
- 具有可靠性,传输前,采用“三次握手” 方式建立连接
- 连接中可进行大数据量的传输
- 连接、发送数据都需要确认,且传输完毕后还需释放已建立的连接,通信效率较低
TCP协议通信场景
- 对信息安全要求较高的场景,例如:文件下载、金融等数据通信
TCP 三次握手 建立连接

TCP 四次挥手 断开连接

UDP协议
- UDP是一种无连接、不可靠传输的协议
- 将数据源IP、目的地IP和端口封装成数据包,无需建立连接
- 每个数据包的大小限制在64KB内
- 不可靠性,发送不管对方是否准备,接收方收到也无需确认
- 可广播发送,发送数据结束时无需释放资源,开销小,速度快
UDP协议通信场景: 语音通话、视频会话等
UDP 通信
DatagramPacket 数据包对象
创建发送端数据包对象的构造器
public DatagramPacket(byte buf[], int offset, int length,
InetAddress address, int port) {
setData(buf, offset, length);
setAddress(address);
setPort(port);
}
- buf:要发送的内容,字节数组
- length:要发送内容的字节长度
- address:接收端的IP地址对象
- port:接收端的端口号
创建接收端的数据包对象的构造器
public DatagramPacket(byte buf[], int length) {
this (buf, 0, length);
}
- buff:存储接收的内容
- length:能够接收内容的长度
常用方法
public int getLength() 得到实际接收到的字节个数
DatagramSocket 发送端和接收端对象
| 构造器 | 描述 |
|---|---|
public DatagramPacket() |
创建发送端的Socket对象,系统会随机分配一个端口号 |
public DatagramPacket(int port) |
创建接收端的Socket对象并指定端口号 |
DatagramSocket类成员方法
public void send(DatagramPacket dp) ——发送数据包
public void receive(DatagramPacket p) ——接收数据包
UDP案例
一发一收
ServerDemo.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo {
public static void main(String[] args) throws Exception{
System.out.println("======服务端启动======");
// 1. 创建接收端对象 , 注册端口
DatagramSocket socket = new DatagramSocket(8888);
// 2. 创建数据包对象,接受数据
byte[] data = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(
data,
data.length);
// 3. 等待接收数据
socket.receive(packet);
String res = new String(data, 0, packet.getLength());
System.out.println("接受数据: " + res);
System.out.println("对方IP: " +packet.getSocketAddress().toString());
System.out.println("对方端口: " + packet.getPort());
socket.close();
}
}
ClientDemo.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class ClientDemo{
public static void main(String[] args) throws Exception {
System.out.println("======客户端启动======");
// 1. 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
// 2. 创建数据包封装数据,发送数据
byte[] data = "测试".getBytes();
DatagramPacket packet = new DatagramPacket(data,
data.length,
InetAddress.getLocalHost(),
8888);
// 发送数据
socket.send(packet);
socket.close();
}
}
多发多收
需求:使用UDP通信方式开发接收端和发送端
分析:
- 发送端可以一直发送消息
- 接收端可以不断的接收多个发送端的消息展示
- 发送端输入exit则结束程序
ClientDemo.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class ClientDemo{
public static void main(String[] args) throws Exception {
System.out.println("======客户端启动======");
// 1. 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("发送数据: ");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功");
socket.close();
break;
}
// 2. 创建数据包封装数据,发送数据
byte[] data = msg.getBytes();
DatagramPacket packet = new DatagramPacket(data,
data.length,
InetAddress.getLocalHost(),
8888);
// 发送数据
socket.send(packet);
}
}
}
ServerDemo.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo {
public static void main(String[] args) throws Exception{
System.out.println("======服务端启动======");
// 1. 创建接收端对象 , 注册端口
DatagramSocket socket = new DatagramSocket(8888);
// 2. 创建数据包对象,接受数据
byte[] data = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(
data,
data.length);
// 3. 等待接收数据
while (true) {
socket.receive(packet);
String res = new String(data, 0, packet.getLength());
System.out.println("受到了来自: " +
packet.getAddress() + "对方端口为:" +
packet.getPort() + "的消息: " +
res);
}
}
}
通信方式之广播、组播
UDP的三种通信方式
- 单播:单台主机与单台主机之间的通信
- 广播:当前主机与所在网络中的所有主机通信
- 组播:当前主机与选定的一组主机的通信
广播
UDP如何实现广播?
- 使用广播地址:255.255.255.255
- 具体操作
- 发送端发送的数据包的目的地为广播地址且指定端口,如(255.255.255.255, 9999)
- 本机所在网段的其他主机的程序只要匹配端口成功就可以收到信息(9999)
ClientDemo.java
DatagramSocket socket = new DatagramSocket();
byte[] data = "测试".getBytes();
DatagramPacket packet = new DatagramPacket(data,
data.length,InetAddress.getByName("255.255.255.255"),9999);
socket.send(packet);
ServerDemo.java
DatagramSocket socket = new DatagramSocket(9999);
byte[] data = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(
data,
data.length);
socket.receive(packet);
String res = new String(data, 0,packet.getLength());
System.out.println("受到了来自: " +packet.getAddress() + "对方端口为:" +packet.getPort() + "的消息: " +res);
组播
UDP如何实现组播?
- 使用组播地址:224.0.0.0 ~ 239.255.255.255
- 具体操作:
- 发送端的数据包的目的是组播IP(例如:224.0.1.1,端口: 9999)
- 接收端必须绑定该组播IP(224.0.1.1),端口还要对应发送端的目的端口9999
- DatagramSocket的子类
MulticastSocket可以在接收端绑定组播IP
ClientDemo.java
DatagramSocket socket = new DatagramSocket();
byte[] data = "测试".getBytes();
DatagramPacket packet = new DatagramPacket(data,data.length,InetAddress.getByName("224.0.1.1"),9999);
socket.send(packet);
ServerDemo.java
MulticastSocket socket = new MulticastSocket(9999); //socket.joinGroup(InetAddress.getByName("224.0.1.1")); JDK 14 开始摒弃
socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"),9999),NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
byte[] data = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(
data,
data.length);
socket.receive(packet);
String res = new String(data, 0,packet.getLength());
System.out.println("受到了来自: " +packet.getAddress() + "对方端口为:" +packet.getPort() + "的消息: " +res);
TCP通信
在Java中只要使用java.net.Socket类实现通信,其底层则使用了TCP协议
客户端 Socket
-
创建客户端的Socket对象,请求与服务器的连接
public Socket(String host, int port) -
使用socket对象调用
getOutputStream()方法得到字节输出流 -
使用字节输出流完成数据的发送
-
释放资源,关闭socket管道
服务端 ServerSocket
构造器
public ServerSocket(int port) 注册服务端端口
方法
public Socket accept() 阻塞等待接收客户端的 Socket通信连接,得到Socket对象
基本原理:
- 客户端怎么发,服务端就怎么收
- 若客户端没有消息,服务端会进入阻塞等待
- Socket一方关闭或出现异常、对方Socket也会失效或出错
TCP案例
一发一收
ClientDemo.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args){
try {
Socket socket = new Socket("127.0.0.1", 1235);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
//ps.print("TCP客户端连接邀请"); 发送失败,因为未带换行符
ps.println("TCP客户端连接邀请");
ps.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerDemo.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo{
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(1235);
// 等待接收客户端socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 字节输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按行读取
String msg;
if((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "say : " + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
多发多收
范例:无断开状态的一发一收
ClientDemo.java
Socket socket = new Socket("127.0.0.1", 1235);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.print("发送 :");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
ServerDemo.java
ServerSocket serverSocket = new ServerSocket(1235);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "发送了 : " + msg);
}
当前案例是否可以同时接收多个客户端的消息 ? 不可以
因为服务端只有一个线程,只能与一个客户端进行通信
范例:多线程实现多发多收
ServerReaderThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
// 字节输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按行读取
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "发送了 : " + msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "已下线.");
}
}
}
ClientDemo.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args){
try {
Socket socket = new Socket("127.0.0.1", 1235);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
//ps.print("TCP客户端连接邀请"); 发送失败,因为未带换行符
while(true){
System.out.print("发送 :");
String msg = sc.nextLine();
// 发送信息
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerDemo.java
import com.sun.security.ntlm.Server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo{
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(1235);
// 等待接收客户端socket连接请求,建立Socket通信管道
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "已上线!");
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:如何实现服务端接收多个客户端消息
- 主线程定义循环负责接收客户端Socket管道连接
- 每接收到一个Socket通信管道后分配一个独立的
线程负责处理
TCP通信 线程池优化
未使用线程池的通信架构存在的问题:
- 客户端与服务端的线程模型是 N - N 关系
- 客户端并发越多,系统瘫痪越快
引入线程池处理多个客户端消息
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @author Uni
* @create 2021/11/11 17:48
*/
public class ServerReaderRunnableImpl implements Runnable{
private Socket socket;
public ServerReaderRunnableImpl(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
// 字节输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按行读取
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "发送了 : " + msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "已下线.");
}
}
}
ServerDemo.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class ServerDemo{
private static ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(1235);
// 等待接收客户端socket连接请求,建立Socket通信管道
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "已上线!");
Runnable target = new ServerReaderRunnableImpl(socket);
pool.execute(target);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientDemo.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args){
try {
Socket socket = new Socket("127.0.0.1", 1235);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
//ps.print("TCP客户端连接邀请"); 发送失败,因为未带换行符
while(true){
System.out.print("发送 :");
String msg = sc.nextLine();
// 发送信息
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程池优势:
- 服务端可复用线程处理多个客户端,避免系统瘫痪
- 适合客户端通信时长较短的场景
TCP 通信实战案例
即时通信
指一个客户端的消息发出去,其他客户端可接收到,之前的案例的消息都是发给服务端的
即时通信需要进行端口转发的设计思想:
- 服务端把在线的Socket管道存储起来(可使用List)
- 服务端一旦收到一个消息要推送给其他管道(遍历List)
ServerDemo.java
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ServerDemo {
public static List<Socket> allOnlineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(9999);
while(true){
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
allOnlineSockets.add(socket);
new ServerReaderThread(socket).start();
}
}
}
ServerReaderThread.java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
InputStream is = null;
try {
is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while((line = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + "发送了: " + line);
// 把消息进行端口转发给全部客户端 socket 管道
sendMsgToAll(line);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + " 下线了");
ServerDemo.allOnlineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg) throws Exception {
for (Socket allOnlineSocket : ServerDemo.allOnlineSockets) {
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
}
ClientDemo.java
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 9999);
new ClientReaderThread(socket).start();
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("发送: ");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
}
}
ClienReaderThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ClientReaderThread extends Thread{
private Socket socket;
ClientReaderThread(Socket socket){this.socket = socket;}
@Override
public void run() {
try{
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while((line = br.readLine()) != null){
System.out.println(" 收到消息: " + line);
}
} catch (IOException e) {
System.out.println("服务端强制将你中断.");
}
super.run();
}
}
模拟BS系统

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class BSserverDemo {
public static void main(String[] args) {
try{
ServerSocket ss = new ServerSocket(8080);
while(true){
Socket socket = ss.accept();
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本, 响应成功信息
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();
ps.println("<h1 style='color:red'> 《测试》</h1>");
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用线程池实现
ServerReaderThread.java
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本, 响应成功信息
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();
ps.println("<h1 style='color:red'> 《测试》</h1>");
} catch (Exception e) {
e.printStackTrace();
}
}
}
BSserverDemo.java
public class BSserverDemo {
private static ExecutorService pool = new ThreadPoolExecutor(3, 5, 60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try{
ServerSocket ss = new ServerSocket(8080);
while(true){
Socket socket = ss.accept();
pool.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

浙公网安备 33010602011771号