JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

阻塞IO与非阻塞IO

通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程。

从JAVA1.4开始引入了NIO API, NIO可以实现非阻塞IO, 这样就可以使用一个线程处理所有的客户请求。

基于NIO的非阻塞Socket通信

服务器将用来监听客户端请求的channel注册到selector上,启动一个线程,使用selector的select()获取求情的客户端的channel数量,

当监听到有客户端请求时,就通过SelectionKey返回对应的客户端channel进行通信。

下面是一个非常简单的例子,

服务器端

 1 package niochat;
 2
 3 import java.io.IOException;
 4 import java.net.InetSocketAddress;
 5 import java.nio.ByteBuffer;
 6 import java.nio.channels.Channel;
 7 import java.nio.channels.SelectionKey;
 8 import java.nio.channels.Selector;
 9 import java.nio.channels.ServerSocketChannel;
10 import java.nio.channels.SocketChannel;
11 import java.nio.charset.Charset;
12
13 public class NServer {
14     //用于检查所有channel状态的selector
15     private Selector selector = null;
16     static final int PORT = 3001;
17     private Charset charset = Charset.forName("utf-8");
18     public void init() throws IOException, InterruptedException {
19         selector = Selector.open();
20         //通过open方法打开一个未绑定的ServerSocketChannel实例
21         ServerSocketChannel server = ServerSocketChannel.open();
22         InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
23         server.bind(isa);
24         //设置非阻塞
25         server.configureBlocking(false);
26         //注册ServerSocketChannel到selector
27         server.register(selector, SelectionKey.OP_ACCEPT);
28         //select()返回一个key集合的元素个数,集合元素对应的channel正请求IO操作
29         int num = 0;
30         while ((num = selector.select()) > 0) {
31
32             //每次都返回1
33             System.out.println("当前selector.select()="+num);
34             Thread.sleep(1000);
35             //被选中的key集合selectedKeys表示需要进行IO处理的channel集合
36             for (SelectionKey sk : selector.selectedKeys()) {
37                 //从selector上的已选择的key集合中删除正在处理的key
38                 selector.selectedKeys().remove(sk);
39                 //如果sk对应channel包含客户端连接请求
40                 if (sk.isAcceptable()) {
41                     //调用accept方法接受请求,产生一个服务器端的SocketChannel
42                     //在非阻塞模式下,如果没有连接则直接返回null
43                     SocketChannel sc = server.accept();
44                     //设置非阻塞模式
45                     sc.configureBlocking(false);
46                     //将SocketChannel也注册到selector
47                     sc.register(selector, SelectionKey.OP_READ);
48                     //再将sk对应的channel设置为请求准备接受其他请求
49                     sk.interestOps(SelectionKey.OP_ACCEPT);
50                 }
51
52                 //如果sk对应的channel有数据需要读取
53                 if (sk.isReadable()) {
54                     //获取sk对应的channel
55                     SocketChannel sc = (SocketChannel)sk.channel();
56                     //channel中的数据必须先写入buffer中,然后才能写入进content中
57                     ByteBuffer buff = ByteBuffer.allocate(1024);
58                     String content = "";
59                     try {
60                         while(sc.read(buff) > 0) {
61                             //buffer 复位
62                             buff.flip();
63                             content += charset.decode(buff);
64                         }
65                         System.out.println("读取的数据:"+ content);
66                         //sk复位
67                         sk.interestOps(SelectionKey.OP_READ);
68                     } //遇到channel有异常说明客户端有异常,取消注册此sk
69                     catch (IOException ex) {
70                         //从已选择key集合中取消sk,下一次select()时此channel将自动被删除
71                         ex.printStackTrace();
72
73                         sk.cancel();
74                         if(sk.channel() != null) {
75                             sk.channel().close();
76                         }
77
78                     }
79
80                     if (content.length() > 0) {
81                         //广播
82                         for(SelectionKey key : selector.keys()) {
83                             Channel targetChannel = key.channel();
84                             if (targetChannel instanceof SocketChannel) {
85                                 SocketChannel dest = (SocketChannel)targetChannel;
86                                 dest.write(charset.encode(content));
87                             }
88                         }
89                     }
90                 }
91
92             }
93         }
94     }
95
96     public static void main(String[] args) throws IOException, InterruptedException {
97         new NServer().init();
98     }
99 }

客户端

 1 package niochat;
 2
 3 import java.io.IOException;
 4 import java.net.InetSocketAddress;
 5 import java.nio.ByteBuffer;
 6 import java.nio.channels.Channel;
 7 import java.nio.channels.SelectionKey;
 8 import java.nio.channels.Selector;
 9 import java.nio.channels.SocketChannel;
10 import java.nio.charset.Charset;
11 import java.util.Scanner;
12
13 public class NClient {
14     //定义检测SocketChannel的selector
15     private Selector selector = null;
16     private static final int PORT = 3001;
17     private Charset charset = Charset.forName("utf-8");
18     //客户端的SocketChannel
19     private SocketChannel sc = null;
20     public void init() throws IOException {
21          selector = Selector.open();
22          InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
23          //调用open静态方法创建连接到指定主机的SocketChannel
24          sc = SocketChannel.open(isa);
25          sc.configureBlocking(false);
26          sc.register(selector, SelectionKey.OP_READ);
27          //创建子线程读取服务器返回的数据
28          new ClientThread().start();
29          //键盘输入流
30          Scanner scan = new Scanner(System.in);
31          while (scan.hasNextLine())    {
32             String line = scan.nextLine();
33             //放进SocketChannel
34             sc.write(charset.encode(line));
35          }
36     }
37
38     private class ClientThread extends Thread {
39         public void run() {
40             try {
41                 while (selector.select() > 0) {
42                     //被选中的key集合selectedKeys表示需要进行IO处理的channel集合
43                     for (SelectionKey sk : selector.selectedKeys()) {
44                         //删除正在处理的key
45                         selector.selectedKeys().remove(sk);
46                         if (sk.isReadable()) {
47                             SocketChannel sc = (SocketChannel)sk.channel();
48                             ByteBuffer buff = ByteBuffer.allocate(1024);
49                             String content = "";
50                             while (sc.read(buff) > 0) {
51                                 sc.read(buff);
52                                 buff.flip();
53                                 content += charset.decode(buff);
54                             }
55                             System.out.println("聊天信息:"+content);
56                             sk.interestOps(SelectionKey.OP_READ);
57                         }
58                     }
59                 }
60             } catch (IOException e) {
61                 e.printStackTrace();
62             }
63         }
64     }
65
66     public static void main(String[] args) throws IOException {
67         new NClient().init();
68     }
69 }

