0


网络编程套接字

很容易搞混,得好好理理

1.网络编程的基本概念

①什么是网络编程:
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输。

②请求和响应分别指的什么?
一般来说,获取一个网络资源,涉及到两次网络数据传输:
第一次:请求数据的发送
第二次:响应数据的发送。
举个例子:
请求和响应在生活中是很好理解的,我手机没电了向别人借一下手机就是一个请求,而别人的回应就是响应。

1.1服务器

在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端(也称服务器),可以提供对外服务。通俗可以理解成供应者。

1.2客户端

获取服务的一方进程,称为客户端。通俗可以理解成获得者。

2.Socket套接字

2.1概念

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。

2.2分类

①流套接字:(使用传输层TCP协议)

a.TCP的特点:

有连接,可靠,面向字节流,全双工

b.分别解释一下以上名词:

有连接:
需要连通,才能交换数据(比如打电话,需要你接了之后才能够和你进行通话)

可靠:
传输过程中发送方知道接收方有没有接收数据(打电话,接了电话那么就会接收到数据,否则就不知道是否接收了数据)

面向字节流:
以字节为单位进行传输的方式

全双工:
一条链路双向通信(无疑,无论是打电话还是发QQ都是一种双向的操作)

②数据报套接字:(使用传输层UDP协议)

a.UDP特点:

无连接,不可靠,面向数据报,全双工

b.分别解释一下以上名词:

无连接:
不需要连接,直接就能发送数据(比如发QQ,我不需要得到你的允许就可以直接给你发送)

不可靠:
不知道传输的对象有没有接收到信息(发QQ别人未回,我们不知道是已读还是未读)

面向数据报:
以数据报为单位进行传输,一次发送/接收都必须是完整的一个数据报或者多个数据报,不能是半个啊,一个半数据报这种

全双工:
一条链路双向通信(无疑,无论是打电话还是发QQ都是一种双向的操作)

③原始套接字

原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。我们不学习原始套接字,简单了解即可。

3.UDP数据报套接字编程

对客户端发送请求,服务器接收请求并作出回应的过程图(这里是一次请求一次回应)

3.1DatagramPocket API

①什么是DatagramPocket?

DatagramPacket是UDP发送和接收的数据报。每次发送/接收数据,都在传输一个DatagramPocke对象

②DatagramPocket的构造方法:

方法签名
方法说明
DatagramPacket(byte[] buf, int length)

构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数为length)

DatagramPacket(byte[]

buf, int offset, int length,

SocketAddress address)

构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号

注意:

构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

InetSocketAddress ( SocketAddress 的子类 )构造方法:

方法签名方法说明InetSocketAddress(InetAddress address, int port)创建一个Socket地址,包含IP地址和端口号③DatagramPocket的方法:
方法签名方法说明
InetAddress

getAddress()

从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址

int getPort()

从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号

byte[]getData()

获取数据报中的数据

3.2DatagramSocket API

①什么是DatagramSocket?

创建了一个UDP版本的Socket对象,实质上代表着操作系统中一个Socket文件(更进一步而言是对网卡进行的读写的操作)

②DatagramSocket的构造方法:
方法签名方法说明
DatagramSocket()

创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)

DatagramSocket(int

port)

创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

③DatagramSocket的方法:
方法签名方法说明
void receive(DatagramPacket p)

从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)

void send(DatagramPacket

p)

从此套接字发送数据报包(不会阻塞等待,直接发送)

void close()

关闭此数据报套接字

3.3UDP实现回显服务

①什么是回显服务?

请求的是啥,回应的就是啥的操作

②对回显服务进行代码实现:

相关注意事项注释中已经讲解得非常细了,这里只着重强调一个点

