假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击http://106.12.206.16:8080/qingruihappy/index.html

转发的范文:

网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。

在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口,Socket负责发起连接操作,连接成功之后,双方通过输入和输出流进行同步阻塞式通信。

下面,我们就以经典的时间服务器(TimeServer)为例,通过代码分析来回顾和熟悉下BIO编程。

2.1.1.BIO通信模型图

首先,我们通过下面的通信模型图来熟悉下BIO的服务端通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。

同步阻塞IO服务端通信模型(一客户端一线程)

该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,由于线程是JAVA虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。

下面的两个小节,我们会分别对服务端和客户端进行源码分析,寻找同步阻塞IO的弊端。

2.1.1.同步阻塞式IO创建的TimeServer源码分析

同步阻塞IO的TimeServer:

 1 01
 2 public class TimeServer {
 3 02
 4
 5 03
 6         /**
 7 04
 8          * @param args
 9 05
10          * @throws IOException
11 06
12          */
13 07
14         public static void main(String[] args) throws IOException {
15 08
16         int port = 8080;
17 09
18         if (args != null && args.length > 0) {
19 10
20
21 11
22             try {
23 12
24             port = Integer.valueOf(args[0]);
25 13
26             } catch (NumberFormatException e) {
27 14
28             // 采用默认值
29 15
30             }
31 16
32
33 17
34         }
35 18
36          ServerSocket server = null;
37 19
38         try {
39 20
40             server = new ServerSocket(port);
41 21
42             System.out.println("The time server is start in port : " + port);
43 22
44             Socket socket = null;
45 23
46             while (true) {
47 24
48             socket = server.accept();
49 25
50             new Thread(new TimeServerHandler(socket)).start();
51 26
52             }
53 27
54         } finally {
55 28
56             if (server != null) {
57 29
58             System.out.println("The time server close");
59 30
60             server.close();
61 31
62             server = null;
63 32
64             }
65 33
66         }
67 34
68         }
69 35
70     }

imeServer根据传入的参数设置监听端口,如果没有入参,使用默认值8080,20行通过构造函数创建ServerSocket,如果端口合法且没有被占用,服务端监听成功。23-26行通过一个无限循环来监听客户端的连接,如果没有客户端接入,则主线程阻塞在ServerSocket的accept操作上。启动TimeServer,通过JvisualVM打印线程堆栈,我们可以发现主程序确实阻塞在accept操作上,如下图所示:

主程序线程堆栈

当有新的客户端接入的时候,执行代码25行,以Socket为参数构造TimeServerHandler对象,TimeServerHandler是一个Runnable,使用它为构造函数的参数创建一个新的客户端线程处理这条Socket链路。下面我们继续分析TimeServerHandler的代码。

同步阻塞IO的TimeServerHandler:

  1 01
  2 public class TimeServerHandler implements Runnable {
  3 02
  4
  5 03
  6     private Socket socket;
  7 04
  8
  9 05
 10     public TimeServerHandler(Socket socket) {
 11 06
 12     this.socket = socket;
 13 07
 14     }
 15 08
 16
 17 09
 18     /*
 19 10
 20      * (non-Javadoc)
 21 11
 22      *
 23 12
 24      * @see java.lang.Runnable#run()
 25 13
 26      */
 27 14
 28     @Override
 29 15
 30     public void run() {
 31 16
 32     BufferedReader in = null;
 33 17
 34     PrintWriter out = null;
 35 18
 36     try {
 37 19
 38         in = new BufferedReader(new InputStreamReader(
 39 20
 40             this.socket.getInputStream()));
 41 21
 42         out = new PrintWriter(this.socket.getOutputStream(), true);
 43 22
 44         String currentTime = null;
 45 23
 46         String body = null;
 47 24
 48         while (true) {
 49 25
 50         body = in.readLine();
 51 26
 52         if (body == null)
 53 27
 54             break;
 55 28
 56         System.out.println("The time server receive order : " + body);
 57 29
 58         currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(
 59 30
 60             System.currentTimeMillis()).toString() : "BAD ORDER";
 61 31
 62         out.println(currentTime);
 63 32
 64         }
 65 33
 66
 67 34
 68     } catch (Exception e) {
 69 35
 70         if (in != null) {
 71 36
 72         try {
 73 37
 74             in.close();
 75 38
 76         } catch (IOException e1) {
 77 39
 78             e1.printStackTrace();
 79 40
 80         }
 81 41
 82         }
 83 42
 84         if (out != null) {
 85 43
 86         out.close();
 87 44
 88         out = null;
 89 45
 90         }
 91 46
 92         if (this.socket != null) {
 93 47
 94         try {
 95 48
 96             this.socket.close();
 97 49
 98         } catch (IOException e1) {
 99 50
100             e1.printStackTrace();
101 51
102         }
103 52
104         this.socket = null;
105 53
106         }
107 54
108     }
109 55
110     }
111 56
112 }