执行结果,启动了一个服务器端,然后启动了两个客户端,

时间: 2024-08-01 10:45:59

JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信的相关文章

JAVA基础知识之网络编程——-基于UDP协议的通信例子

UDP是一种不可靠的协议,它在通信两端各建立一个socket,这两个socket不会建立持久的通信连接,只会单方面向对方发送数据,不检查发送结果. java中基于UDP协议的通信使用DatagramSocket类的receive和send方法即可,但消息需要通过一个特定的类封装(DatagramPacket) 下面是一个基于UDP协议的通信的例子, 服务器端, 1 package udp; 2 3 import java.io.IOException; 4 import java.net.Dat

JAVA基础知识之网络编程——-基于AIO的异步Socket通信

异步IO 下面摘子李刚的<疯狂JAVA讲义> 按照POSIX标准来划分IO,分为同步IO和异步IO.对于IO操作分为两步,1)程序发出IO请求. 2)完成实际的IO操作. 阻塞IO和非阻塞IO都是针对第一步来划分的,如果发出IO请求会阻塞线程,就是阻塞IO,否则就是非阻塞IO. 同步IO和非同步IO是针对第二步来说的,如果实际IO操作是由操作系统完成,再返回给程序,就是异步IO. 如果实际的IO需要程序本身去执行,会阻塞线程,就是同步IO. JAVA7的NIO.2提供了异步的channel,

JAVA基础知识之网络编程——-网络基础(多线程下载,get,post)

本文主要介绍java.net下为网络编程提供的一些基础包,InetAddress代表一个IP协议对象,可以用来获取IP地址,Host name之类的信息.URL和URLConnect可以用来访问web资源,URLDecode和URLEncode用来转换字符串. 本文会写两个例子来演示java网络编程的一些基本用法. 第一个例子,写一个程序用来模拟多线程下载. 本例中用到的技术有,多线程--多个线程同时读文件写文件,可以加快下载速度, 线程池--在本例中线程池不是必须,甚至是多余,只不过是为了演示

JAVA基础知识之网络编程——-TCP/IP协议,socket通信,服务器客户端通信demo

OSI模型分层 OSI模型是指国际标准化组织(ISO)提出的开放系统互连参考模型(Open System Interconnection Reference Model,OSI/RM),它将网络分为七层:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层 TCP/IP协议 TCP/IP是一系列网络通信协议的统称,其中最核心的两个协议是TCP和IP.TCP称为传输控制协议,IP称为互联网络协议. 网络分层除了OSI模型分层,还有TCP/IP模型分层,将网络划分为四层,应用层.传输层.网际层

java基础学习总结——网络编程

永不放弃,一切皆有可能!!! 只为成功找方法,不为失败找借口! java基础学习总结——网络编程 一.网络基础概念 首先理清一个概念:网络编程 != 网站编程,网络编程现在一般称为TCP/IP编程. 二.网络通信协议及接口 三.通信协议分层思想 四.参考模型 五.IP协议 每个人的电脑都有一个独一无二的IP地址,这样互相通信时就不会传错信息了. IP地址是用一个点来分成四段的,在计算机内部IP地址是用四个字节来表示的,一个字节代表一段,每一个字节代表的数最大只能到达255. 六.TCP协议和UD

java基础9:网络编程

关于Java基础的文章,我觉得写得还可以,以前发在了我其它的博客了,肯定是原创,现在再分享给大家出来. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

java基础11( 网络编程)

网络编程三要素 IP:一个计算机的标识 端口:应用程序都会对应一个端口,用来进行通信,有效端口:0~65535,其中0~1024系统使用或保留端口. 协议:两种协议(UDP,TCP) 特殊IP地址:127.0.0.1本地回环地址用来做一些本地测试 pingIP地址; 用来检测本机是否可以和指定的IP地址的计算机可以进行正常通讯 ipconfig     用来查看IP地址 xxx.xxx.xxx.255  广播地址 端口: 物理端口:物理设备对应的端口, 网卡口 逻辑端口:用来标示我们的计算机上的

Android学习之基础知识十三—网络编程的最佳实践

上一讲已经掌握了HttpURLConnection和OkHttp的用法,知道如何发起HTTP请求,以及解析服务器返回的数据,但是也许你还没发现,之前我们的写法其实是很有问题的,因为一个应用程序很可能会在许多地方使用到网络功能,而发送HTTP请求的代码基本都是相同的,如果我们每次都去编写一遍发送HTTP请求的代码,这显然是非常差劲的做法. 通常情况下我们都应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,当想要发起网络请求的时候,只需要简单的调用一下这个方法即可,比如使用如下的写法

java基础增强:网络编程、Tcp、Udp案例

一入编程深似海,从此妹子是路人. 1.Udp的客户端,服务端-----DatagramSocket 通过Udp协议编写一个群聊室,具体实现代码如下: package day.by.day.udp.charroom; //客户端代码 import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import j