网络编程 -- socket 套接字

socket 套接字


本文就来学习一下 网络编程, 既然谈到了网络编程,那么要如何进行呢 ?


这里的核心就是 Socket API

在这里插入图片描述


Socket 就是操作系统,给应用程序,提供的网络编程 API


在上篇文章说过,在网络分层内传输层及其以下都是属于操作系统内核实现的,应用层是在应用程序内的,这里 socket api 就相当于 站在传输层的角度 与应用层进行交互的 ( 可以认为 socket api 是和 传输层密切相关的 )。


回忆一下 : 说过的传输层,是不是说过两个最核心的协议 UDP , TCP .


因此针对 传输层 中的 两种 核心的协议 , socket 推出了两种风格 API , 也就是 UDP 版本的 和 TCP 版本的 。(其实这里还有第三种风格 unix 域套接字, 只不过没人使用了,知道了也没啥用 ,这里就过个眼熟) .


这里我们想要学习 UDP 和 TCP 两种风格的API ,那么就需要对 UDP 和 TCP 这两种协议 有个简单的认识 , 所以下面就先来简单了解一下这两种协议

UDP 和 TCP 区别


这里先来看看 UDP 和 TCP 两种协议的 区别


UDP :

  • 无连接
  • 不可靠传输
  • 面向数据报
  • 全双工


TCP :

  • 有连接
  • 可靠传输
  • 面向字节流
  • 全双工

1. 有连接 VS 无连接


有连接 : 使用TCP 协议 ,需要连接成功才能够建立通信 ,连接建立需要对方来 接收 ,如果连接没建立好 ,通信不了. (简单来说就是 双方 需要数据传输的时候需要先建立联系,没有建立好就无法进行传输).


好比 打电话 :假设现在我要给女神打电话 , 拨号口,等待女神接听 ,等听到女神的 喂 , 就说明此时连接建立成功,可以进行交流了, 如果女神挂掉了,也会有对方正在繁忙的语音提示此时就说明没有连接建立成功,不能进行通话。


无连接 : 双方无需建立建立,直接发送消息 。


好比 : 发微信 发 qq 等 , 假设我 给女神发送早安 , 女神可能看到了也可能没看到,或者看到了但是不想回我们 。此时我们并不能知道了 女神 看没看到消息 ,这种情况就是 无连接 ,


总的来说 :TCP 就是要求双发先建立连接,连接好了,才能进行传数据。 而 UDP,直接传输数据,不需要双方建立连接。

2. 可靠传输 VS 不可靠传输


可靠传输 : 发送方知道 接收方 有没有接收到数据


注意 : 网络环境天然是复杂的 , 不可能保证传输的数据 100% 就能到达 , 比如说 拔网线 ,即便我们厉害 也不顶用。


所以可靠传输 , 传输的数据并不是百分百就能够成功的, 关键是看这里是否能 **感知 **到 传输的数据是否成功到达。


这里打电话就是可靠传输 : 比如说我们打电话,对方接听了, 我们说了一大堆事情,对方是否听到了我们是可以知道的,比如对方一直没有回应,我们就可以问一句,你听到了吗, 对方回应,此时我们就能够知道对方听到了 。


不可靠传输 : 发送方不知道接收方有没有接收到数据


这里的不可靠传输,同样于发微信或发qq消息 一样


还是发消息给女神,想要邀请女神来吃麻辣烫 ,假设将消息发送出去,

对方可以能不在线,此时无法看到消息,

对方可能太忙 (毕竟是女神吗,咋可能就一条舔狗呢) 。

对方看见了 但是不回你 (你都是舔狗 ,那不就是可有可无的吗 ,人家不会挺正常)

对方看见了 , 并接收了你的邀请消息没有发送成功 等


此时不管是那种情况,我们都无法知道 ,此时就相当于不可靠传输.


另外 : 有些软件,比如抖音 ,聊天有已读功能,此时发送消息,是能够知道对方是否接收到了 (看到了显示已读 ) 这种已读功能的就相当于可靠传输, QQ 微信 就没有这种已读功能, 就可以认为是不可靠传输 。


注意 : 可不可靠,和有没有连接没有任何关系


总的来说 : 你发送的消息 , 对方是否看的到 自己是知道的 心里有底对方看到了 ,那么就是 可靠的, 心里没底 , 不知道对方是否收到 那么就是不可靠的

3. 面向字节流 VS 面向数据报


面向字节流 : 数据传输就和 文件读写二 类似 “流式” 的


还记得 文件 IO 那片 吗, 这里的流 其实就是一种比喻的 说法,我们的数据可以 分多次发送