25行通过BufferedReader读取一行,如果已经读到了输入流的尾部,则返回值为null,退出循环。如果读到了非空值,则对内容进行判断,如果请求消息为查询时间的指令”QUERY TIME ORDER”则获取当前最新的系统时间,通过PrintWriter的println函数发送给客户端,最后退出循环。代码35-52行释放输入流、输出流、和Socket套接字句柄资源,最后线程自动销毁并被虚拟机回收。

在下一个小结,我们将介绍同步阻塞IO的客户端代码,然后分别运行服务端和客户端,查看下程序的运行结果。

2.1.1.同步阻塞式IO创建的TimeClient源码分析

客户端通过Socket创建,发送查询时间服务器的”QUERY TIME ORDER”指令,然后读取服务端的响应并将结果打印出来,随后关闭连接,释放资源,程序退出执行。

同步阻塞IO的TimeClient:

  1 01
  2 public class TimeClient {
  3 02
  4
  5 03
  6     /**
  7 04
  8      * @param args
  9 05
 10      */
 11 06
 12     public static void main(String[] args) {
 13 07
 14     int port = 8080;
 15 08
 16     if (args != null && args.length > 0) {
 17 09
 18         try {
 19 10
 20         port = Integer.valueOf(args[0]);
 21 11
 22         } catch (NumberFormatException e) {
 23 12
 24         // 采用默认值
 25 13
 26         }
 27 14
 28     }
 29 15
 30     Socket socket = null;
 31 16
 32     BufferedReader in = null;
 33 17
 34     PrintWriter out = null;
 35 18
 36     try {
 37 19
 38         socket = new Socket("127.0.0.1", port);
 39 20
 40         in = new BufferedReader(new InputStreamReader(
 41 21
 42             socket.getInputStream()));
 43 22
 44         out = new PrintWriter(socket.getOutputStream(), true);
 45 23
 46         out.println("QUERY TIME ORDER");
 47 24
 48         System.out.println("Send order 2 server succeed.");
 49 25
 50         String resp = in.readLine();
 51 26
 52         System.out.println("Now is : " + resp);
 53 27
 54     } catch (Exception e) {
 55 28
 56        //不需要处理
 57 29
 58     } finally {
 59 30
 60         if (out != null) {
 61 31
 62         out.close();
 63 32
 64         out = null;
 65 33
 66         }
 67 34
 68
 69 35
 70         if (in != null) {
 71 36
 72         try {
 73 37
 74             in.close();
 75 38
 76         } catch (IOException e) {
 77 39
 78             e.printStackTrace();
 79 40
 80         }
 81 41
 82         in = null;
 83 42
 84         }
 85 43
 86         if (socket != null) {
 87 44
 88         try {
 89 45
 90             socket.close();
 91 46
 92         } catch (IOException e) {
 93 47
 94             e.printStackTrace();
 95 48
 96         }
 97 49
 98         socket = null;
 99 50
100         }
101 51
102     }
103 52
104     }
105 53
106 }

第23行客户端通过PrintWriter向服务端发送”QUERY TIME ORDER”指令,然后通过BufferedReader的readLine读取响应并打印。

分别执行服务端和客户端,执行结果如下:

服务端执行结果如下:

同步阻塞IO时间服务器服务端运行结果

客户端执行结果如下:

同步阻塞IO时间服务器客户端运行结果

