【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.mzph.cn/news/730298.shtml

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

相关文章

C语言进阶——位段

在C语言中,位段(Bit Fields)是一种用来对结构体中的成员进行位级别的控制的特性。通过位段,我们可以灵活地控制结构体中各个成员的位数,从而节省内存空间并提高程序的效率。本篇博客将详细讲解C语言中位段的相关知识&a…

力扣爆刷第89天之hot100五连刷31-35

力扣爆刷第89天之hot100五连刷31-35 文章目录 力扣爆刷第89天之hot100五连刷31-35一、25. K 个一组翻转链表二、138. 随机链表的复制三、148. 排序链表四、23. 合并 K 个升序链表五、146. LRU 缓存 一、25. K 个一组翻转链表 题目链接:https://leetcode.cn/problem…

python第九节:类的使用(3)

类的继承: 一个类继承另一个类时,它将自动获得另一个类的所有属性和方法;原有的类称为父类,而新类称为子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。 创建子类时,必须在括号…

Java 简历优化及注意事项

Java 简历优化及注意事项 前言1、自我介绍2、掌握技术技能3、项目经验4、项目必问的细节点:5、项目中的难点以及优化改进点6、获奖经历7、面试注意事项 前言 最新的 Java 面试题,技术栈涉及 Java 基础、集合、多线程、Mysql、分布式、Spring全家桶、MyBatis、Dubbo…

找出单身狗1,2

目录 1. 单身狗12. 单身狗2 1. 单身狗1 题目如下: 思路:一部分人可能会使用对数组排序,遍历数组的方式去找出只出现一次的数字,但这种方法的时间复杂度过高,有时候可能会不满足要求。 有一种十分简便的方法是使用异或…

​LeetCode解法汇总2834. 找出美丽数组的最小和

目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:. - 力扣(LeetCode) 描述: 给你两个正整数:n 和 target …

DEAP:利用生理信号进行情绪分析的数据库【DEAP数据集】

文章目录 摘要引言刺激选择实验环境参与者步骤参与者自我评估 主观评价分析EEG频率与参与者评分之间的相关性单次试验分类结果 结论 点击下载原文 摘要 ● DEAP:用于分析人类情感状态的多模态数据集。 ● 32名参与者观看了40个一分钟长的音乐视频。 ● 参与者根据唤…

c++ primer中文版第五版作业第十三章

仓库地址 文章目录 13.113.213.313.413.513.613.713.813.913.1013.1113.1213.1313.1413.1513.1613.1713.1813.1913.2013.2113.2213.2313.2413.2513.2613.2713.2813.2913.3013.3113.3213.3313.3413.3513.3613.3713.3813.3913.4013.4113.4213.4313.4413.4513.4613.4713.4813.4913…

PostgreSQL教程(二十二):服务器管理(四)之服务器配置

一、设置参数 1.1 参数名称和值 所有参数名都是大小写不敏感的。每个参数都可以接受五种类型之一的值: 布尔、字符串、整数、 浮点数或枚举。该类型决定了设置该参数的语法: 布尔: 值可以被写成 on, off, true, false, yes, no, 1, 0 (都是…

Programming Abstractions in C阅读笔记:p312-p326

《Programming Abstractions in C》学习第77天,p312-p326,总计15页,第7章完结。 一、技术总结 第7章主要讲算法分析——引入时间复杂度这一概念来评估算法的快慢。时间复杂度使用大O符号来表示。 第7章以排序算法为示例,包含&a…

go调用 c++中数组指针相关

要在Go语言中调用C编译的DLL(动态链接库)并传递数组,你需要遵循以下步骤: 编写C代码:首先,你需要有一个C的DLL,它提供了你想要在Go中调用的函数。为了确保Go可以调用它,你需要使用C…

[PTA] 分解质因子

输入一个正整数n(1≤n≤1e15),编程将其分解成若干个质因子(素数因子)积的形式。 输入格式: 任意给定一个正整数n(1≤n≤1e15)。 输出格式: 将输入的正整数分解成若干个质因子积的形式&#…

ubuntu 卸载miniconda3

一开始安装路径错了,需要重新安一次,就一起记录了。 前提是这种方式安装: ubuntu安装miniconda3管理python版本-CSDN博客 删除Miniconda的安装目录 这目录就是你选择安装的时候指定的,如果记不得了,可以这样查看 which conda 这…

数据库压力测试方法概述

一、前言 在前面的压力测试过程中,主要关注的是对接口以及服务器硬件性能进行压力测试,评估请求接口和硬件性能对服务的影响。但是对于多数Web应用来说,整个系统的瓶颈在于数据库。 原因很简单:Web应用中的其他因素,…

Chrome安装Axure插件

打开原型目录/resources/chrome,重命名axure-chrome-extension.crx,修改后缀为rar,axure-chrome-extension.rar 解压到axure-chrome-extension目录打开Chrome,更多工具->扩展程序,打开开发者模式,选择加…

结构体和malloc学习笔记

结构体学习: 为什么会出现结构体: 为了表示一些复杂的数据,而普通的基本类型变量无法满足要求; 定义: 结构体是用户根据实际需要自己定义的符合数类型; 如何使用结构体: //定义结构体 struc…

[C++]类和对象,explicit,static,友元,构造函数——喵喵要吃C嘎嘎4

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,大大会看到更多有趣的博客哦!!! 喵喵喵,你对我真的…

FineReport决策报表Excel导出数据不全解决办法

一、首先建立决策报表 决策报表不带参数导出办法(即没有参数面板) 普通决策报表导出(没有搜索面板) 如果决策报表带参数(即有搜索框),用上面的办法只能导出部分数据,数据不全 二、…

如何应对不想工作的情况

不想工作,这是每个人都可能遇到的情况。但是,作为一名程序员,我们需要保持高效率和创造力,以满足项目的需求和进度。以下是一些建议,帮助你在不想工作的时候保持高效和专注。 确定原因 不想工作可能有很多原因,比如疲劳、缺乏动力、工作内容不感兴趣等等。确定原因可以帮助你找…

蓝色经典免费wordpress模板主题

蓝色经典配色的免费wordpress建站主题,万能的wordpress建站主题。 https://www.wpniu.com/themes/24.html