简易版聊天系统实现 Socket VS NIO两种实现方式

简介:

说是简单聊天系统,压根不能算是一个系统,顶多算个雏形。本文重点不在聊天系统设计和实现上,而是通过实现类似效果,展示下NIO 和Socket两种编程方式的差异性。说是Socket与NIO的编程方式,不太严谨,因为NIO的底层也是通过Socket实现的,但又想不出非常好的题目,就这样吧。

主要内容

Socket方式实现简易聊天效果

NIO方式实现简易聊天效果

两种方式的性能对比


前言

预期效果,是客户端之间进行“广播”式聊天,类似于QQ群聊天。希望以后有机会,以此简易版为基础,不断演进,演练下在线聊天系统。

1.Socket方式实现简易聊天效果

1.1服务端 Server.java

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
package  com.example.socket.server;
import  java.io.IOException;
import  java.net.InetAddress;
import  java.net.ServerSocket;
import  java.net.Socket;
import  java.util.ArrayList;
import  java.util.List;
public  class  Server {
     private  static  int  port = 9999 ;
     // 可接受请求队列的最大长度
     private  static  int  backlog= 100 ;
     // 绑定到本机的IP地址
     private  static  final  String bindAddr =  "127.0.0.1" ;
     //socket字典列表
     private  static  List<Socket> nodes=  new  ArrayList<Socket>();
     public  static  void  main(String[] args) {
         try  {
             ServerSocket ss =  new  ServerSocket(port, backlog,InetAddress.getByName(bindAddr));
             for (;;){
                 //发生阻塞,等待客户端连接            
                 Socket sc = ss.accept();
                 nodes.add(sc);
                 InetAddress addr = sc.getLocalAddress();
                 System.out.println( "create new session from " +addr.getHostName()+ ":" +sc.getPort()+ "\n" );            
                //针对一个Socket 客户端 启动两个线程,分别是接收信息,发送信息
                 new  Thread( new  ServerMessageReceiver(sc,nodes)).start();
                 new  ServerMessageSender(sc).start();
             }            
         catch  (IOException e) {
             e.printStackTrace();
         }
     }
}

1.2 消息接收端 ServerMessageReceiver.java

额外负责信息广播

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
package  com.example.socket.server;
import  java.io.BufferedReader;
import  java.io.BufferedWriter;
import  java.io.IOException;
import  java.io.InputStreamReader;
import  java.io.OutputStreamWriter;
import  java.net.Socket;
import  java.util.ArrayList;
import  java.util.List;
/**
 
  * 接收消息
  *
  */
public  class  ServerMessageReceiver  implements  Runnable{
     private  Socket socket;
     //socket字典列表
     private  List<Socket> nodes=  new  ArrayList<Socket>();
     public  ServerMessageReceiver(Socket sc,List<Socket> nodes){
         this .socket=sc;
         this .nodes=nodes;
     }
     /**
      * 信息广播到其他节点
      */
     @Override
     public  void  run() {    
         try  {
             BufferedReader reader =  new  BufferedReader( new  InputStreamReader(socket.getInputStream(),  "UTF-8" ));
             //接收到的消息
             String content;
             while  ( true ) {
                 if (socket.isClosed()){
                     System.out.println( "Socket已关闭,无法获取消息" );
                     reader.close();
                     socket.close();
                     break ;
                 }
                 content=reader.readLine();
                 if (content!= null  && content.equals( "bye" )){
                     System.out.println( "对方请求关闭连接,无法继续进行聊天" );
                     reader.close();
                     socket.close();
                     break ;
                 }
                 String message =socket.getPort()+ ":" +content;
                 //广播信息
                 for (Socket n: this .nodes){
                     if (n != this .socket){
                         BufferedWriter writer= new  BufferedWriter( new  OutputStreamWriter(n.getOutputStream(), "UTF-8" ));
                         writer.write(message);
                         writer.newLine();
                         writer.flush();        
                     }
                 }
             }
         catch  (IOException e) {            
             e.printStackTrace();
         }
         
     }
 
}

1.3消息发送服务端 ServerMessageSender.java

主要作用:发送欢迎信息

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
package  com.example.socket.server;
import  java.io.BufferedWriter;
import  java.io.IOException;
import  java.io.OutputStreamWriter;
import  java.net.Socket;
public  class  ServerMessageSender  extends  Thread{
     private  Socket socket;
 
