网络编程- Socket-BIO

本文深入探讨了Java网络编程中的BIO( Blocking IO)模型,解释了其工作原理和系统调用过程。在BIO模型中,每个线程对应一个连接,当连接数量增加时,可能导致大量线程创建,带来资源浪费和效率问题。为解决这个问题,文章引入了NIO(Non-blocking IO)的概念,概述了其非阻塞同步IO模型的优势,为后续的NIO讲解埋下伏笔。
摘要由CSDN通过智能技术生成

网络编程- Scoket-BIO

在这里插入图片描述
上图时对socket做一个简单介绍, 具体的实现这里就说了。主要将一下java网络编程几大IO模型实现、原理、演变,IO模型分别为BIOOS级别的NIO(NONBLOCKING IO)、NIO java jdk1.4升级的NEW IO

BIO模型

前提知识:想要彻底的了解IO模型及其原理和演变,你需要了解一下操作系统底层相关的一些系统调用知识,我上篇文章介绍过了,参考学习,有错误的望指出。系统调用|内核程序

想必大家都知道IO操作肯定需要系统调用的,因为网络传输势必会用到网卡硬件,这就需要内核程序的参与,接下来详细介绍下整个BIO实现以及原理模型

大家先有个简单网络模型:
在这里插入图片描述

客户端和服务器端建立链接请求的时候肯定是调用了系统调用,那么系统调用又是怎么做的呢?
一下主要讲解服务器端
在这里插入图片描述
先抓包看下内核:
在这里插入图片描述
以上3和系统调用就是我们写建立连接的时候系统调用做的事。

在这里插入图片描述
系统调用通过这三个函数帮我们建立起服务器链接,并开启监听,监听客户端链接事件。并通过系统调用函数 accept() 方法接受客户端链接,因为一般需要链接多个客户端,所以需要用 while 无线循环去接受客户端。
在这里插入图片描述
但是accept方法是阻塞的,所以必须要等有至少一个客户端链接进来的时候才会继续下一步。(重点)
当有客户端链接进来的时候,服务器端需要对连接进行轮询去获取客户端发来的信息,通过 recv函数获取客户端数据。
在这里插入图片描述

以上是我们在服务器端建立链接,我们所需要的系统调用函数。
因为accpet 和 recv 都是阻塞函数,所以会产生一个问题:
如果客户端一直不发信息过来,系统会一直阻塞在那,遇到多个客户端链接时就会一直阻塞等待了,因为你的代码就一直阻塞在那了。
那么怎么解决呢,所以在我们java程序中我们会为每一个链接创建一个新的线程,去专门处理自己的链接发送的消息。
](https://img-blog.csdnimg.cn/20210530224635433.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMxMTQyMjM3,size_16,color_FFFFFF,t_70)

代码演示:
服务器端:

package com.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 基于TCP协议 网络通讯编程(网络中客户端于服务器io流传输)
 * Created by Administrator on 2021/5/16.
 * Socket 服务器端
 * 1.
 */
public class SocketServerC {
    public static void main(String[] args) throws IOException {
        // 1. 创建server服务器,指定端口号,形成ip+端口构成唯一能识别的标识符套接字
        ServerSocket serverSocket =new ServerSocket(5154);

        System.out.println("============等待连接============");

        // 监听客户端连接,该方法线程阻塞,如果没有客户端连接,就阻塞一直等待。
        // 优化:1.让服务器一直处理链接状态不要,链接到了就执行完关闭链接while循环
        //      2. 因为下面很多地方线程阻塞,多个线程链接服务器时,产生效率问题,链接成功之后:使用多线程处理每一个链接
        while(true) {
            final Socket accept = serverSocket.accept();    // 阻塞, 系统调用accept函数
            System.out.println("============成功连接了============" + accept.getInetAddress());
            new Thread(new Runnable() {   
                public void run() {
                    try {
                        // 连接成功之后,读取客户端传来的网络IO对象,输入流
                        InputStream is = accept.getInputStream();

                        // 读取数据流,打印数据
                        // 定义一个缓存数组
                        byte[] bytes = new byte[1024];
                        int len = 0;
                        // 系统调用
                        while (-1 != (len =is.read(bytes))){   // 阻塞--一直读取流通道,获取流数据,直到发现结束符才会跳出(File文件操作不会堵塞,因为读到最后会遇到EOF的)
                            // 打印客户端发送过来的信息
                            System.out.println("客户端:" + new String(bytes, 0, len));
                            // 设置跳出条件,len长度小于bytes长度,这样就没办法持续读取流通道数据了,因为跳出了
                            if(len<bytes.length){
                                 break;
                            }
                        }
                        // 向客户端发送信息
                        OutputStream os = accept.getOutputStream();

                        os.write("我收到你的信息了".getBytes());
                    }catch (IOException e){
                        System.out.println(e);
                    }
                }
            }).start();
        }
     }
}

客户端:

package com.socket;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 *  * 基于TCP协议 网络通讯编程(网络中客户端于服务器io流传输)
 *  Created by Administrator on 2021/5/16.
 *  Socket 客户端
 */
public class SocketClientC {

    public static void SocketClientCC(Socket socket) throws IOException, InterruptedException {
        // 通过ip+端口号 套接字 建立tcp链接
        System.out.println("连接成功");
        // 向服务器端发送信息
        OutputStream os = socket.getOutputStream();
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            os.write(("服务我来链接你了..."+i+"").getBytes());
        }
        //获取服务器端发送过来的io输入流
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

        // 打印
        System.out.println("服务器端:"+new String(bytes,0,len));
        //socket.close();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Socket socket = new Socket("127.0.0.1",5154);
        SocketClientCC(socket);
    }
}

以上的实现方式就是传统的IO模式 即 BIO同步阻塞IO模型.

总结:

  1. BIO 每线程对应每一个链接
  2. 使用多线程可以接受很多了链接了

缺点:
如果线程数很大时,需要创建很多的线程去实现读写。

  1. 线程内存浪费。
  2. cpu调度消耗
    根本原因: accept 和 recv 阻塞(java中:accept 和 read 阻塞)

既然问题找到了,就肯定需要解决,既然链接多了需要很多线程不行,原因就是因为你阻塞了,那要是accept 和 read不阻塞了,那是不是就一个线程就行了。这样问题是不是就解决了。

上述的解决思想就是NIO ( NONBLOCKING IO ) 的实现思想,切记此处说的NIO 是站在操作系统层面的非阻塞同步IO模型(不是用多路复用器),而不是jdk提供的NEW NIO,此包用Selector对多路复用器的封装。下一篇讲解!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Survivor001

你可以相信我,如果你愿意的话

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值