JAVA NIO 服务器与客户端实现示例

简介:

以下代码只兼容Java 7及以上版本,对于一些关键地方请看注释说明。


公共类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package  com.stevex.app.nio;
 
import  java.nio.ByteBuffer;
import  java.nio.CharBuffer;
import  java.nio.charset.CharacterCodingException;
import  java.nio.charset.Charset;
import  java.nio.charset.CharsetDecoder;
import  java.nio.charset.CharsetEncoder;
 
public  class  CharsetHelper {
     private  static  final  String UTF_8 =  "UTF-8" ;
     private  static  CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
     private  static  CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
     
     public  static  ByteBuffer encode(CharBuffer in)  throws  CharacterCodingException{
         return  encoder.encode(in);
     }
 
     public  static  CharBuffer decode(ByteBuffer in)  throws  CharacterCodingException{
         return  decoder.decode(in);
     }
}


服务器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package  com.stevex.app.nio;
 
import  java.io.IOException;
import  java.net.InetSocketAddress;
import  java.nio.ByteBuffer;
import  java.nio.CharBuffer;
import  java.nio.channels.ClosedChannelException;
import  java.nio.channels.SelectionKey;
import  java.nio.channels.Selector;
import  java.nio.channels.ServerSocketChannel;
import  java.nio.channels.SocketChannel;
import  java.util.Iterator;
 
public  class  XiaoNa {
     private  ByteBuffer readBuffer;
     private  Selector selector;
     
     public  static  void  main(String[] args){
         XiaoNa xiaona =  new  XiaoNa();
         xiaona.init();
         xiaona.listen();
     }
     
     private  void  init(){
         readBuffer = ByteBuffer.allocate( 1024 );
         ServerSocketChannel servSocketChannel;
         
         try  {
             servSocketChannel = ServerSocketChannel.open();
             servSocketChannel.configureBlocking( false );
             //绑定端口
             servSocketChannel.socket().bind( new  InetSocketAddress( 8383 ));
             
             selector = Selector.open();
             servSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
         catch  (IOException e) {
             e.printStackTrace();
         }      
     }
 
     private  void  listen() {
         while ( true ){
             try {
                 selector.select();             
                 Iterator ite = selector.selectedKeys().iterator();
                 
                 while (ite.hasNext()){
                     SelectionKey key = (SelectionKey) ite.next();                  
                     ite.remove(); //确保不重复处理
                     
                     handleKey(key);
                 }
             }
             catch (Throwable t){
                 t.printStackTrace();
             }                          
         }              
     }
 
     private  void  handleKey(SelectionKey key)
             throws  IOException, ClosedChannelException {
         SocketChannel channel =  null ;
         
         try {
             if (key.isAcceptable()){
                 ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                 channel = serverChannel.accept(); //接受连接请求
                 channel.configureBlocking( false );
                 channel.register(selector, SelectionKey.OP_READ);
             }
             else  if (key.isReadable()){
                 channel = (SocketChannel) key.channel();
                 readBuffer.clear();
                 /*当客户端channel关闭后,会不断收到read事件,但没有消息,即read方法返回-1
                  * 所以这时服务器端也需要关闭channel,避免无限无效的处理*/              
                 int count = channel.read(readBuffer);
                 
                 if(count > 0){
                     //一定需要调用flip函数,否则读取错误数据
                     readBuffer.flip();
                     /*使用CharBuffer配合取出正确的数据
                     String question = new String(readBuffer.array());  
                     可能会出错,因为前面readBuffer.clear();并未真正清理数据
                     只是重置缓冲区的position, limit, mark,
                     而readBuffer.array()会返回整个缓冲区的内容。
                     decode方法只取readBuffer的position到limit数据。
                     例如,上一次读取到缓冲区的是"where", clear后position为0,limit为 1024,
                     再次读取“bye"到缓冲区后,position为3,limit不变,
                     flip后position为0,limit为3,前三个字符被覆盖了,但"re"还存在缓冲区中,
                     所以 new String(readBuffer.array()) 返回 "byere",
                     而decode(readBuffer)返回"bye"。            
                     */
                     CharBuffer charBuffer = CharsetHelper.decode(readBuffer); 
                     String question = charBuffer.toString(); 
                     String answer = getAnswer(question);
                     channel.write(CharsetHelper.encode(CharBuffer.wrap(answer)));
                 }
                 else {
                     //这里关闭channel,因为客户端已经关闭channel或者异常了
                     channel.close();               
                 }                      
             }
         }
         catch (Throwable t){
             t.printStackTrace();
             if (channel !=  null ){
                 channel.close();
             }
         }      
     }
     
     private  String getAnswer(String question){
         String answer =  null ;
         
         switch (question){
         case  "who" :
             answer =  "我是小娜\n" ;
             break ;
         case  "what" :
             answer =  "我是来帮你解闷的\n" ;
             break ;
         case  "where" :
             answer =  "我来自外太空\n" ;
             break ;
         case  "hi" :
             answer =  "hello\n" ;
             break ;
         case  "bye" :
             answer =  "88\n" ;
             break ;
         default :
                 answer =  "请输入 who, 或者what, 或者where" ;
         }
         
         return  answer;
     }
}


客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package  com.stevex.app.nio;
 
import  java.io.IOException;
import  java.net.InetSocketAddress;
import  java.nio.ByteBuffer;
import  java.nio.CharBuffer;
import  java.nio.channels.SelectionKey;
import  java.nio.channels.Selector;
import  java.nio.channels.SocketChannel;
import  java.util.Iterator;
import  java.util.Random;
import  java.util.concurrent.ArrayBlockingQueue;
import  java.util.concurrent.BlockingQueue;
import  java.util.concurrent.TimeUnit;
 
 
public  class  Client  implements  Runnable{
     private  BlockingQueue<String> words;
     private  Random random;
     
