【netty系列-02】深入理解socket本质和BIO底层实现

Netty系列整体栏目


内容链接地址
【一】深入理解网络通信基本原理和tcp/ip协议https://zhenghuisheng.blog.csdn.net/article/details/136359640
【二】深入理解Socket本质和BIOhttps://zhenghuisheng.blog.csdn.net/article/details/136549478

深入理解socket本质和bio底层实现

  • 一, Socket本质和初识BIO
    • 1,Socket
    • 2,BIO
      • 2.1,单线程场景
      • 2.2,多线程场景

一, Socket本质和初识BIO

在上一篇中,讲解了网络通信的基本原理,以及tcp/ip层与应用层之间的关系,可以得知在 OSI 七层模型中,数据需要先通过应用层将数据转成报文,然后将报文从应用层中传向传输层,封装成报文段,依次将数据封装到网络层,数据链路层,物理层,最后再通过以太网,光纤将数据传到到对应的主机上。

在这里插入图片描述

在网络编程中,由于tcp和ip已经有了对应的协议,因此在tcp层往下只需遵守对应的协议即可,因此在实际开发中,只需要将数据从应用层发送到传输层即可。

因此在操作系统的底层,封装了一个Socket,类似于一个中间件,用于应用层和传输层的TCP/IP协议族之间的通信,该中间层将所有与传输层连接的注意事项全部封装好,让开发者在开发无需关心底层的具体实现,更加的关注业务即可。如一些数据丢包的网络重传,滑动窗口等数据都会提前封装好。socket类似于sqlSession的功能,是一个门面模式,主要用于接收和转发,不做具体的执行功能。

在linux操作系统的源码中,会有一个 socket.c 的文件。在该文件中,里面已经封装了了应用层和tcp协议之间的细节,如如何建立连接,如何接受连接,如何监听,如何绑定等等都已经实现。因此对于网络应用程序来说,只需要与Socket进行交互即可。

1,Socket

客户端发送一条 hello word 到另一个客户端的流程如下,数据从客户端A的应用层再到传输层,再到网络层,再到数据链路层,再到物理层进行层层封包,通过以太网到客户端B的物理层,数据链路层,网络层,传输层,应用层进行层层解析,才能将数据进行解析出来

在这里插入图片描述

对于开发人员来说要实现层层的细节,肯定是不友好的。因此在操作系统底部,就为我们封装了一套socket,内部已经帮我们实现了tcp等协议的细节,让开发者更加的注重于业务上面的开发,其流程可以简化如下

在这里插入图片描述

让开发者只需考虑应用层的业务代码实现,不需要考虑底层的实现细节。因此在网络编程中只需要关注三件事,就是客户端和服务端的连接、读网络数据、写网络数据

2,BIO

2.1,单线程场景

在原生网络编程中,使用BIO编程的比较多,BIO指的是 Blocking IO 阻塞式io,顾名思义,就是在进行io时,会出现阻塞的情况。

先看一段原生通过BIO来实现网络编程的代码,来了解BIO的基本使用和被阻塞的时机,先看一段服务端的代码。改代码中创建一个serverSocket,用于实现应用层和tcp层之间的交互,随后绑定了一个端口8089,当有客户端来访问这个服务的这个端口时,就会做出响应