客户端:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPClient {
    //首先需要有一个DataframSocket实例,这是进行网络编程的前提
    private DatagramSocket socket=null;
    private String serverIP;//IP地址,这里是指服务器传过来的
    private int serverPort;//端口号,这里是指服务器传过来的
public UDPClient(String Ip,int port) throws SocketException {
    socket = new DatagramSocket();
    //这里就是用无参版本的构造socket对象,就是让系统随机分配一个端口号
    serverIP=Ip;//这个指定的是服务器的IP地址和端口号和客户端没有关系
    serverPort=port;
}
public void start() throws IOException {
    Scanner input= new Scanner(System.in);
    while (true){
        //首先先从控制台读取到用户输入的信息,然后才能发送
        System.out.println("->");
        String request = input.next();
        //然后把用户写的信息构造成一个UDP请求,才能发送,指定IP和端口号,才知道传到了哪个服务器去
        DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
                InetAddress.getByName(serverIP),serverPort);
        //然后发送UDP数据报
        socket.send(requestPacket);
        //发送完之后需要再接收从服务器得到的响应数据
        //这里仍然需要用一个字节类数组来对返回内容进行接收
        DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
        socket.receive(responsePacket);
        //然后再把响应的数据写成字符串形式,然后打印到控制台
        String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");
        System.out.printf("req:%s,resp:%s\n",request,response);
    }
}
    public static void main(String[] args) throws IOException {
        UDPClient udpClient=new UDPClient("127.0.0.1",3030);
        udpClient.start();
    }
}

服务器:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPServer {
   //首先需要有一个DataframSocket实例,这是进行网络编程的前提
    private DatagramSocket socket = null;
    public UDPServer(int port) throws SocketException {
        //这个地方会出现异常的原因
        //1.端口号已经被占用了,同一主机的两个程序不能有相同的端口号
        //2.2.每个进程能打开的文件个数是有上限的,资源耗尽后是打不开的
     socket=new DatagramSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器!");
        //由于UDP的不可靠性,因此可以直接接收从客户端传来的数据,不用进行连接
        while(true){
            //这个时候就需要等待客户端发来请求,进行接收并作出回应
            //因为服务器本身就是被动接收数据的,而不是发送
            //构造一个Datagrampocket来对数据报进行发送和接收
            // 且接收应该用一个空的byte类型的数组对内容来进行接收(这是DatagramPocket的一个构造方法)
            DatagramPacket requestPacket=new DatagramPacket(new byte[1024],1024);
            //当没有接收到客户端的请求时,receive()就会阻塞,直到接收到才停止阻塞
            socket.receive(requestPacket);
            //接收到客户端的请求后需要作出回应
            //此时已经将接收到的数据放在requestPacket里面了,然后将这里面的数据解析出来,解析成字符串
            //new String()方法可以将字符,字节转换为字符串
            String request=new String(requestPacket.getData(),0,requestPacket.getLength(),"UTF-8");//也可以指定字符编码
            //解析后需要找到一个新的字符串来进行存放,并产生相应
            String response = prcoess(request);
            //对客户端作出响应
            //这里不仅要放回响应内容,而且要指定好要发送到的IP地址和端口号
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
            //发送过来的是DatagramPacket数据报,返回的也就是这样的数据报,但是发送回去的就不再是一个空的了,而是应该把从上面所得到的响应字符串放到里面然后
            socket.send(responsePacket);
            //可以再手动输出一下客户端的IP地址和端口号
            System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);

        }
    }
public String prcoess(String request){
        return request;

}
    public static void main(String[] args) throws IOException {
        UDPServer udpServer=new UDPServer(3030);
        udpServer.start();
    }
}

③一个简单翻译程序的实现(代码):

客户端没有更改,只是把服务器进行了调整

package UDP;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
public class UDPdictServer extends UDPServer {
    private HashMap<String,String> dict=new HashMap<>();
    public UDPdictServer(int port) throws SocketException {
        super(port);
        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("fuck", "卧槽");
        dict.put("pig", "小猪");
    }
    @Override
    public String prcoess(String request) {
        return dict.getOrDefault(request, "该词无法被翻译!");
    }
    public static void main(String[] args) throws IOException {
        UDPdictServer server = new UDPdictServer(3030);
        server.start();
    }
}

4.TCP流套接字编程

关于TCP的执行过程图

4.1ServerSocket API

①什么是ServerSocket?

ServerSocket 是创建TCP服务端Socket的API。

②ServerSocket的方法:
方法签名方法说明
Socket accept()

开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待

void close()
关闭此套接字

4.2Socket API

①什么是Socket?

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

②Socket的构造方法:

方法签名方法说明
Socket(String host, int port)

创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

③Socket的方法:
方法签名方法说明
InetAddress getInetAddress()

返回套接字所连接的地址

InputStream getInputStream()

返回此套接字的输入流

OutputStream getOutputStream()

返回此套接字的输出流

4.3TCP实现回显服务

代码实现:(相关细节内容在代码注解中讲得很详细)

客户端:

package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
    private Socket socket=null;