比如 : 发送 100 个字节的数据

可以采用 一次 传 100个字节, 一次 传 10 个字节 分 10次 ,一次传1个字节 分 100次 … 这里是不是就像水流一样。


面向数据报 : 数据传输 以 一个一个的 数据报 为基本单位 (一个数据报可能是诺干个字节,带有一定的格式 )

4. 全双工


全双工 : 一个通信通道,可以双向传输 (既可以发送,也可以接收)


图示 : 半双工 (了解即可)

在这里插入图片描述


简单了解完,UDP 和 TCP 的区别之后,下面 就来学习一下 ,两种风格的 Socket API 的使用 。


这里更详细的 UDP 和 TCP 协议 ,在后面的文章种会说到 ,不急 ,本主要还是对于 Socket 的使用.

UDP 数据报套接字编程


这里我们就基于 UDP 来编写一个简单的客户端服务器的网络通信程序


想要实现这个, 先来学习一下两个类 .


图示 :

在这里插入图片描述


1.DatagramSocket


DatagramSocketUDP Socket,用于发送和接收UDP数据报。


DatagramSocket 构造方法:

方法签名方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口
(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用
于服务端)


DatagramSocket 方法:

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacketp)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字


2.DatagramPacket


DatagramPacketUDP Socket 发送和接收的数据报。

DatagramPacket 构造方法:

方法签名方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在
字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节
数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号


DatagramPacket 方法:

方法签名方法说明
InetAddressgetAddress()从接收的数据报中,获取发送端主机IP地址;
或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;
或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据


有了对这些方法的认识,就可以 写一个最简单的 UDP 版本的 客户端服务器程序 回显服务器 echo server 。


一个普通的服务器 : 收到请求, 更具请求计算响应 , 返回响应。


这里 就好比 去小馆子里 吃个炒粉, 我们向店长说来份炒粉,店长听到了 ,就将这个请求告诉了厨师 ,厨师接收到了请求,就去炒粉(更具请求计算出响应) ,然后将炒出来的粉 交给我们 (返回响应) .


这里我们的回显服务器 (echo server) 会省略其中的 “根据计算计算响应” 这个步骤, 相当于 请求是啥,就返回啥 (当前这个代码没有实际的业务, 这个代码也没啥太大作用 和意义 ,只是展示了 socket api 基础用法).作为一个真正的服务器, 一定是"根据请求计算响应 " 这个环节是最最重要的 , 这里的 根据请求计算响应 其实就相当于业务逻辑。


下面就来编写代码 :


图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


图三 :

在这里插入图片描述


图四 :补充一点小细节
在这里插入图片描述


到此 UDP 服务器 就写完了


附上 : UDP 服务器代码 ,注意这里最后加了一个启动服务器的 main 方法


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

// UDP 版本的回显服务器
public class UdpEchoServer {

    // 网络编程 , 本质上是要操作网卡
    // 但是网卡不方便直接操作,在操作系统内核中, 使用了一种特殊的叫 "socket" 这样的文件来抽象表示网卡
    // 因此进行网络通信 , 势必需要现有一个 socket 对象
    private DatagramSocket socket = null;

    // 对于服务器来说 , 创建 socket 对象的同时 , 要让他绑定一个具体的端口号,
    // 服务器一定要关联上一个具体的端口号 !!!
    //服务器是网络传输中 ,被动的一方,如果是操作系统随机分配的端口号,此时客户端就不知道
    //  这个端口是啥了,也就无法进行通信了 !!!

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }


    public void start() throws IOException {
        System.out.println("启动服务器");

        // 服务器不是只给一个客户提供服务就完了 ,需要服务很多客户端
        // 这里就可以使用循环
        while (true) {

            // 只要有客户端过来,就可以提供服务 (只要有人来买烤肠,我们就可以卖给他们)

            // 1. 读取客户端发来的请求  (烤肠不止一种,等待客户进行挑选,我们根据客户的需求 来烤那种肠)

            //receive 方法的参数是一个输出型参数,需要先构造好一个空白的 DatagramPacket 对象 ,交给 receive 来进行填充。

            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);

            socket.receive(requestPacket);

            // 此时这个 DatagramPacket 是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来, 构造成一个字符串

            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());


            // 2. 根据请求计算响应 , 由于此时是一个回显服务器, 响应和请求相同

            String response = process(request);

            // 3.把响应写回到客户端  , send 的参数也是 DatagramPacket ,需要把这个 Packet 对象构造好

            // 此处构造的响应对象,不能是用空的字节数组构造了,而是要使用响应数据 (response字符)

            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());

            // 将 计算出来的响应发送给客户端
            socket.send(responsePacket);

            // 4. 打印一下 ,当前这次请求响应的处理中间结果
            System.out.printf("[%s : %d] request : %s; response: %s\n" ,requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request,response);

        }
    }

    // 这个方法就表示 "根据请求计算响应"
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        // 端口号的在指定,可以随便指定
        // 1024 -> 65535 这个范围内 随便挑一个数组即可  , 后面会说为啥 在这个范围内
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();

    }

}


