您好,登錄后才能下訂單哦!
說是簡單聊天系統,壓根不能算是一個系統,頂多算個雛形。本文重點不在聊天系統設計和實現上,而是通過實現類似效果,展示下NIO 和Socket兩種編程方式的差異性。說是Socket與NIO的編程方式,不太嚴謹,因為NIO的底層也是通過Socket實現的,但又想不出非常好的題目,就這樣吧。
主要內容
Socket方式實現簡易聊天效果
NIO方式實現簡易聊天效果
兩種方式的性能對比
前言
預期效果,是客戶端之間進行“廣播”式聊天,類似于QQ群聊天。希望以后有機會,以此簡易版為基礎,不斷演進,演練下在線聊天系統。
1.Socket方式實現簡易聊天效果
1.1服務端 Server.java
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
額外負責信息廣播
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
主要作用:發送歡迎信息
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
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
僅僅是輸出
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
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 效果
2.NIO方式實現簡易聊天效果
2.1服務端 NServer.java
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
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 效果
3. 對比
從API操作上來看,NIO偏復雜,面向的是異步編程方式,重點圍繞Selector,SelectKey操作。
性能對比,主要簡單模擬下Echo情景:客戶端連接成功,服務端返回一條信息。
3.1Socket性能測試入口
可以關閉ServerMessageReceiver線程
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性能測試入口
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性能又相對提高了。
可見一個技術的好壞,是和業務場景分不開的。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。