     public  static  void  main(String[] args) {      
         //种多个线程发起Socket客户端连接请求
         for ( int  i= 0 ; i< 10 ; i++){
             Client c =  new  Client();
             c.init();
             new  Thread(c).start();
         }      
     }
 
     @Override
     public  void  run() {     
         SocketChannel channel =  null ;
         Selector selector =  null ;
         try  {
             channel = SocketChannel.open();
             channel.configureBlocking( false );
             //请求连接
             channel.connect( new  InetSocketAddress( "localhost" 8383 ));
             selector = Selector.open();
             channel.register(selector, SelectionKey.OP_CONNECT);
             boolean  isOver =  false ;
             
             while (! isOver){
                 selector.select();
                 Iterator ite = selector.selectedKeys().iterator();
                 while (ite.hasNext()){
                     SelectionKey key = (SelectionKey) ite.next();
                     ite.remove();
                     
                     if (key.isConnectable()){
                         if (channel.isConnectionPending()){
                             if (channel.finishConnect()){
                                 //只有当连接成功后才能注册OP_READ事件
                                 key.interestOps(SelectionKey.OP_READ);
                                 
                                 channel.write(CharsetHelper.encode(CharBuffer.wrap(getWord())));
                                 sleep();
                             }
                             else {
                                 key.cancel();
                             }
                         }                                              
                     }
                     else  if (key.isReadable()){
                         ByteBuffer byteBuffer = ByteBuffer.allocate( 128 );                       
                         channel.read(byteBuffer);
                         byteBuffer.flip();
                         CharBuffer charBuffer = CharsetHelper.decode(byteBuffer);
                         String answer = charBuffer.toString(); 
                         System.out.println(Thread.currentThread().getId() +  "---"  + answer);
                         
                         String word = getWord();
                         if (word !=  null ){
                             channel.write(CharsetHelper.encode(CharBuffer.wrap(word)));
                         }
                         else {
                             isOver =  true ;
                         }
                         sleep();                       
                     }
                 }
             }                          
         catch  (IOException e) {
             e.printStackTrace();
         }
         finally {
             if (channel !=  null ){
                 try  {
                     channel.close();
                 catch  (IOException e) {                      
                     e.printStackTrace();
                 }                  
             }
             
             if (selector !=  null ){
                 try  {
                     selector.close();
                 catch  (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
     }
 
     private  void  init() {
         words =  new  ArrayBlockingQueue<String>( 5 );
         try  {
             words.put( "hi" );
             words.put( "who" );
             words.put( "what" );
             words.put( "where" );
             words.put( "bye" );
         catch  (InterruptedException e) {
             e.printStackTrace();
         }  
         
         random =  new  Random();
     }
     
     private  String getWord(){
         return  words.poll();
     }
 
     private  void  sleep() {
         try  {
             TimeUnit.SECONDS.sleep(random.nextInt( 3 ));
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
     }  
     
     private  void  sleep( long  l) {
         try  {
             TimeUnit.SECONDS.sleep(l);
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
     }
}


     本文转自sarchitect 51CTO博客,原文链接:http://blog.51cto.com/stevex/1581376 ,如需转载请自行联系原作者




相关文章
|
9天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
26 2
|
7天前
|
Java Linux
java读取linux服务器下某文档的内容
java读取linux服务器下某文档的内容
19 3
java读取linux服务器下某文档的内容
|
12天前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
9天前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
22 4
|
12天前
|
JavaScript 前端开发 Java
Java 8 新特性详解及应用示例
Java 8 新特性详解及应用示例
|
18天前
|
Kubernetes Java Maven
揭秘无服务器革命:Quarkus如何让Java应用在云端“零”负担起飞?
本文介绍如何使用Quarkus从零开始开发无服务器应用,通过示例代码和详细步骤引导读者掌握这一技术。无服务器架构让开发者无需管理服务器,具有自动扩展和成本效益等优势。Quarkus作为Kubernetes Native Java框架,优化了Java应用的启动速度和内存使用,适合无服务器环境。文章涵盖环境搭建、项目创建及部署全流程,并介绍了Quarkus的扩展性和监控工具,助力高效开发与应用性能提升。
25 9
|
11天前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
2月前
|
缓存 安全 Java
Java服务器端技术:Servlet与JSP的集成与扩展
Java服务器端技术:Servlet与JSP的集成与扩展
23 3
|
2月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
70 0
|
2月前
|
前端开发 安全 Java
在Java服务器端开发的浩瀚宇宙中,Servlet与JSP犹如两颗璀璨的明星,它们联袂登场,共同编织出动态网站的绚丽篇章。
在Java服务器端开发的浩瀚宇宙中,Servlet与JSP犹如两颗璀璨的明星,它们联袂登场,共同编织出动态网站的绚丽篇章。
18 0
下一篇
无影云桌面