其实上面这个服务器的代码并不太复杂核心代码也就那么几行 , 这里我们的主要任务 就是 理解服务器的工作流程。

1.读取请求并解析

2.根据请求计算响应

3.构造响应并写回给客户端 .


下面就来写 客户端代码


图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


图三 :

在这里插入图片描述


附上客户端代码 :


import java.io.IOException;
import java.net.*;
import java.util.Scanner;

// UDP 版本的 回显客户端
public class UdpEchoClient {

    // 同样需要先创建一个 socket对象 , 来操作网卡
    private DatagramSocket socket = null;

    // 服务器 的 IP 地址
    private String serverIp;

    // 服务器 的 端口号
    private Integer serverPort;

    // 一次通信 ,需要由两个 ip , 两个端口
    // 客户端的 ip 是 127.0.0.1 已知.
    // 客户端的 port 是系统自动分配的
    // 服务器 ip 和 端口 也需要告诉客户端 , 才能顺利把消息发给服务器.
    public UdpEchoClient(String serverIp, Integer serverPort) throws SocketException {

        socket = new DatagramSocket();

        //这里需要 给 服务器的 ip 地址 和 端口号 ,相当于 买家了商品 ,需要提供 地址和电话 , 如果没有卖家咋发货呢?
        this.serverIp = serverIp;

        this.serverPort = serverPort;
    }

    // 启动 客户端
    public void start() throws IOException {

        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);

        while (true) {
//        1. 从控制台读取发送的数据.
            System.out.println("-> ");

            String request = scanner.next();

            if (request.equals("exit")) {

                System.out.println("goodbye");
                break;
            }

//        2. 构造成 UDP 请求,并发送.
            // 构造这个 Packet 的时候 ,需要把 serverIp 和 port 都传入过来, 但是此处 IP 地址需要填写一个 32位的整数形式
            // 上述的 IP 地址是一个字符串 , 需要使用 InetAddress.getByName 来进行一个转换 .

            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            // 构造好了请求发送
            socket.send(requestPacket);

//        3. 读取服务器的 UDP 响应,并解析.

            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);

            socket.receive(responsePacket);

            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());

//        4. 把解析好的结果显示出来.
            System.out.println(response);
        }


    }

    // 最后添加启动方法
    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }

}


下面来执行一下 看看下效果 :

在这里插入图片描述


可以看到我们的客户端和服务器程序 ,是在我们自己的电脑上运行的, 而实际上,网络存在的意义,是跨主机通信.


那么我们的层序是否可以做到 跨主机通信的呢?


这里是可以的, 只不过需要外网 IP ,这里可以将服务器的代码 上传到 云服务器上,让后修改 客户端的 ServerIp 改为云服务器的, 就能够进行跨主机通信的。


这里就不演示了 ,比较麻烦 . 后面我们学习了 Linux , 购买了云服务器可以自己尝试一下.


下面继续 , 这里我们写的是回显服务器,缺少业务逻辑,这里就可以在上述代码的基础上稍作调正,实现一个 “查词典” 的服务器. (根据英文单词 , 翻译成中文解释)


附上代码 : 因为大部分代码是一样的 ,这里就可以通过继承来 ,重写 process 方法 , 来书写逻辑即可 .


// 对于 DictServer 来说 和 EchoServer 相比,大部分的东西都是一样的

// 主要是 "根据请求计算响应" 这个步骤不太一样 .

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 设置内容
        dict.put("cat", "小猫");

        dict.put("dog", "小狗");

        dict.put("error", "错误");

        // 当然,这里可以无限多的设置键值对. .

    }


    @Override
    public String process(String request) {

        // 重写我们的 process 方法, 添加逻辑即可

        // 查词典的过程

        return dict.getOrDefault(request, "当前单词没有查询到结果!");

    }

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


这里关于上面这个程序还有一个小知识点 这里来补充一下 ,

针对上述的层序,来看看端口冲突 是啥效果 .

这里一个端口只能被一个进程使用 ,如果由多个使用,就不行。

这里具体来看一下咋样个不行法 。