public TCPClient(String serverIP,int serverPort) throws IOException {
    //对于UDP的socket和TCP的serverSocket来说,构造函数里面给定的端口号,就是指定自己要绑定哪个端口号
    //而这里对于TCP的Socket来说,这里指定的IP和端口号是表示自己要与哪一个服务器进行连接,这里的IP和端口号都是服务器的
    //调用这个构造方法就会和服务器进行连接,就会使accept的立马进行接通
    socket = new Socket(serverIP,serverPort);
}
//启动客户端
    public void start() throws IOException {
        System.out.println("和服务器连接成功!");
        Scanner input = new Scanner(System.in);
        //因为TCP是流来传输运行的
        try(InputStream inputStream=socket.getInputStream()){
            try(OutputStream outputStream=socket.getOutputStream()){
                while(true){//此时仍然是需要四个步骤
                    //1.从控制台读取字符串
                    System.out.println("从控制台读取到的字符串->");
                    //客户端要输入请求
                    String request=input.next();
                    // 2. 根据读取的字符串,将所读的字符串构造为请求, 把请求发给服务器
                    //用printWriter来对读取的内容进行接收
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush(); // 如果不刷新, 可能服务器无法及时看到数据.
                   //3.读取到服务器来的响应,并对其写入的内容进行解析
                    //然后从服务器中读取响应,并解析
                    Scanner respScanner = new Scanner(inputStream);
                    String response = respScanner.next();
                    //打印先关结果
                    System.out.printf("req:%s,resp:%s\n",request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException {
TCPClient tcpClient=new TCPClient("127.0.0.1",9090);
tcpClient.start();
    }
}

服务器:

package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer {
    private ServerSocket serverSocket = null;
public TCPServer(int port) throws IOException {
    serverSocket=new ServerSocket(port);//给服务器指定端口号
}
public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
        // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
        // accept 就是在 "接电话", 接电话的前提是, 有人给你打了, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
        // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
        // 进一步讲, serverSocket 就干了一件事, 接电话
        //进一步来说是建立了连接然后应答上,用clientSocket来进行后续操作
        Socket clientSocket = serverSocket.accept();
        //对接收的响应进行处理
        processConnection(clientSocket);
    }
}
public  void  processConnection(Socket clientSocket) throws IOException {
    //IP地址和端口来进行打印
    System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
    //对客户端发来的请求进行响应处理

        // 这里的针对 TCP socket 的读写就和文件读写是一致的
        try(InputStream inputStream=clientSocket.getInputStream()){
          try(OutputStream outputStream=clientSocket.getOutputStream()) {
    // 循环的处理每个请求, 分别返回响应
    Scanner input = new Scanner(inputStream);
    while(true){
        //读取请求
        //已经读取完,不会再输入新的内容
        while(!input.hasNext()){
            //返回客户端的ip地址以及端口号
            System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
              break;
        }
        // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
        //对请求进行处理
        String request=input.next();
        // 2. 根据请求, 计算响应
        String response = process(request);
        // 3. 把这个响应返回给客户端
        // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
        //读取返回响应所输入的内容用printWriter包裹,返回
        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.println(response);
         // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果.
        printWriter.flush();
        //并打印出客户端的地址,端口号,以及请求和响应的内容
        System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort(), request, response);

    }
}
        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 要关闭,因为资源时有限的,一个服务器不止为1个客户端进行服务,用了之后要及时关闭
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}
public  String process(String request){
    return request;
}


    public static void main(String[] args) throws IOException {
        TCPServer tcpServer=new TCPServer(9090);
        tcpServer.start();
    }
}

结果如下:

此时我们会发现,如果是一个客户端的话,与服务器之间的连接是没有问题的,如果是多个客户端,就不能够进行连接。那这到底是为什么呢?

但是显然在我们实际中,这样是不可取的,大大浪费了服务器的资源,所以我们引入了线程池,或者多线程来解决这个问题,代码如下。

4.3.1用多线程进行优化

代码如下:(对服务器部分进行修改)

package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer {
    private ServerSocket serverSocket = null;
public TCPServer(int port) throws IOException {
    serverSocket=new ServerSocket(port);//给服务器指定端口号
}
public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){
        // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
        // accept 就是在 "接电话", 接电话的前提是, 有人给你打了, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
        // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
        // 进一步讲, serverSocket 就干了一件事, 接电话
        //进一步来说是建立了连接然后应答上,用clientSocket来进行后续操作
        Socket clientSocket = serverSocket.accept();
        //使用多线程:

        Thread t = new Thread(()->{
            try {
                processConnection(clientSocket);//这样之后每一个客户端的连接就从串行变成并行的了,然后每一个客户端的连接就可以成功了
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        t.start();
    }
}
public  void  processConnection(Socket clientSocket) throws IOException {
    //IP地址和端口来进行打印
    System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
    //对客户端发来的请求进行响应处理

        // 这里的针对 TCP socket 的读写就和文件读写是一致的
        try(InputStream inputStream=clientSocket.getInputStream()){
          try(OutputStream outputStream=clientSocket.getOutputStream()) {
    // 循环的处理每个请求, 分别返回响应
    Scanner input = new Scanner(inputStream);
    while(true){
        //读取请求
        //已经读取完,不会再输入新的内容
        while(!input.hasNext()){
            //返回客户端的ip地址以及端口号
            System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
              break;
        }
        // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
        //对请求进行处理
        String request=input.next();
        // 2. 根据请求, 计算响应
        String response = process(request);
        // 3. 把这个响应返回给客户端
        // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
        //读取返回响应所输入的内容用printWriter包裹,返回
        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.println(response);
         // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果.
        printWriter.flush();
        //并打印出客户端的地址,端口号,以及请求和响应的内容
        System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort(), request, response);

    }
}
        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 要关闭,因为资源时有限的,一个服务器不止为1个客户端进行服务,用了之后要及时关闭
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}
public  String process(String request){
    return request;
}
    public static void main(String[] args) throws IOException {
        TCPServer tcpServer=new TCPServer(9090);
        tcpServer.start();
    }
}

4.3.2用线程池进行优化

对服务器代码进行修改:

package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.*;
public class TCPServer {
    private ServerSocket serverSocket = null;
    public TCPServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);//给服务器指定端口号
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService pool=Executors.newCachedThreadPool();
        while(true){
            // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
            // accept 就是在 "接电话", 接电话的前提是, 有人给你打了, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
            // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
            // 进一步讲, serverSocket 就干了一件事, 接电话
            //进一步来说是建立了连接然后应答上,用clientSocket来进行后续操作
            Socket clientSocket = serverSocket.accept();
            //对接收的响应进行处理
           //通过线程池来实现
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    public  void  processConnection(Socket clientSocket) throws IOException {
        //IP地址和端口来进行打印
        System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //对客户端发来的请求进行响应处理

        // 这里的针对 TCP socket 的读写就和文件读写是一致的
        try(InputStream inputStream=clientSocket.getInputStream()){
            try(OutputStream outputStream=clientSocket.getOutputStream()) {
                // 循环的处理每个请求, 分别返回响应
                Scanner input = new Scanner(inputStream);
                while(true){
                    //读取请求
                    //已经读取完,不会再输入新的内容
                    while(!input.hasNext()){
                        //返回客户端的ip地址以及端口号
                        System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
                    //对请求进行处理
                    String request=input.next();
                    // 2. 根据请求, 计算响应
                    String response = process(request);
                    // 3. 把这个响应返回给客户端
                    // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
                    //读取返回响应所输入的内容用printWriter包裹,返回
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果.
                    printWriter.flush();
                    //并打印出客户端的地址,端口号,以及请求和响应的内容
                    System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);

                }
            }
        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 要关闭,因为资源时有限的,一个服务器不止为1个客户端进行服务,用了之后要及时关闭
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public  String process(String request){
        return request;
    }
    public static void main(String[] args) throws IOException {
        TCPServer tcpServer=new TCPServer(9090);
        tcpServer.start();
    }
}

最后用TCP实现一个翻译功能(服务器作出一定修改,客户端不改)

package TCP;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
public class TcpDictSever extends TCPServer{
    private HashMap<String,String> map = new HashMap<>();
    public TcpDictSever(int port) throws IOException {
        super(port);
        map.put("cat","猫");
        map.put("pig","猪");
        map.put("dog","狗");
    }
    @Override
    public String process(String request) {
        return map.getOrDefault(request,"当前词组无法找到!");
    }
    public static void main(String[] args) throws IOException {
        TcpDictSever tcpDictSever=new TcpDictSever(9090);
        tcpDictSever.start();
    }
}

感谢观看~

标签: 网络 tcp/ip 服务器

本文转载自: https://blog.csdn.net/weixin_58850105/article/details/124442614
版权归原作者 红苹果超好吃 所有, 如有侵权,请联系我们删除。

“网络编程套接字”的评论:

还没有评论