到此为止,同步阻塞式IO开发的时间服务器程序已经讲解完毕,我们发现,BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接。在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。
为了改进一线程一连接模型,后来又演进出了一种通过线程池或者消息队列实现1个或者多个线程处理N个客户端的模型,由于它的底层通信机制依然使用同步阻塞IO,所以被称为 “伪异步”,下面章节我们就对伪异步代码进行分析,看看伪异步是否能够满足我们对高性能、高并发接入的诉求。

自己的理解:

一,服务器端

 1 package bhz.bio;
 2
 3 import java.io.IOException;
 4 import java.net.ServerSocket;
 5 import java.net.Socket;
 6
 7
 8 public class Server {
 9
10     final static int PROT = 8765;
11
12     public static void main(String[] args) {
13
14         ServerSocket server = null;
15         try {
16             server = new ServerSocket(PROT);
17             System.out.println(" server start .. ");
18             //进行阻塞
19             Socket socket = server.accept();
20             //新建一个线程执行客户端的任务
21             new Thread(new ServerHandler(socket)).start();
22
23         } catch (Exception e) {
24             e.printStackTrace();
25         } finally {
26             if(server != null){
27                 try {
28                     server.close();
29                 } catch (IOException e) {
30                     e.printStackTrace();
31                 }
32             }
33             server = null;
34         }
38     }
39
58 }

在服务器端会有一个ServerSocket对象,这个对象和客户端的Socket是呼应的,

首先启动服务器端,代码会阻塞到19行,server.accept();就类似与一个监听器,会监听客户端对自己的访问,假如没有客户端访问的话,debug的时候代码会在19行停止,一直等待客户端的消息,假如有客户端访问的数据过来的话会执行20行的代码。

开启一个线程ServerHandler。传入的对象就行socket,这时候可以理解成客户端的也可以理解成服务端的。

这种写法的一个好处就是使逻辑代码和业务代码分离,便于阅读和写作。

启动服务端之后的打印日志,

我们可以看到代码阻塞到了19行,并没有往下走,没有去执行ServerHandler里面的内容,

他在等待客户端的请求的。

二:线程执行客户端类ServerHandler

 1 package bhz.bio;
 2
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStreamReader;
 6 import java.io.PrintWriter;
 7 import java.net.Socket;
 8
 9 public class ServerHandler implements Runnable{
10
11     private Socket socket ;
12
13     public ServerHandler(Socket socket){
14         this.socket = socket;
15     }
16
17     @Override
18     public void run() {
19         BufferedReader in = null;
20         PrintWriter out = null;
21         try {
22             in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
23             out = new PrintWriter(this.socket.getOutputStream(), true);
24             String body = null;
25             while(true){
26                 body = in.readLine();
27                 if(body == null) break;
28                 System.out.println("Server :" + body);
29                 out.println("服务器端回送响的应数据.");
30             }
31
32         } catch (Exception e) {
33             e.printStackTrace();
34         } finally {
35             if(in != null){
36                 try {
37                     in.close();
38                 } catch (IOException e) {
39                     e.printStackTrace();
40                 }
41             }
42             if(out != null){
43                 try {
44                     out.close();
45                 } catch (Exception e) {
46                     e.printStackTrace();
47                 }
48             }
49             if(socket != null){
50                 try {
51                     socket.close();
52                 } catch (IOException e) {
53                     e.printStackTrace();
54                 }
55             }
56             socket = null;
57         }
58
59
60     }
61
62 }

这个线程类中会有写出的流和写入的流

,写入的流就是客户端传过来的数据,

写出的流就是服务端要返给客户端的数据。

都是通过socket来传递的。

这是启动客户端之后服务端的打印日志,就是已经请求到数据了。

注意这里一旦请求完就会销毁线程的。