在这里插入图片描述


这里 UDP socket api 使用就到这里 ,下满来 看看 TCP 的 socket api 的使用 .

TCP 数据报套接字编程


关于 TCP socket api 的这个 API , 难易程度 ,可以取决于你之前学习 IO 章节的掌握程度, 如果比较熟悉那么就容易, 如果不太熟悉或没学过,那么就可能有点困难.


这里就不多说了,下面就来愉快的学习一下 TCP 的 socket api 。


同样的这里 与 UDP 一样涉及到两个类


图示 :

在这里插入图片描述


ServerSocket API


ServerSocket 是创建TCP服务端Socket的API

ServerSocket 构造方法

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口


ServerSocket 方法

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,
返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close ()关闭此套接字


Socket API

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

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。


Socket 构造方法

方法签名方法说明
Socket(String host , int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接


Socket 方法

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流


看完这些 类和方法, 下面通过 写一个 TCP 版本的 回显服务器来 熟悉他们.


图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


附上代码 :

package T_J4.T_1_23;

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 TcpEchoServer {

    private ServerSocket serverSocket = null;


    public TcpEchoServer(int port) throws IOException {

        serverSocket = new ServerSocket(port);

    }

    public void start() throws IOException {
        System.out.println("启动服务器");

        while (true) {

            // TCP 是有连接的, 因此不能一上来就读取数据, 需要先建立连接

            // 使用这个 clientSocket 和 具体的客户端进行交流 (建立连接).
            Socket clientSocket = serverSocket.accept();

            processConnection(clientSocket);
        }
    }

    // 使用这个方法来处理一个连接.
    // 这一个连接对应到一个客户端 ,但是这里可能会涉及到多次交互.
    private void processConnection(Socket clientSocket) {

        System.out.printf("[%s : %d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());

        // 基于上述 socket 对象 和 客户端进行通信

        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {

            // 由于要处理多个请求和响应 , 这里也是使用循环来处理
            while (true) {
//                1. 读取请求

                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 判断是否有下一个数据 , 没有下一个数据说明读完了 (客户端关闭连接) ;
                    System.out.printf("[%s : %d] 客户端下线\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                // 注意 !! 此处使用 next 是一直读取到换行符 / 空格 / 其他空白符结束 , 但是最终返回结果里不包含上述 空白符 .
                String request = scanner.next();

//                2. 根据请求构造响应
                String response = process(request);

//                3. 返回响应结果
//                outputStream 没有 write String 这样的功能 , 可以把 String 里的字节数组拿出来,进行 写入 .
                // 也可以用字符流来转换一下
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response); // 此处使用println 让结果中带有 \n 换行. 方便 对端来接受解析
                printWriter.flush(); // flush 用来刷新缓冲区 , 保证当前写入的数据, 确实是发送出去了

                System.out.printf("[%s : %d] request : %s , response %s \n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);

            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // finally 中的代码一定会被执行,所以 close 放在这里更加合适
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {
        return request;
    }

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

        TcpEchoServer server = new TcpEchoServer(9090);

        server.start();
    }
}


这里 TCP版本的服务器写完了,下面来写 TCP 版本的客户端.


图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


最后加上 main 方法 来启动 客户端 。


附上代码 :


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 TcpEchoClient {

    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {

        // Socket 构造方法 , 能够识别 点分十进制格式的 IP 地址 . 比 DatagramPacket 更方便 .
        // new 这个对象的同时 , 就会 进入 TCP 的连接操作.
        socket = new Socket(serverIp, serverPort);
    }


    public void start() {
        System.out.println("客户端启动!");

        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {

                // 1. 先从键盘上读取用户输入的内容

                System.out.printf("> ");

                String request = scanner.next();

                if (request.equals("exit")) {

                    System.out.println("goodbye");

                    break;
                }
                // 2. 把读到的内容构造成请求,发送给服务器

                PrintWriter printWriter = new PrintWriter(outputStream);

                printWriter.println(request);

                // 此处加上一个 flush 保证数据确实发送出去了
                printWriter.flush();

                // 3. 读取服务器的响应

                Scanner respScanner = new Scanner(inputStream);

                String response = respScanner.next();

                // 4. 把响应内容显示到界面上 .
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
       
        client.start();
    }
}


这里执行以下, 来看一下效果如何 :

在这里插入图片描述


效果看完,这里提一个小问题 :

在这里插入图片描述


看完这个小问题 ,其实我们的代码还有一个大问题, 这里来演示一下 问题所在 :


图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


优化 : 这里使用多线程,可以解决 一个服务器 只能处理一个客户端的问题, 但是客户端特别多,很多客户端频繁的来进行连接 , 此时就需要频繁的创建 / 销毁线程。


之前说过 创建 线程和 销毁线程 也是会消耗资源的 (虽然比 多进程 少) ,这里 频繁的创建和销毁 开销也是比较大的 。


那么 你能 回忆起之前学过的 知识 来优化它 吗 ?

想必你一定有了答案 , 没错就是线程池 , 下面再次调整我们的代码 :

在这里插入图片描述


补充一下 : TCP 的短链接 和 长连接

在这里插入图片描述


另外 : 这里虽然使用了线程池,但是还不够


如果 客户端非常多,而且客户端连接迟迟不肯断开,就会导致我们的机器上有很多线程 . (我们使用的线程池是自增的,觉得需要线程了就会自己创建) .


按照上面这种迟迟不肯断开的情况下 , 如果一个服务器 有几千个客户端,就的是几千个线程,如果是几万个客户端 , 就会有几万个线程 。


此时这种情况就会对我们的机器产生很大的负担 。


那么如何确解决这个问题呢 ?


方法一 : 采用多开服务器 ,但是多开服务器意味着成本的增加 , 得多加钱,明显是不好的选择 。


方法二 : 使用 IO 多路复用 (IO 多路转接)


关于这里的问题 ,其实就是 单机(单个服务器)如何支持更大量客户端的问题 , 这个问题 也可以称为 C 10M 问题.


C 10k 问题 : 单机处理 1w个客户端 , 这里 1w 个 ,我们通过 一些方法 ,还是能够解决的 。

C 10M 问题 : 但其处理 1Kw个 客户端 ,这里 不是说单机真的能处理 1kw 个 客户端,只是表达说 客户端比 C 10k 这里多很多 。


出现 C 10M 问题 的本质 就是 机器 承担不了 这么线程的开销 (每个线程处理一个客户端) ,


好比 你开了一家公司 , 你的公司 需要服务很多客户 ,然而你给每个客户都有一个专属的客服 , 因为你雇佣了很多客服 ,导致 月底了 你发不出工资 ,此时 你的公司自然就宣布 破产了。 。


这里解决办法很容易 ,不用雇佣那么多客服,而每名客服对接多名用户即可 ,这样就大大的减少了 客服人员 。


放在我们这里也是同理 ,我们想要一个线程处理多个客户端 连接,就可以采取 IO 多路复用 (IO 多路转接) , 别觉得这个东西很高大上,其实再生活中都接触过 。


比如说 : 买饭 , 假设 你 想吃 杂粮煎饼 , 你的弟弟想吃 牛肉面 , 你的 妹妹 想吃 肉夹馍 。


此时就有两种做法 :

1.采用单线程的方式 , 你作为老大 ,一个人去买 ,先去买你吃的 杂粮煎饼 ,然后去帮妹妹买 肉夹馍,最后卖牛肉面 .

2.采用多线程的方式 , 你说 要吃自己去买 ,然后就各买各的 。

3.还是采用单线程的方式 , 还是你去买饭 ,但是这次不同了,你来到 卖杂粮煎饼的摊子前 ,说老板给我来份杂粮煎饼 ,等会做好了我来那,然后你就跑到肉夹馍的摊子 如法炮制, 最后来到了 牛肉面的摊子 ,点了份牛肉面,此时那个好了就去拿那个 。


这三种方式 , 明显是第三种更好 ,我们的IO 复用就是这种操作 , 虽然是单线程 ,但是充分的利用了等待时间,再等待的过程中做其他事情。


这里我们IO 过程中 ,并不会一直持续的 ,IO过程也会有等待, 所以 IO 多路复用就抓住了这一点, 将这个等待时间充分利用起来, 做别的事情 .


知道了 IO 复用是啥 ,那么来处理 C 10M问题 。


思路 : 这里就可以 给线程安排个集合 ,这个集合就放了一堆连接 ,线程就负责监听这个集合,那个连接有数据来了 , 线程就来处理那个连接 … 这个其实就应用了一个事实 , 虽然连接很多 ,但是这些连接的请求并非严格意义上的同时,总还是有先有后的 。


解决 :多路复用 在操作系统里 ,提供了一些原生 API select , poll , epoll , 在 java 提供了一组 NIO 类 就封装了上述多路复用的 API , 我们就可以使用这个 类来解决问题


这里 思路 大于 API 的使用 , 这里就不继续下去了 ,如果感兴趣 可以自己去看看这个 NIO 类 .


到此 本文结束 , 下文预告 网络原理 。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值