package com.zhs.netty.bio;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;/*** @author zhenghuisheng* @date 2024/3/7 22:31*/
public class BioServer {public static void main(String[] args) throws IOException {//创建一个socketServerSocket serverSocket = new ServerSocket();//服务端监听的端口号serverSocket.bind(new InetSocketAddress(8089));System.out.println("服务端开始监听");try{while(true){//监听事件Socket socket = serverSocket.accept();try{ObjectInputStream input = new ObjectInputStream(socket.getInputStream());ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());//客户端传入的数据String readData = input.readUTF();System.out.println("成功接收到了数据" + readData);output.writeUTF("已经接收到了" + readData);}catch (Exception e){e.printStackTrace();}finally {socket.close();}}}catch (Exception e){e.printStackTrace();}finally {serverSocket.close();}}
}

在启动这个服务端的时候,可以看出有如下信息打印,表示此时正被阻塞着,并且阻塞在这个accept的监听上

服务端开始监听

随后再编写一个客户端的代码。服务端中需要使用ServerSocket,在客户端中则需要使用Socket

package com.zhs.netty.bio;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;public class BioClient {public static void main(String[] args) throws IOException {//客户端启动必备Socket socket = null;//实例化与服务端通信的输入输出流ObjectOutputStream output = null;ObjectInputStream input = null;//服务器的通信地址InetSocketAddress addr = new InetSocketAddress("127.0.0.1",8089);try{socket = new Socket();socket.connect(addr);//连接服务器System.out.println("连接成功");output = new ObjectOutputStream(socket.getOutputStream());input = new ObjectInputStream(socket.getInputStream());System.out.println("Ready send message.....");/*向服务器输出请求*/output.writeUTF("zhenghuisheng");output.flush();//接收服务器的输出System.out.println(input.readUTF());}finally{if (socket!=null) socket.close();if (output!=null) output.close();if (input!=null) input.close();}}
}

在启动完客户端之后,可以发现客户端打印的信息如下

连接成功

而在服务端中,由于接收到了客户端的请求,在服务端中也会将阻塞的代码继续往下执行

服务端开始监听
成功接收到了数据zhenghuisheng

除了服务端没有客户端来连接时会阻塞之外,在已经有一个客户端来连接且没释放,再来一个客户端进行连接时,此时的客户端也会出现阻塞的情况,假设在服务端刚开启之后,第一个客户端进行连接时在以下的代码处打一个debug断点阻塞在哪

 output.writeUTF("zhenghuisheng");

此时第二个客户端来建立连接的代码如下,客户端这边不需要绑定具体的端口号,可以直接由操作系统进行分配即可,服务器的通信地址为刚刚设置的ip地址和端口号,目前设置的ip最地址为本地地址

package com.zhs.netty.bio;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;public class BioClient2 {public static void main(String[] args) throws IOException {//客户端启动必备Socket socket = null;//实例化与服务端通信的输入输出流ObjectOutputStream output = null;ObjectInputStream input = null;//服务器的通信地址InetSocketAddress addr = new InetSocketAddress("127.0.0.1",8089);try{socket = new Socket();socket.connect(addr);//连接服务器System.out.println("连接成功");output = new ObjectOutputStream(socket.getOutputStream());input = new ObjectInputStream(socket.getInputStream());System.out.println("Ready send message.....");/*向服务器输出请求*/output.writeUTF("zhenghuisheng2号");output.flush();//接收服务器的输出System.out.println(input.readUTF());}finally{if (socket!=null) socket.close();if (output!=null) output.close();if (input!=null) input.close();}}
}

此时客户端2打印的信息如下,就是处于连接成功的状态

连接成功

但是在服务端这边,并不能够感知到第二个服务端来连接,也不能够做出响应。由于双端都是通过socket来进行数据的传输,包括三次握手等等,而客户端2可以连接成功,表示客户端2的socket和服务端的socket已经连接成功了,但是socket是操作系统的资源,由于服务器与一个客户端连接的socket还未释放连接,因此此时的服务端还没有来得及去处理第二个socket,当第一个socket正式的处理完数据传输以及响应,完成四次挥手之后,才可以去处理第二个建立的socket

在这里插入图片描述

因此bio的阻塞就两个地方:

  • 服务端没有接收到客户端请求时会阻塞
  • 已有客户端再进行连接未释放时,新来的客户端连接也会被阻塞

2.2,多线程场景

如果仅仅只是在单线程中用BIO,那么拿过存在多个客户端连接服务端时,那么就会存在没被连接的客户端全部都被阻塞着,此时就是完全变成了串行执行,效率极其低下。但是也可以通过多线程去解决这个问题,每当一个客户端与服务端进行连接时,服务端就开启一个子线程去响应客户端的请求,而在实际开发中,一般都会通过线程池的方式去代替多线程,从而达到线程更好的管理和复用

如下面这段利用线程池的代码,每当一个客户端来进行连接时,就会通过线程池中的线程去执行这些任务

package com.zhs.netty.bio;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ServerPool {private static ExecutorService executorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());public static void main(String[] args) throws IOException {//服务端启动必备ServerSocket serverSocket = new ServerSocket();//表示服务端在哪个端口上监听serverSocket.bind(new InetSocketAddress(10001));System.out.println("Start Server ....");try{while(true){executorService.execute(new ServerTask(serverSocket.accept()));}}finally {serverSocket.close();}}//每个和客户端的通信都会打包成一个任务,交个一个线程来执行private static class ServerTask implements Runnable{private Socket socket = null;public ServerTask(Socket socket){this.socket = socket;}@Overridepublic void run() {//实例化与客户端通信的输入输出流try(ObjectInputStream inputStream =new ObjectInputStream(socket.getInputStream());ObjectOutputStream outputStream =new ObjectOutputStream(socket.getOutputStream())){//接收客户端的输出,也就是服务器的输入String userName = inputStream.readUTF();System.out.println("Accept client message:"+userName);//服务器的输出,也就是客户端的输入outputStream.writeUTF("Hello,"+userName);outputStream.flush();}catch(Exception e){e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}
}

但是也会出现一个问题,就是最大的连接数就是和核心线程数以及阻塞队列,核心线程数有关,根据io密集型和cpu密集型去考虑核心线程数的大小,而为了不丢失连接,阻塞队列肯定是越大越好,因此一般这种情况的最大连接数就是核心线程的个数,在一定的并发上会有一定的限制。

并且如果是io密集型的传输,如涉及大文件的io传输这种,那么整体效率就会底下,严重影响客户端的体验

由于BIO会存在着阻塞的缺陷以及并发量小的缺陷,因此随着网络编程的不断发展,BIO这种阻塞的方式使用的频率逐渐变小。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/520396.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go编译报错 link: running gcc failed: exit status 1(已解决)

背景 在对一个开源的Go程序二次开发 重新编译时 , 报错截图如下 报错文字如下:关键信息 link: running gcc failed: exit status 1 $ go build -o orchestrator-didi -i go/cmd/orchestrator/main.go go build: -i flag is deprecated # command-li…

Lego-loam 算法三维建图

运行环境 Linux:Ubuntu18.04ros:MelodicCeres Solver 2.0.0(Ubuntu18.04安装Ceres)PCL 1.8.1(Ubuntu系统的PCL、Eigen卸载和安装) 运行数据集 lego-loam 39/39 终端一:进入catkin_ws工作空间…

【Flink网络数据传输】OperatorChain的设计与实现

文章目录 1.OperatorChain的设计与实现2.OperatorChain的创建和初始化3.创建RecordWriterOutput 1.OperatorChain的设计与实现 OperatorChain的大致逻辑 在JobGraph对象的创建过程中,将链化可以连在一起的算子,常见的有StreamMap、StreamFilter等类型的…

字符函数

1.字符分类函数 专门做字符分类的函数&#xff0c;都包含一个头文件#include <ctype.h> islower() 是一个用于判断字符是否为小写字母的函数。 通常情况下&#xff0c;如果一个字符是小写字母&#xff0c;则 islower() 函数会返回 true 或者一个表示真的值&#xff08…

【变量提升】关于JavaScript变量提升的理解,它导致了什么问题?

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

大模型笔记:最少到最多提示过程 (Least to Most prompting, LtM)

LEAST-TO-MOST PROMPTING ENABLES COMPLEX REASONING IN LARGE LANGUAGE MODELS 2023 ICLR 1 概述 进一步发展维链提示过程 (CoT prompting) 分为两个阶段&#xff1a; 第一阶段&#xff1a;向语言模型提出查询&#xff0c;将问题分解成子问题。第二阶段&#xff1a;再次向语…

Z Potentials | 星爵,他的征途不止向量数据库

纵观过去几十年的科技发展史&#xff0c;每一代新的技术架构的出现往往都伴随着新的数据范式的出现&#xff0c;也催生了多家百亿到千亿美金数据平台的诞生。如果说 2023 年科技领域的关键词是 LLM&#xff0c;那么数据库领域的关键词一定非向量数据库莫属。向量数据库是一种专…

SpringMVC拦截器和过滤器执行顺序及区别

拦截器&#xff08;Inteceptor&#xff09;和过滤器&#xff08;Filter&#xff09;执行顺序&#xff1f; 拦截器和过滤器区别&#xff1f; 1、拦截次数不同&#xff1a; 过滤器&#xff1a;一次请求只能被一个过滤器拦截一次&#xff0c;它们按照在web.xml中的声明顺序依次执…

IJCAI23 - Continual Learning Tutorial

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 本篇 Tutorial 主要介绍了 CL 中的一些基本概念以及一些过往的方法。 Problem Definition Continual Learning 和 Increm…

DataX及使用

DataX及使用 【一】DataX概述【二】DataX架构原理【1】设计理念【2】框架设计【3】运行流程【4】调度决策思路【5】DataX和Sqoop对比 【三】DataX部署【四】DataX上手【1】使用概述【2】配置文件格式【3】同步Mysql数据到HDFS 【五】DataX整合Springboot 【一】DataX概述 Data…

C++面试宝典【配文档,全方面学习】

原word文档[链接: https://pan.baidu.com/s/1CKnm7vHDmHSDskAgxgZgKA?pwdr4wv 提取码: r4wv 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v5的分享] 一、C / C基础 1、简述C的内存分区&#xff1f; 一个C、C程序的内存分区主要有5个…

【李沐精读系列】GPT、GPT-2和GPT-3论文精读

论文&#xff1a; GPT&#xff1a;Improving Language Understanding by Generative Pre-Training GTP-2&#xff1a;Language Models are Unsupervised Multitask Learners GPT-3&#xff1a;Language Models are Few-Shot Learners 参考&#xff1a;GPT、GPT-2、GPT-3论文精读…