三:客户端类

 1 package bhz.bio;
 2
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStreamReader;
 6 import java.io.PrintWriter;
 7 import java.net.Socket;
 8
 9 public class Client {
10
11     final static String ADDRESS = "127.0.0.1";
12     final static int PORT = 8765;
13
14     public static void main(String[] args) {
15
16         Socket socket = null;
17         BufferedReader in = null;
18         PrintWriter out = null;
19
20         try {
21             socket = new Socket(ADDRESS, PORT);
22             in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
23             out = new PrintWriter(socket.getOutputStream(), true);
24
25             //向服务器端发送数据
26             out.println("接收到客户端的请求数据...");
27             String response = in.readLine();
28             System.out.println("Client: " + response);
29
30         } catch (Exception e) {
31             e.printStackTrace();
32         } finally {
33             if(in != null){
34                 try {
35                     in.close();
36                 } catch (IOException e) {
37                     e.printStackTrace();
38                 }
39             }
40             if(out != null){
41                 try {
42                     out.close();
43                 } catch (Exception e) {
44                     e.printStackTrace();
45                 }
46             }
47             if(socket != null){
48                 try {
49                     socket.close();
50                 } catch (IOException e) {
51                     e.printStackTrace();
52                 }
53             }
54             socket = null;
55         }
56     }
57 }

客户端的类主要就是这个socket主要有ip的地址和端口号,

然后又写出的流和写入的流,

写入的流就是服务端返回的数据,会在客户端来读取

写出的流就是客户端要传给服务端的数据。

注意,流传完之后一定要关闭,否则会占用大量的内存的。

但是有时候即使用非常标准的代码格式来关闭流的时候,也不一定会把流关闭掉,这个后续再讲。

这是客户端启动后打印的东西,就是服务端返回的数据。

和服务端一样的,一旦请求道数据,线程一旦销毁,就会把服务终止掉的。

阻塞,非阻塞

IO(BIO)和NIO的区别:其本质就是阻塞和非阻塞的区别。
阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。
非阻塞概念:应用程序直接可以获取已经准备就绪好的数据,无需等待。
IO为同步阻塞形式,NIO为同步非阻塞形式。NIO并没有实现异步,在JDK1.7之后,升级了NIO库包,支持异步非阻塞通信模型即NIO2.0(AIO)

最直接的理解就是阻塞代码会在某一行停止往下运行,只有特定的条件触犯之后代码才会往下走。

而非阻塞就是即使某一样要有其它的数据传入才会有数据返回,但是它照样会往下走的,不会停留在哪一行的。

bio就是典型的阻塞类型的,因为我们可以看到,它会在accept这一行停止往下运行,只有当客户端启动有参数传递过来请求的时候触发监听器,才会往下走,

至于非阻塞,我们下面会看到案例的。

同步,异步

同步和异步:同步和异步一般是面向操作系统与应用程序对IO操作的层面上来区别的。
同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪;或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。
异步时,则所有的IO读写操作交给操作系统处理,与我们的应用程序没有直接关系,我们程序不需要关心IO读写,当操作系统完成了IO读写操作时,会给我们应用程序发送通知,我们的应用程序直接拿走数据即可。
同步说的是你的server服务器
阻塞说的是具体的技术(io、nio)

同步和异步,我们举个例子,就是假如前台html页面有个请求的url,一般情况下会有一个请求头

比如我们请求百度的时候

里面会有一个content-length的字段,后面的就是发给前段的一个字节的长度。

同步就是必须要等到95个字节都加载好了才会返回,

而异步就是有可能在加载到94个字节的时候由于网络,服务器等原因没有加载完,照样会返回的。

bio通讯的缺点

缺点1:就是每请求一次就会开启一个线程,假如有上百万的线程,服务器是根本不够用的:。

缺点2:阻塞异步:这样会大大影响效率的和cup的利用率的。

缺点3:一旦一个线程出问题就会停止,就会出现数据加载不到的情况。