     public  ServerMessageSender(Socket socket) {
         this .socket = socket;
     }
/**
  * 只发送一个欢迎信息
  */
     @Override
     public  void  run() {
         try  {
             BufferedWriter writer= new  BufferedWriter( new  OutputStreamWriter(socket.getOutputStream(), "UTF-8" ));
//            BufferedReader inputReader=new BufferedReader(new InputStreamReader(System.in));
             try  {
                 String msg= "server :welcome " +socket.getPort();
                 writer.write(msg);
                 writer.newLine();
                 writer.flush();
             catch  (IOException e) {
                 e.printStackTrace();
             }
         catch  (Exception e) {
             e.printStackTrace();
         }
     }
}

1.4 客户端 Client.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package  com.example.socket.client;
import  java.net.InetAddress;
import  java.net.Socket;
 
public  class  Client {
     // 监听端口号
     private  static  final  int  port =  9999 ;
     // 绑定到本机的IP地址
     private  static  final  String bindAddr =  "127.0.0.1" ;
 
     public  static  void  main(String[] args) {
         try  {
             System.out.println( "正在连接Socket服务器" );
             Socket socket= new  Socket(InetAddress.getByName(bindAddr),port);
             System.out.println( "已连接\n==================================" );
             new  ClientMessageSender(socket).start();
             new  ClientMessageReceiver(socket).start();
         catch  (Exception e) {
             e.printStackTrace();
         }
         
     }
}

1.4 消息接收客户端  ClientMessageReceiver.java

仅仅是输出

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
package  com.example.socket.client;
 
import  java.io.BufferedReader;
import  java.io.InputStreamReader;
import  java.net.Socket;
 
public  class  ClientMessageReceiver  extends  Thread {
     
     private  Socket socket;
     
     public  ClientMessageReceiver(Socket socket) {
         this .socket=socket;    
     }
 
     @Override
     public  void  run() {
         try  {
             // 获取socket的输 出\入流
             BufferedReader reader =  new  BufferedReader( new  InputStreamReader(socket.getInputStream(),  "UTF-8" ));
             //接收到的消息
             String content;
             while  ( true ) {
                 if (socket.isClosed()){
                     System.out.println( "Socket已关闭,无法获取消息" );
                     reader.close();
                     socket.close();
                     break ;
                 }
                 content=reader.readLine();
                 if (content.equals( "bye" )){
                     System.out.println( "对方请求关闭连接,无法继续进行聊天" );
                     reader.close();
                     socket.close();
                     break ;
                 }
                 System.out.println(content+ "\n" );
             }
             reader.close();
             socket.close();
         catch  (Exception e) {
             e.printStackTrace();
         }
     }
     
}

1.5 消息发送客户端 ClientMessageSender.java 

通过输入流输入,将信息传入Socket

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
package  com.example.socket.client;
 
import  java.io.BufferedReader;
import  java.io.BufferedWriter;
import  java.io.IOException;
import  java.io.InputStreamReader;
import  java.io.OutputStreamWriter;
import  java.net.Socket;
 
public  class  ClientMessageSender  extends  Thread {
     
     private  Socket socket;
 
     public  ClientMessageSender(Socket socket) {
         this .socket = socket;
     }
 
     @Override
     public  void  run() {
         try  {
             BufferedWriter writer= new  BufferedWriter( new  OutputStreamWriter(socket.getOutputStream(), "UTF-8" ));
             BufferedReader inputReader= new  BufferedReader( new  InputStreamReader(System.in));
             try  {
                 String msg;
                 for (;;){
                     msg=inputReader.readLine();
                     if (msg.toLowerCase().equals( "exit" )){
                         System.exit( 0 );
                     }
                     if (socket.isClosed()){
                         System.out.println( "Socket已关闭,无法发送消息" );
                         writer.close();
                         socket.close();
                         break ;
                     }
                     writer.write(msg);
                     writer.newLine();
                     writer.flush();
                     System.out.println();
                 }
             catch  (IOException e) {
                 e.printStackTrace();
             }
         catch  (Exception e) {
             e.printStackTrace();
         }
     }
     
}

1.6 效果

wKiom1fBjLLBDw4eAABAPFTSfRw104.png

2.NIO方式实现简易聊天效果

2.1服务端 NServer.java

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
package  com.example.nio;
import  java.io.IOException;
import  java.net.InetSocketAddress;
import  java.nio.ByteBuffer;
import  java.nio.channels.Channel;
import  java.nio.channels.SelectionKey;
import  java.nio.channels.Selector;
import  java.nio.channels.ServerSocketChannel;
import  java.nio.channels.SocketChannel;
import  java.nio.charset.Charset;
 
/**
  * 服务器端
  */
public  class  NServer {
     private  Selector selector;
     private  Charset charset = Charset.forName( "UTF-8" );
     
     public  void  init()  throws  Exception {
         selector = Selector.open();
         ServerSocketChannel server = ServerSocketChannel.open();
         InetSocketAddress isa =  new  InetSocketAddress( "127.0.0.1" 3000 );
         server.socket().bind(isa);
         server.configureBlocking( false );
         server.register(selector, SelectionKey.OP_ACCEPT);
         while  (selector.select() >  0 ) {
             for  (SelectionKey key : selector.selectedKeys()) {
                 selector.selectedKeys().remove(key);
                 if  (key.isAcceptable()) {
                     SocketChannel sc = server.accept();
                     System.out.println( "create new session from " +sc.getRemoteAddress()+ "\n" );
                     sc.configureBlocking( false );
                     sc.register(selector, SelectionKey.OP_READ);
                     key.interestOps(SelectionKey.OP_ACCEPT);        
                     sc.write(charset.encode( "welcome" +sc.getRemoteAddress()));
                 }
                 
                 if  (key.isReadable()) {
                     SocketChannel sc = (SocketChannel)key.channel();
                     ByteBuffer buff = ByteBuffer.allocate( 1024 );
                     String content =  "" ;
                     try  {
                         while  (sc.read(buff) >  0 ) {
                             buff.flip();
                             content += charset.decode(buff);
                             buff.clear();
                         }                        
                         key.interestOps(SelectionKey.OP_READ);
                     catch  (IOException e) {
                         key.cancel();
                         if  (key.channel() !=  null )
                             key.channel().close();
                     }
                     if  (content.length() >  0 ) {
                         for  (SelectionKey sk : selector.keys()) {
                             Channel targetchannel = sk.channel();
                            if  (targetchannel  instanceof  SocketChannel && targetchannel!=sc) {
                                 SocketChannel dest = (SocketChannel)targetchannel;
                                 dest.write(charset.encode(content));
                             }
                         }
                     }
                 }
             }
         }
     }
     
     public  static  void  main(String[] args)  throws  Exception {
         new  NServer().init();
     }
}

2.2 客户端 NClient.java

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
package  com.example.nio;
import  java.io.IOException;
import  java.net.InetSocketAddress;
import  java.nio.ByteBuffer;
import  java.nio.channels.SelectionKey;
import  java.nio.channels.Selector;
import  java.nio.channels.SocketChannel;
import  java.nio.charset.Charset;
import  java.util.Scanner;
 
/**
  * 客户端
  */
public  class  NClient {
     private  Selector selector;
     private  Charset charset = Charset.forName( "UTF-8" );
     private  SocketChannel sc =  null ;
     
     public  void  init()  throws  IOException {
         selector = Selector.open();
         InetSocketAddress isa =  new  InetSocketAddress( "127.0.0.1" 3000 );
         sc = SocketChannel.open(isa);
         sc.configureBlocking( false );
         sc.register(selector, SelectionKey.OP_READ);
         new  ClientThread().start();
         @SuppressWarnings ( "resource" )
         Scanner scan =  new  Scanner(System.in);
         while  (scan.hasNextLine()) {
             sc.write(charset.encode(scan.nextLine()));
         }
     }
     
     private  class  ClientThread  extends  Thread {
         public  void  run() {
             try  {
                 while  (selector.select() >  0 ) {
                     for  (SelectionKey sk : selector.selectedKeys()) {
                         selector.selectedKeys().remove(sk);
                         if  (sk.isReadable()) {
                             SocketChannel sc = (SocketChannel)sk.channel();
                             ByteBuffer buff = ByteBuffer.allocate( 1024 );
                             String content =  "" ;
                             while  (sc.read(buff) >  0 ) {
                                 sc.read(buff);
                                 buff.flip();
                                 content += charset.decode(buff);
                                 buff.clear();
                             }
                             System.out.println( "chat info: "  + content);
                             sk.interestOps(SelectionKey.OP_READ);
                         }
                     }
                 }
             catch  (IOException e) {
                 e.printStackTrace();
             }
         }
     }
     
     public  static  void  main(String[] args)  throws  IOException {
         new  NClient().init();
     }
}

代码来自

https://github.com/xeostream/chat
2.3 效果

wKioL1fBkqDwtc2TAAA9tjiDK4E475.png


3. 对比

从API操作上来看,NIO偏复杂,面向的是异步编程方式,重点围绕Selector,SelectKey操作。

性能对比,主要简单模拟下Echo情景:客户端连接成功,服务端返回一条信息。

3.1Socket性能测试入口

可以关闭ServerMessageReceiver线程

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
package  com.example.socket.client;
 
import  java.io.BufferedReader;
import  java.io.IOException;
import  java.io.InputStreamReader;
import  java.net.InetAddress;
import  java.net.Socket;
import  java.net.UnknownHostException;
import  java.util.concurrent.Callable;
import  java.util.concurrent.ExecutionException;
import  java.util.concurrent.Executors;
import  java.util.concurrent.Future;
 
public  class  BenchmarkClient {
     // 监听端口号
     private  static  final  int  port =  9999 ;
     // 绑定到本机的IP地址
     private  static  final  String bindAddr =  "127.0.0.1" ;
 
     /**
      * @param <T>
      * @param args
      */
     public  static  <T>  void  main(String[] args) {
         try  {
             long  s=System.currentTimeMillis();
             for  ( int  i =  0 ; i <  1000 ; i++) {
                 final  Socket socket =  new  Socket(
                         InetAddress.getByName(bindAddr), port);
                 Future<String> future = Executors.newFixedThreadPool( 4 ).submit(
                         new  Callable<String>() {
                             @Override
                             public  String call()  throws  Exception {
                                 BufferedReader reader =  new  BufferedReader(
                                         new  InputStreamReader(socket
                                                 .getInputStream(),  "UTF-8" ));
                                 String content = reader.readLine();
                                 return  Thread.currentThread().getName()+ "--->" +content;
                             }
                         });                
                 System.out.println(i+ ":" +future.get());
                 socket.close();
             }
             long  e=System.currentTimeMillis();
             System.out.println(e-s);
         catch  (UnknownHostException e) {
             e.printStackTrace();
         catch  (IOException e) {
             e.printStackTrace();
         catch  (InterruptedException e) {
             e.printStackTrace();
         catch  (ExecutionException e) {
             e.printStackTrace();
         }
 
     }
 
}

3.2 NIO性能测试入口

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
package  com.example.nio;
import  java.io.BufferedReader;
import  java.io.IOException;
import  java.io.InputStreamReader;
import  java.net.InetSocketAddress;
import  java.nio.ByteBuffer;
import  java.nio.channels.SelectionKey;
import  java.nio.channels.Selector;
import  java.nio.channels.SocketChannel;
import  java.nio.charset.Charset;
import  java.util.Scanner;
import  java.util.concurrent.Callable;
import  java.util.concurrent.ExecutionException;
import  java.util.concurrent.Executors;
import  java.util.concurrent.Future;
 
/**
  * 客户端
  * @author arthur
  */
public  class  BenchMarkNClient {
     private  Selector selector;
     private  Charset charset = Charset.forName( "UTF-8" );
     private  SocketChannel sc =  null ;
     
     public  void  init()  throws  IOException {
         long  s = System.currentTimeMillis();
         selector = Selector.open();
         InetSocketAddress isa =  new  InetSocketAddress( "127.0.0.1" 3000 );
         for  ( int  i =  0 ; i <  10000 ; i++) {    
             sc = SocketChannel.open(isa);
             sc.configureBlocking( false );
             sc.register(selector, SelectionKey.OP_READ);
             Future<String> future = Executors.newFixedThreadPool( 4 ).submit( new  ClientTask());
             try  {
                 System.out.println(i+ ":" +future.get());
             catch  (InterruptedException e) { 
                 e.printStackTrace();
             catch  (ExecutionException e) {
                 e.printStackTrace();
             }
         }
         long  e= System.currentTimeMillis();
         System.out.println(e-s);
     }
     
     private  class  ClientTask  implements  Callable<String> {
         public  String call() {
             try  {
                 while  (selector.select() >  0 ) {
                     for  (SelectionKey sk : selector.selectedKeys()) {
                         selector.selectedKeys().remove(sk);
                         if  (sk.isReadable()) {
                             SocketChannel sc = (SocketChannel)sk.channel();
                             ByteBuffer buff = ByteBuffer.allocate( 1024 );
                             String content =  "" ;
                             while  (sc.read(buff) >  0 ) {
                                 sc.read(buff);
                                 buff.flip();
                                 content += charset.decode(buff);
                                 buff.clear();
                             }
 
                             sk.interestOps(SelectionKey.OP_READ);
                             return  content;
                         }
                     }
                 }
             catch  (IOException e) {
                 e.printStackTrace();
             }
             return  null ;
         }
     }
     
     public  static  void  main(String[] args)  throws  IOException {
         new  BenchMarkNClient().init();
     }
}

3.3 性能对比

次数
NIO
SOCKET(ms)
1000
525
637
2000
1411 1215
2000(休眠时间为100毫秒)
205928 206313
5000
6731
2976

次数较少时,NIO性能较好。但随着次数增加,性能下降非常厉害。(存疑)

当通讯时间变长时,发现NIO性能又相对提高了。

可见一个技术的好坏,是和业务场景分不开的。




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

相关文章
|
3月前
|
网络协议
关于套接字socket的网络通信。&聊天系统 聊天软件
关于套接字socket的网络通信。&聊天系统 聊天软件
|
6月前
|
存储 监控 Java
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
|
缓存 Java API
02RPC - socket nio原理
02RPC - socket nio原理
41 0
|
缓存 Java
Java NIO实战篇:使用Socket实现报文交互
Java NIO实战篇:使用Socket实现报文交互
245 0
java.nio.* 篇(1) FileChannel AsynchronousFileChannel ServerSocket Socket 使用案例
java.nio.* 篇(1) FileChannel AsynchronousFileChannel ServerSocket Socket 使用案例
|
Java Apache
学习socket nio 之 mina实例(1)
学习socket nio 之 mina实例(1)
140 0
学习socket nio 之 mina实例(1)
学习socket nio 之 mina实例(2)
学习socket nio 之 mina实例(2)
145 0
|
消息中间件 关系型数据库 MySQL
消息队列、socket(UDP)实现简易聊天系统
  前言:   最近在学进程间通信,所以做了一个小项目练习一下。主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作数据库不熟悉的可以参照我的这篇博客:https://www.cnblogs.com/liudw-0215/p/9593414.html,所有代码提交我的Github上,地址:https://github.com/ldw0215/Chat-System.git,可以自行下载,然后make一下就可以了。
1674 0
|
消息中间件 关系型数据库 MySQL
用消息队列和socket实现聊天系统
  前言:最近在学进程间通信,所以做了一个小项目练习一下。主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作不熟悉的可以参照我的这篇博客:https://www.cnblogs.com/liudw-0215/p/9593414.html,所有代码提交我的Github上,地址:https://github.com/ldw0215/Chat-System.git,可以自行下载,然后make一下就可以了。
2037 0
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用