zoukankan      html  css  js  c++  java
  • <分布式程序设计> 读书笔记三

    三 客户与服务器程序设计

    对现有服务编写客户程序

    Smtp 简单邮件传输协议simple mail transfer protocol,使用TCP 协议,默认端口号为25

    示例代码如下

    /**

     * Copyright (C) 2015

     * 

     * FileName:StmpClient.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.mail;

    import java.io.BufferedReader;

    import java.io.DataOutputStream;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.net.Socket;

    import java.net.UnknownHostException;

    import javax.print.DocFlavor.STRING;

    /**

     * 简单邮件客户端

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class SmtpClient {

    public static void main(String[] args) {

    Socket smtpSocket = null;

    DataOutputStream os = null;

    BufferedReader is = null;

    try {

    smtpSocket = new Socket("sina.com", 25);

    os = new DataOutputStream(smtpSocket.getOutputStream());

    is = new BufferedReader(new InputStreamReader(smtpSocket.getInputStream()));

    } catch (UnknownHostException e) {

    e.printStackTrace();

    System.err.println("do not know host: hostname");

    } catch (IOException e) {

    System.err.println("could not get I/O ");

    }

    if (smtpSocket != null && os != null) {

    try {

    os.writeBytes("HELO  ");

    os.writeBytes("MAIL From: zhenhuayue@sina.com");

    os.writeBytes("RCPT To:zhenhuayue@sina.com  ");

    os.writeBytes("DATA  ");

    os.writeBytes("From: 149780042@qq.com  ");

    os.writeBytes("Subject:testing  ");

    os.writeBytes(" .  ");

    os.writeBytes("QUIT");

    String responseLine;

    while ((responseLine = is.readLine()) != null) {

    System.out.println("server:" + responseLine);

    if ((responseLine.indexOf("OK")) != -1) {

    break;

    }

    }

    os.close();

    is.close();

    smtpSocket.close();

    } catch (IOException e) {

    System.out.println("IOException:" + e);

    }

    }

    }

    }

    Finger 用于确定当前哪一个用户登录到指定计算机,也可用于探测更多用户

    使用tcp协议,默认端口号为79

    示例代码如下:/**

     * Copyright (C) 2015

     * 

     * FileName:Finger.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.net.finger;

    import java.io.BufferedReader;

    import java.io.DataOutputStream;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.net.InetAddress;

    import java.net.Socket;

    import java.net.UnknownHostException;

    /**

     * 用于检测哪个用户登录到指定计算机

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class Finger {

    /** 计算机名 **/

    static String host = null;

    /** 用户名 **/

    static String user = null;

    /**

     * 取得计算机名

     * 

     * @return

     * @throws UnknownHostException

     */

    public static String localHost() throws UnknownHostException {

    InetAddress host = null;

    host = InetAddress.getLocalHost();

    return host.getHostName();

    }

    /**

     * 解析方法

     * 

     * @param str

     * @throws UnknownHostException

     */

    public static void parse(String str) throws UnknownHostException {

    int position = 0;

    while (position != -1) {

    position = str.indexOf("@", position);

    if (position != -1) {

    host = str.substring(position + 1).trim();

    user = str.substring(0, position).trim();

    position++;

    } else {

    user = str;

    host = localHost();

    }

    }

    }

    public static void main(String[] args) throws Exception {

    Socket fingerSocket = null;

    DataOutputStream os = null;

    BufferedReader is = null;

    if (args.length == 1) {

    parse(args[0]);

    } else {

    host = localHost();

    user = "@" + user;

    }

    try {

    fingerSocket = new Socket(host, 79);

    os = new DataOutputStream(fingerSocket.getOutputStream());

    is = new BufferedReader(new InputStreamReader(fingerSocket.getInputStream()));

    } catch (UnknownHostException e) {

    System.err.println("count not get I/O for the connection to:" + host);

    }

    if (fingerSocket != null && os != null && is != null) {

    try {

    os.writeBytes(user);

    os.writeBytes(" ");

    String responseLine;

    while ((responseLine = is.readLine()) != null) {

    System.out.println(responseLine);

    }

    os.close();

    is.close();

    fingerSocket.close();

    } catch (UnknownHostException e) {

    System.err.println("trying to connect to unknow host:" + e);

    } catch (IOException e) {

    System.err.println("IOException" + e);

    }

    }

    }

    }

    Ping客户程序 packet interNet groper,作用是通过向远程主机发送ICMP echo请求并等待一个响应,来检查主机是否可达 

    :icmp需要通过socket_ram类型的socket来产生,java不支持

    示例代码如下:

    /**

     * Copyright (C) 2015

     * 

     * FileName:Ping.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.net;

    import java.io.IOException;

    import java.net.Socket;

    import java.net.UnknownHostException;

    /**

     * 检查目标主机是否可达

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class Ping {

    public final static int ECHO_PORT = 7;

    public static void main(String[] args) {

    if (args.length != 1) {

    System.out.println("usage: java ping hostname");

    System.exit(0);

    }

    if (alive(args[0])) {

    System.out.println(args[0] + " is alive");

    } else {

    System.out.println("no response from " + args[0] + ". host is down or does not exist!");

    }

    }

    /**

     * 检查目标主机是否可达

     * 

     * @param host

     * @return

     */

    private static boolean alive(String host) {

    Socket pingSocket = null;

    try {

    pingSocket = new Socket(host, ECHO_PORT);

    } catch (UnknownHostException e) {

    System.err.println("UnknownHostException: " + e);

    } catch (IOException e) {

    System.err.println("IOException: " + e);

    }

    if (pingSocket != null) {

    try {

    pingSocket.close();

    } catch (IOException e) {

    System.err.println("IOException: " + e);

    }

    return true;

    } else {

    return false;

    }

    }

    }

    使用线程进行编程

    创建并行运行线程,有两种方法Thread 和实现runnable接口 

    另外还可以使用线程池Executors或第三方框架amino,java8中好像添加了新的实现方法

    示列代码如下:

    /**

     * Copyright (C) 2015

     * 

     * FileName:MyThread.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.thread;

    public class MyThread extends Thread {

    public MyThread(String name) {

    super(name);

    }

    @Override

    public void run() {

    for (int i = 0; i < 3; i++) {

    System.out.println(getName() + ":" + i);

    try {

    //Thread.sleep(500);

    TimeUnit.MICROSECONDS.sleep(100);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    }

    }

    public static void main(String[] args) {

    MyThread t1 = new MyThread("readFromSocket");

    MyThread t2 = new MyThread("readFromKeyBoard");

    t1.start();

    t2.start();

    }

    }

    /**

     * Copyright (C) 2015

     * 

     * FileName:MyThread2.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.thread;

    /**

     * 使用实现runnable接口的方法来开启线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class MyThread2 implements Runnable {

    public static void main(String[] args) {

    MyThread2 t1 = new MyThread2();

    MyThread2 t2 = new MyThread2();

    new Thread(t1, "readFromSocket").start();

    new Thread(t2, "readFromKeyBoard").start();

    }

    @Override

    public void run() {

    for (int i = 0; i < 3; i++)

    try {

    {

    System.out.println(Thread.currentThread() + " : " + i);

    //Thread.sleep(500);

    TimeUnit.MICROSECONDS.sleep(500);

    }

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    }

    }

    使用线程睡眠    老版本sleep();

    新TimeUnit.MICROSECONDS.sleep(100);

    使用join等待另处一个线程结束,示例代码如下:

    /**

     * Copyright (C) 2015

     * 

     * FileName:JoinTest.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.thread;

    import java.util.concurrent.TimeUnit;

    /**

     * 使用join等待另处一个线程结束

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class JoinTest extends Thread {

    static int result = 0;

    public JoinTest(String name) {

    super(name);

    }

    public static void main(String[] args) {

    System.out.println("主线程执行...");

    // 开始子线程

    Thread t = new JoinTest("计算线路程");

    t.start();

    System.out.println("result:" + result);

    try {

    long start = System.nanoTime();

    t.join();

    long end = System.nanoTime();

    System.out.println((end - start) / 1000000 + "毫秒后: " + result);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    }

    @Override

    public void run() {

    System.out.println(this.getName() + "开始计算...");

    try {

    TimeUnit.MICROSECONDS.sleep(4000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    result = (int) (Math.random() * 10000);

    System.out.println(this.getName() + "计算结束");

    }

    }

    控制线程 ,以下三个方法都已过时

    t1.stop();  //停止  可用定义标识或是interrupted

    t1.resume(); //重新启动线程

    t1.suspend(); //挂起一个线程 

    响应线程中断Thread.interrupted()来取消线程,示例代码如下:

    /**

     * Copyright (C) 2015

     * 

     * FileName:InterruptTest.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.thread;

    /**

     * 使用interrupt来取消线程

     * 

     * 主线程等待计算线程2000毫秒后,中断计算线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class InterruptTest extends Thread {

    static int result = 0;

    public InterruptTest(String name) {

    super(name);

    }

    public static void main(String[] args) {

    System.out.println("主线程执行...");

    // 开始子线程

    Thread t = new InterruptTest("计算线程");

    t.start();

    System.out.println("result:" + result);

    try {

    long start = System.nanoTime();

    t.join(10);

    long end = System.nanoTime();

    t.interrupt();

    System.out.println((end - start) / 1000000 + "毫秒后: " + result);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    }

    @Override

    public void run() {

    System.out.println(this.getName() + "开始计算...");

    // 方法一

    // try {

    // Thread.sleep(4000);

    // } catch (InterruptedException e) {

    // System.out.println(this.getName() + "被中断,结束");

    // return;

    // }

    // result = (int) (Math.random() * 10000);

    // System.out.println(this.getName() + "计算结束");

    // 方法二

    for (int i = 0; i < 1000000; i++) {

    result++;

    if (Thread.interrupted()) {

    System.out.println(this.getName() + "被中断,结束");

    return;

    }

    }

    System.out.println(this.getName() + "计算结束");

    }

    }

     改变线程优先级

      同步 synchronization

    使用synchronization访问标识符

    /**

     * Copyright (C) 2015

     * 

     * FileName:BankAccount.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.thread;

    /**

     * 同步

     * 

     * 使用同步标识符

     * 

     * 使用同步代码段

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class BankAccount {

    private int number;

    private int balance;

    public BankAccount(int number, int balance) {

    this.number = number;

    this.balance = balance;

    }

    public int getBalance() {

    return this.balance;

    }

    /**

     * 存款

     * 

     * @param amount

     */

    public synchronized void deposit(int amount) {

    balance = balance + amount;

    }

    /**

     * 取款

     * 

     * @param amount

     */

    public synchronized void withdraw(int amount) {

    balance = balance - amount;

    }

    public static void main(String[] args) throws InterruptedException {

    BankAccount account = new BankAccount(1, 1000);

    Thread t1 = new Thread(new Despositor(account, 100), "depositor");

    Thread t2 = new Thread(new Withdrawer(account, 100), "wiithdraw");

    t1.start();

    t2.start();

    t1.join();

    t2.join();

    System.out.println(account.getBalance());

    }

    /**

     * 存款线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    static class Despositor implements Runnable {

    BankAccount account;

    int amount;

    public Despositor(BankAccount account, int amount) {

    this.account = account;

    this.amount = amount;

    }

    @Override

    public void run() {

    for (int i = 0; i < 100000; i++) {

    account.deposit(amount);

    }

    }

    }

    /**

     * 取款线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    static class Withdrawer implements Runnable {

    BankAccount account;

    int amount;

    public Withdrawer(BankAccount account, int amount) {

    this.account = account;

    this.amount = amount;

    }

    @Override

    public void run() {

    for (int i = 0; i < 100000; i++) {

    account.withdraw(amount);

    }

    }

    }

    }

    使用synchronization代码段

    /**

     * Copyright (C) 2015

     * 

     * FileName:BankAccount.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-16

     */

    // Package Information

    package cn.yue.test.thread;

    /**

     * 同步

     * 

     * 使用同步标识符

     * 

     * 使用同步代码段

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    public class BankAccount {

    private int number;

    private int balance;

    public BankAccount(int number, int balance) {

    this.number = number;

    this.balance = balance;

    }

    public int getBalance() {

    return this.balance;

    }

    /**

     * 存款

     * 

     * @param amount

     */

    public void deposit(int amount) {

    synchronized (this) {

    balance = balance + amount;

    }

    }

    /**

     * 取款

     * 

     * @param amount

     */

    public void withdraw(int amount) {

    synchronized (this) {

    balance = balance - amount;

    }

    }

    public static void main(String[] args) throws InterruptedException {

    BankAccount account = new BankAccount(1, 1000);

    Thread t1 = new Thread(new Despositor(account, 100), "depositor");

    Thread t2 = new Thread(new Withdrawer(account, 100), "wiithdraw");

    t1.start();

    t2.start();

    t1.join();

    t2.join();

    System.out.println(account.getBalance());

    }

    /**

     * 存款线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    static class Despositor implements Runnable {

    BankAccount account;

    int amount;

    public Despositor(BankAccount account, int amount) {

    this.account = account;

    this.amount = amount;

    }

    @Override

    public void run() {

    for (int i = 0; i < 100000; i++) {

    account.deposit(amount);

    }

    }

    }

    /**

     * 取款线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-16

     * 

     */

    static class Withdrawer implements Runnable {

    BankAccount account;

    int amount;

    public Withdrawer(BankAccount account, int amount) {

    this.account = account;

    this.amount = amount;

    }

    @Override

    public void run() {

    for (int i = 0; i < 100000; i++) {

    account.withdraw(amount);

    }

    }

    }

    }

    编写新的服务器和客户端程序

    示例代码如下:/**

     * Copyright (C) 2015

     * 

     * FileName:ArrayIO.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-17

     */

    // Package Information

    package cn.yue.test.net.concurrent;

    import java.io.BufferedReader;

    import java.io.DataOutputStream;

    import java.io.IOException;

    /**

     * 数组操作

     * 

     * 用于数组的读写和两个数组相加

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-17

     * 

     */

    public class ArrayOption {

    public ArrayOption() {

    }

    /**

     * 写数组内容

     * 

     * @param out

     * @param arr

     * @throws IOException

     */

    public void writeArray(DataOutputStream out, int arr[]) throws IOException {

    for (int i = 0; i < arr.length; i++) {

    out.write(arr[i]);

    }

    }

    /**

     * 读取数组内容

     * 

     * @param br

     */

    public int[] readArray(BufferedReader br) throws Exception {

    int e[] = new int[10];

    for (int h = 0; h < 10; h++) {

    try {

    e[h] = br.read();

    } catch (IOException e1) {

    e1.printStackTrace();

    }

    }

    return e;

    }

    /**

     * 两个数组相加

     * 

     * @param a

     * @param b

     * @return

     */

    public int[] addArray(int a[], int b[]) {

    int result[] = new int[10];

    for (int s = 0; s < result.length; s++) {

    result[s] = a[s] + b[s];

    }

    return result;

    }

    }

    /**

     * Copyright (C) 2015

     * 

     * FileName:Client.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-17

     */

    // Package Information

    package cn.yue.test.net.concurrent;

    import java.io.BufferedOutputStream;

    import java.io.BufferedReader;

    import java.io.DataOutputStream;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.net.Socket;

    import java.net.UnknownHostException;

    /**

     * 

     * 支持并发访问的客户端

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-17

     * 

     */

    public class Client {

    public final static int REMOTE_PORT = 3333;

    static int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    static int b[] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };

    /**

     * @param args

     * @throws IOException

     * @throws UnknownHostException

     */

    public static void main(String[] args) {

    Socket c1 = null;

    Socket c2 = null;

    BufferedReader is = null;

    DataOutputStream os = null;

    ArrayOption ao = new ArrayOption();

    try {

    c1 = new Socket("localhost", REMOTE_PORT);

    is = new BufferedReader(new InputStreamReader(c1.getInputStream()));

    os = new DataOutputStream(c1.getOutputStream());

    } catch (UnknownHostException e) {

    System.out.println("unknown host: " + e);

    } catch (IOException e) {

    System.out.println("error io: " + e);

    }

    try {

    ao.writeArray(os, a);

    ao.writeArray(os, b);

    } catch (IOException e) {

    System.out.println("error writing to server..." + e);

    }

    // 从服务器接收数据

    int result[] = new int[10];

    try {

    result = ao.readArray(is);

    } catch (Exception e) {

    e.printStackTrace();

    }

    System.out.println("the sum of the two arrays: ");

    for (int j = 0; j < result.length; j++) {

    System.out.println(result[j] + " ");

    }

    System.out.println(" ");

    try {

    is.close();

    os.close();

    c1.close();

    } catch (IOException e) {

    System.out.println("error writing..." + e);

    }

    }

    }

    /**

     * Copyright (C) 2015

     * 

     * FileName:Server.java

     *

     * Author:<a href="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * CreateTime: 2015-1-17

     */

    // Package Information

    package cn.yue.test.net.concurrent;

    import java.io.BufferedReader;

    import java.io.DataOutputStream;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.net.ServerSocket;

    import java.net.Socket;

    import sun.print.resources.serviceui;

    /**

     * 支持并发访问的服务器端

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-17

     * 

     */

    public class Server extends Thread {

    public static final int MATH_PORT = 3333;

    protected ServerSocket listen;

    public Server() {

    try {

    listen = new ServerSocket(MATH_PORT);

    } catch (IOException e) {

    System.out.println("exception.." + e);

    }

    // 启动监听

    this.start();

    }

    @Override

    public void run() {

    try {

    while (true) {

    Socket client = listen.accept();

    Connects cs = new Connects(client);

    }

    } catch (IOException e) {

    System.out.println("exception..." + e);

    }

    }

    public static void main(String[] args) {

    new Server();

    }

    }

    /**

     * 连接线程

     * 

     * @version

     * 

     * @Description:

     * 

     * @author <a href="mailto:zhenhuayue@sina.com">Retacn</a>

     * 

     * @since 2015-1-17

     * 

     */

    class Connects extends Thread {

    Socket client;

    BufferedReader is;

    DataOutputStream os;

    ArrayOption ao = new ArrayOption();

    public Connects(Socket s) {

    client = s;

    try {

    is = new BufferedReader(new InputStreamReader(client.getInputStream()));

    os = new DataOutputStream(client.getOutputStream());

    } catch (IOException e) {

    try {

    client.close();

    } catch (IOException e1) {

    System.out.println("error getting socket stream ..." + e);

    }

    return;

    }

    this.start();

    }

    @Override

    public void run() {

    int a1[] = new int[10];

    int a2[] = new int[10];

    try {

    a1 = ao.readArray(is);

    a2 = ao.readArray(is);

    } catch (Exception e) {

    e.printStackTrace();

    }

    int r[] = new int[10];

    r = ao.addArray(a1, a2);

    try {

    ao.writeArray(os, r);

    } catch (IOException e) {

    e.printStackTrace();

    }

    }

    }

  • 相关阅读:
    mysql 开启sql执行日志
    opcache.revalidate_freq 修改无效
    centos7 maven3.6.3安装
    CentOS7.5下基于FTP服务的局域网yum源搭建
    Centos7——防火墙(Firewall)开启常见端口命令
    Linux系统通过firewall限制或开放IP及端口
    CentOS7 FTP安装与配置
    centos7 搭建个人-企业私有云盘-seafile
    Centos6-7下杀毒软件clamav的安装和使用 (已成功测试)-----转发
    tomcat 安全规范(tomcat安全加固和规范1)--转发
  • 原文地址:https://www.cnblogs.com/retacn-yue/p/6194237.html
Copyright © 2011-2022 走看看