Netty(二)(入门篇)传统的Bio编程相关推荐

  1. kdir测试软件,[OK210开发板体验]入门篇(4)编程入门(NFS登录、驱动入门)

    前面分别介绍了 [OK210开发板体验]的第一篇:开箱验板 [OK210开发板验]的第二篇:板载资源 [OK210开发板体验]的第三篇:开发环境(软件安装,开发环境,烧写系统) 今天是[OK210开发 ...

  2. 运动控制器编程_快速入门 | 篇二十一:运动控制器ZHMI组态编程简介一

    点击上方"正运动小助手",随时关注新动态! 运动控制器ZHMI组态编程简介一  今天我们来学习一下,运动控制器的ZHMI组态编程简介.本文主要从产品概述.控制器连接触摸屏使用.HM ...

  3. 单片机stm32LED流水灯C语言,STM32单片机入门 篇二:单片机编程:从点亮LED灯开始(二)...

    STM32单片机入门 篇二:单片机编程:从点亮LED灯开始(二) 2019-04-26 13:21:48 19点赞 73收藏 19评论 写在前面的话:本篇是承接上一篇文章,属于小白向. 没看过的朋友点 ...

  4. Netty入门笔记-BIO编程

    导语   对于网络编程来说最为典型的就是基于客户端.服务器的C/S模型.也就是说客户端有一个线程,服务器端有一个线程,两个线程之间进行相互的通信.其中服务器段提供的是数据的信息,例如IP端口以及数据等 ...

  5. 微服务入门篇(二),万字长文带你实操作SpringCloudAlibaba微服务组件

    目录 SpringCloudAlibaba介绍 简介 为什么要学SpringCloudAlibaba 从Spring Cloud netflix 到 Spring Cloud Alibaba 功能组件 ...

  6. Scala进阶之路-并发编程模型Akka入门篇

    Scala进阶之路-并发编程模型Akka入门篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Akka Actor介绍 1>.Akka介绍 写并发程序很难.程序员不得不处 ...

  7. Android10.0 Binder通信原理(二)-Binder入门篇

    摘要:本节主要来讲解Android10.0 Binder的设计原理,如何设计一个Binder通信 阅读本文大约需要花费15分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分 ...

  8. Netty入门篇-从双向通信开始

    百度百科描述 Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户 ...

  9. 树莓派python开发教程_树莓派教程(基于python编程)--入门篇

    原标题:树莓派教程(基于python编程)--入门篇 一:格式化SD卡 SD卡插入读卡器连接电脑,使用SDFormatter对SD卡进行格式化 (重装烧录也要进行着SD卡格式化操作) 二:下载官方镜像 ...

最新文章

  1. 《Photoshop Lab修色圣典(修订版)》—第1课深入讨论
  2. msf各种弱口令爆破
  3. java array_Java 数组
  4. haskell程序设计语言
  5. 飞鸽传书:服务器开发系列—系统构架
  6. 10.FreeRTOS学习笔记-中断管理
  7. java 允许魔术变量_PHP超级全局变量、魔术变量和魔术函数汇总整理
  8. sed学习笔记(1) - 入门知识
  9. javascript学习心得(1)replace
  10. 速锐得驾培驾考免接线OBD数据价值及发展思路
  11. 第一章 DHT11温湿度传感器的使用
  12. Ant下载及配置安装
  13. 将标准的EclipseWTP项目转化成具有Gradle功能的EclipseWTP项目
  14. 我终于深入参与了一个分布式系统了,好多想法不一样了
  15. 2014年中款MacBook Pro 更换CPU散热硅脂再战5年
  16. 2014年全国专业技术人员计算机应用能力考试大纲,2014年全国专业技术人员计算机应用能力考试 Excel2003...
  17. 重磅 | 消灭所有马赛克,谷歌宣布机器学习图像锐化工具RAISR
  18. html标签中h4和h5,h5与h4的区别
  19. 第六章 Cesium学习入门之添加Geojson数据(dataSource)
  20. EI漏录的会议文章,如何申请EI数据库补录!

热门文章

  1. hibernate中antlr对于hql生成抽象语法树源码解析
  2. idm 假冒_IDM出现假冒序列号问题解决
  3. 数据结构与算法--递归(Recursion Algorithm)
  4. Java 并发编程CyclicBarrier的应用与源码解析(基于ReentrantLock实现)
  5. onvif 客户端发现
  6. 什么是嵌入式工程师,发展前景如何
  7. 刚开始用 Go 做项目开发时都会面临哪些问题?
  8. zookeeper3.5.4源码环境搭建
  9. Python入门8_方法,属性,迭代器
  10. springboot中日志配置