`
gsb
  • 浏览: 14015 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
文章分类
社区版块
存档分类
最新评论

java socket非阻塞通讯1

阅读更多
java socket非阻塞通讯
                                       java socket非阻塞通讯1

本篇文章观点和例子来自 《Java网络编程精解》, 作者为孙卫琴, 出版社为电子工业出版社。

      对于用ServerSocket 及 Socket 编写的服务器程序和客户程序, 他们在运行过程中常常会阻塞. 例如, 当一个线程执行 ServerSocket 的accept() 方法时, 假如没有客户连接, 该线程就会一直等到有客户连接才从 accept() 方法返回. 再例如, 当线程执行 Socket 的 read() 方法时, 如果输入流中没有数据, 该线程就会一直等到读入足够的数据才从 read() 方法返回.

      假如服务器程序需要同时与多个客户通信, 就必须分配多个工作线程, 让他们分别负责与一个客户通信, 当然每个工作线程都有可能经常处于长时间的阻塞状态.

      从 JDK1.4 版本开始, 引入了非阻塞的通信机制. 服务器程序接收客户连接, 客户程序建立与服务器的连接, 以及服务器程序和客户程序收发数据的操作都可以按非阻塞的方式进行. 服务器程序只需要创建一个线程, 就能完成同时与多个客户通信的任务.

      非阻塞的通信机制主要由 java.nio 包(新I/O包) 中的类实现, 主要的类包括 ServerSocketChannel, SocketChannel, Selector, SelectionKey 和 ByteBuffer 等.

      本章介绍如何用 java.nio 包中的类来创建服务器程序和客户程序, 并且 分别采用阻塞模式和非阻塞模式来实现它们. 通过比较不同的实现方式, 可以帮助读者理解它们的区别和适用范围.

一. 线程阻塞的概念

      在生活中, 最常见的阻塞现象是公路上汽车的堵塞. 汽车在公路上快速行驶, 如果前方交通受阻, 就只好停下来等待, 等到交通畅顺, 才能恢复行驶.

      线程在运行中也会因为某些原因而阻塞. 所有处于阻塞状态的线程的共同特征是: 放弃CPU, 暂停运行, 只有等到导致阻塞的原因消除, 才能恢复运行; 或者被其他线程中断, 该线程会退出阻塞状态, 并且抛出 InterruptedException.

1.1 线程阻塞的原因

      导致线程阻塞的原因主要有以下几方面.

线程执行了 Thread.sleep(int n) 方法, 线程放弃 CPU, 睡眠 n 毫秒, 然后恢复运行.
线程要执行一段同步代码, 由于无法获得相关的同步锁, 只好进入阻塞状态, 等到获得了同步锁, 才能恢复运行.
线程执行了一个对象的 wait() 方法, 进入阻塞状态, 只有等到其他线程执行了该对象的 notify() 和 notifyAll() 方法, 才可能将其呼醒.
线程执行 I/O 操作或进行远程通信时, 会因为等待相关的资源而进入阻塞状态. 例如, 当线程执行 System.in.read() 方法时, 如果用户没有向控制台输入数据, 则该线程会一直等读到了用户的输入数据才从 read() 方法返回.
进行远程通信时, 在客户程序中, 线程在以下情况可能进入阻塞状态.

请求与服务器建立连接时, 即当线程执行 Socket 的带参数构造方法, 或执行 Socket 的 connect() 方法时, 会进入阻塞状态, 直到连接成功, 此线程才从 Socket 的构造方法或 connect() 方法返回.
线程从 Socket 的输入流读入数据时, 如果没有足够的数据, 就会进入阻塞状态, 直到读到了足够的数据, 或者到达输入流的末尾, 或者出现了异常, 才从输入流的 read() 方法返回或异常中断. 输入流中有多少数据才算足够呢? 这要看线程执行的 read() 方法的类型.
> int read(): 只要输入流中有一个字节, 就算足够.

> int read( byte[] buff): 只要输入流中的字节数目与参数buff 数组的长度相同, 就算足够.

> String readLine(): 只要输入流中有一行字符串, 就算足够. 值得注意的是, InputStream 类并没有 readLine() 方法, 在过滤流 BufferedReader 类中才有此方法.

线程向 Socket 的输出流写一批数据时, 可能会进入阻塞状态, 等到输出了所有的数据, 或者出现异常, 才从输出流 的 write() 方法返回或异常中断.
调用 SOcket 的setSoLinger() 方法设置了关闭 Socket 的延迟时间, 那么当线程执行 Socket 的 close() 方法时, 会进入阻塞状态, 直到底层 Socket 发送完所有剩余数据, 或者超过了 setSoLinger() 方法设置的延迟时间, 才从 close() 方法返回.
在服务器程序中, 线程在以下情况下可能会进入阻塞状态.

线程执行 ServerSocket 的 accept() 方法, 等待客户的连接, 直到接收到了客户连接, 才从 accept() 方法返回.      
线程从 Socket 的输入流读入数据时, 如果输入流没有足够的数据, 就会进入阻塞状态.
线程向 Socket 的输出流写一批数据时, 可能会进入阻塞状态, 等到输出了所有的数据, 或者出现异常, 才从输出流的 write() 方法返回或异常中断.
由此可见, 无论在服务器程序还是客户程序中, 当通过 Socket 的输入流和输出流来读写数据时, 都可能进入阻塞状态. 这种可能出现阻塞的输入和输出操作被称为阻塞 I/O. 与此对照, 如果执行输入和输出操作时, 不会发生阻塞, 则称为非阻塞 I/O.

1.2 服务器程序用多线程处理阻塞通信的局限

      本书第三章的第六节(创建多线程的服务器) 已经介绍了服务器程序用多线程来同时处理多个客户连接的方式. 服务器程序的处理流程如图 4-1 所示. 主线程负责接收客户的连接. 在线程池中有若干工作线程, 他们负责处理具体的客户连接. 每当主线程接收到一个客户连接, 就会把与这个客户交互的任务交给一个空闲的工作线程去完成, 主线程继续负责接收下一个客户连接.


                              图4-1 服务器程序用多线程处理阻塞通信

      在图4-1 总, 用粗体框标识的步骤为可能引起阻塞的步骤. 从图中可以看出, 当主线程接收客户连接, 以及工作线程执行 I/O 操作时, 都有可能进入阻塞状态.

      服务器程序用多线程来处理阻塞 I/O, 尽管能满足同时响应多个客户请求的需求, 但是有以下局限:

      ⑴ Java 虚拟机会为每个线程分配独立的堆栈空间, 工作线程数目越多, 系统开销就越大, 而且增加了 Java虚拟机调度线程的负担, 增加了线程之间同步的复杂性, 提高了线程死锁的可能性;

      ⑵ 工作线程的许多时间都浪费在阻塞 I/O 操作上, Java 虚拟机需要频繁地转让 CPU 的使用权, 使进入阻塞状态的线程放弃CPU, 再把CPU 分配给处于可运行状态的线程.

      由此可见, 工作线程并不是越多越好. 如图 4-2 所示, 保持适量的工作线程, 会提高服务器的并发性能, 但是当工作线程的数目达到某个极限, 超出了系统的负荷时, 反而会减低并发性能, 使得多数客户无法快速得到服务器的响应.


                      图4-2 线程数目与并发性能的更新                 

1.3 非阻塞通信的基本思想

      假如要同时做两件事: 烧开水和烧粥. 烧开水的步骤如下:

      锅里放水, 打开煤气炉;

      等待水烧开;                                                            //阻塞

      关闭煤气炉, 把开水灌到水壶里;

      烧粥的步骤如下:

      锅里放水和米, 打开煤气炉;

      等待粥烧开;                                                             //阻塞

      调整煤气炉, 改为小火;  

      等待粥烧熟;                                                             //阻塞

      关闭煤气炉;

      为了同时完成两件事, 一个方案是同时请两个人分别做其中的一件事, 这相当于采用多线程来同时完成多个任务. 还有一种方案是让一个人同时完成两件事, 这个人应该善于利用一件事的空闲时间去做另一件事, 一刻也不应该闲着:

      锅子里放水, 打开煤气炉;                      //开始烧水

      锅子力放水和米, 打开煤气炉;                //开始烧粥

      while(一直等待, 直到有水烧开, 粥烧开或粥烧熟事件发生){          //阻塞

            if(水烧开)

                   关闭煤气炉, 把开水灌到水壶里;

            if(粥烧开)

                   调整煤气炉, 改为小火;

            if(粥烧熟)

                   关闭煤气炉;

            if(水已经烧开并且粥已经烧熟)

                   退出循环;

      }         //这里的煤气炉我可以理解为每件事就有一个煤气炉配给吧, 这也是一部分的开销呢

                 //并且if里面的动作必须要能快速完成的才行, 不然后面的就要排队了

                 //如是太累的工作还是不要用这个好                                  

      这个人不断监控烧水及烧粥的状态, 如果发生了 "水烧开", "粥烧开" 或 "粥烧熟" 事件, 就去处理这些事件, 处理完一件事后进行监控烧水及烧粥的状态, 直到所有的任务都完成.

       以上工作方式也可以运用到服务器程序中, 服务器程序只需要一个线程就能同时负责接收客户的连接, 接收各个客户发送的数据, 以及向各个客户发送响应数据. 服务器程序的处理流程如下:

       while(一直等待, 直到有接收连接就绪事件, 读就绪事件或写就绪事件发生){             //阻塞

              if(有客户连接)

                   接收客户的连接;                                                    //非阻塞

              if(某个 Socket 的输入流中有可读数据)

                   从输入流中读数据;                                                 //非阻塞

              if(某个 Socket 的输出流可以写数据)

                   向输出流写数据;                                                    //非阻塞

       }

      以上处理流程采用了轮询的工作方式, 当某一种操作就绪时, 就执行该操作, 否则就查看是否还有其他就绪的操作可以执行. 线程不会因为某一个操作还没有就绪, 就进入阻塞状态, 一直傻傻地在那里等待这个操作就绪.

      为了使轮询的工作方式顺利进行, 接收客户的连接, 从输入流读数据, 以及向输出流写数据的操作都应该以非阻塞的方式运行. 所谓非阻塞, 就是指当线程执行这些方法时, 如果操作还没有就绪, 就立即返回, 而不会一直等到操作就绪. 例如, 当线程接收客户连接时, 如果没有客户连接, 就立即返回; 再例如, 当线程从输入流中读数据时, 如果输入流中还没有数据, 就立即返回, 或者如果输入流还没有足够的数据, 那么就读取现有的数据, 然后返回. 值得注意的是, 以上 while 学校条件中的操作还是按照阻塞方式进行的, 如果未发生任何事件, 就会进入阻塞状态, 直到接收连接就绪事件, 读就绪事件或写就绪事件中至少有一个事件发生时, 才会执行 while 循环体中的操作. 在while 循环体中, 一般会包含在特定条件下退出循环的操作.

二. java.nio 包中的主要类

        java.nio 包提供了支持非阻塞通信的类.

ServerSocketChannel: ServerSocket 的替代类, 支持阻塞通信与非阻塞通信.
SocketChannel: Socket 的替代类, 支持阻塞通信与非阻塞通信.
Selector: 为ServerSocketChannel 监控接收连接就绪事件, 为 SocketChannel 监控连接就绪, 读就绪和写就绪事件.
SelectionKey: 代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄. 当一个 SelectionKey 对象位于Selector 对象的 selected-keys 集合中时, 就表示与这个 SelectionKey 对象相关的事件发生了.      
ServerSocketChannel 及 SocketChannel 都是 SelectableChannel 的子类, 如图 4-3 所示. SelectableChannel 类及其子类都能委托 Selector 来监控他们可能发生的一些事件, 这种委托过程也称为注册事件过程.

                           


分享到:
评论

相关推荐

    基于Java语言的异步非阻塞socket通讯内核设计源码

    该项目是一款基于Java语言的异步非阻塞socket通讯内核设计源码,总文件量为432个,其中Java源文件占主导地位,共计381个。此外,还包括25个XML配置文件、13个Markdown文档、3个Shell脚本、3个属性文件、1个YAML配置...

    java_sx.rar_java socket _java 通讯_socket

    标题中的"java_sx.rar_java socket _java 通讯_socket"暗示了这个压缩包可能包含了一些关于使用Java Socket进行网络通信的示例代码或教程资料。Java Socket通信通常涉及以下关键知识点: 1. **Socket类与...

    java Socket即时通讯

    此外,还可以考虑使用NIO(非阻塞I/O)或者WebSocket等高级API来提升性能和用户体验。 总之,Java Socket是构建网络即时通讯系统的重要工具,通过它,开发者可以创建定制化的、高效的数据传输解决方案。理解并熟练...

    C#和java 之间基于Socket的通信

    - 使用非阻塞I/O或异步I/O可以提高Socket通信的性能。Java的NIO(Non-blocking I/O)和C#的异步编程模型(如`async/await`关键字)都是不错的选择。 10. **测试与调试**: - 为确保通信正常,需要编写测试用例,...

    三步学会Java_Socket编程

    在实际应用中,我们可能需要处理多个并发连接,这时可以使用多线程或者NIO(非阻塞I/O)来提高服务器的并发能力。对于服务器端,可以创建一个线程池,每当有新的连接请求时,创建一个新的线程来处理。对于客户端,...

    java_chat_program.rar_socket java用户_socket即时通讯

    在实际的开发中,为了提高效率和用户体验,我们可能会使用NIO(非阻塞I/O)或者Netty这样的高性能网络库,以及JSON或XML等格式来序列化消息,使得数据交换更加规范和高效。此外,为了防止恶意攻击,还需要考虑安全...

    卫通星GPS定位器GT06协议socket通讯JAVA Spring Boot对接.zip

    5. **线程管理**:由于Socket通讯通常是异步的,为了处理多个并发连接,你可能需要使用线程池或者非阻塞I/O模型(如NIO)。Spring Boot提供了线程池的支持,也可以结合WebSocket或其他非阻塞技术进行优化。 6. **...

    Socket编程QQ实现 JAVA

    - 使用NIO(非阻塞I/O)可以提高服务器的并发性能,特别是面对大量并发连接时。 7. **其他高级特性**: - Java的`MulticastSocket`支持多播通信,可以实现一对多的消息传递。 - `Selector`和`SelectableChannel`...

    socket_java_yibu.rar_java socket _socket异步

    在Java中,可以使用NIO(非阻塞I/O)或者CompletableFuture等高级特性来实现异步Socket通信。 1. **NIO(Non-blocking I/O)**:Java NIO库提供了一种新的I/O模型,不同于传统的阻塞I/O。在NIO中,当没有数据可读或...

    JAVANIO在Socket通讯中的应用

    ### JAVANIO在Socket通讯中的应用 #### 引言 在Java开发中,网络通信是常见需求之一,其中Socket编程是最基础也是最重要的技术之一。然而,在传统的Java Socket编程中,由于采用的是阻塞I/O模型,即所有的读写操作...

    聊天室基础项目资料_socket聊天室_聊天室_java项目_socket_

    8. **并发编程**:在处理大量并发连接时,可能需要用到线程池或者非阻塞I/O(NIO)来提高性能。 9. **测试与调试**:编写单元测试,模拟不同场景下的网络通信,确保聊天室的稳定性和正确性。 通过这个项目,开发者...

    java socket通信h

    3. 阻塞与非阻塞:默认情况下,Socket操作是阻塞的,这意味着如果数据未准备好,read()方法会一直等待。非阻塞模式可以通过NIO(非阻塞I/O)实现。 总之,Java Socket通信是构建网络应用的基础,通过理解和熟练运用...

    基于Socket的即时通讯系统_源代码

    5. **多线程/异步处理**:为了处理多个并发连接,服务器端通常需要使用多线程或异步IO模型,如Java中的NIO(非阻塞I/O)或Python的asyncio库,以提高系统并发性能。 6. **心跳机制**:为了保持连接状态并检测网络...

    java socket编程实例

    - Java NIO(New I/O)提供了非阻塞的Socket编程接口,可以更高效地处理多个连接。`java.nio.channels`包下的`ServerSocketChannel`和`SocketChannel`可以替代传统的Socket和ServerSocket。 通过这个压缩包中的...

    socket实现tcp双机通讯

    在IT行业中,网络通信是至关重要的一个领域,特别是在分布式系统和互联网应用中。...在项目实践中,可以结合具体的业务需求,使用NIO(非阻塞I/O)或者Netty这样的高性能网络库来提高效率和可维护性。

    基于Java Socket网络编程的基础性应用研究.zip

    Java NIO(Non-blocking I/O)提供了另一种方式来处理Socket通信,它支持非阻塞I/O操作,可以提高服务器处理大量并发连接的能力。通过Selector和Channel,开发者可以更高效地管理多个Socket连接。 七、实战应用 ...

    java源码:java Socket通信实现.rar

    - 使用NIO(New IO)或NIO.2可以实现非阻塞I/O,提升服务器的并发能力。 - 安全性方面,SSL/TLS协议可以为Socket通信提供加密保护,防止数据被窃取。 通过学习并实践这个"java Socket通信实现"的源码,你可以深入...

    java socket编程

    十、NIO(非阻塞I/O) Java NIO(New IO)提供了更高效的数据传输方式,通过选择器(Selector)和通道(Channel),可以实现多路复用,提高服务器处理大量并发连接的能力。 总之,Java Socket编程是构建网络应用的...

    java网络编程 socket

    - NIO(非阻塞I/O):Java的非阻塞I/O模型可以提高高并发场景下的性能,如使用Selector监控多个Socket的状态。 - SSL/TLS:对于需要加密传输的场景,可以使用Java的SSL套接字提供安全的网络通信。 通过阅读《Java...

Global site tag (gtag.js) - Google Analytics