溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

簡易版聊天系統(tǒng)實現(xiàn) Socket VS NIO兩種實現(xiàn)方式

發(fā)布時間:2020-08-02 02:40:34 來源:網(wǎng)絡(luò) 閱讀:2365 作者:randy_shandong 欄目:數(shù)據(jù)庫

說是簡單聊天系統(tǒng),壓根不能算是一個系統(tǒng),頂多算個雛形。本文重點不在聊天系統(tǒng)設(shè)計和實現(xiàn)上,而是通過實現(xiàn)類似效果,展示下NIO 和Socket兩種編程方式的差異性。說是Socket與NIO的編程方式,不太嚴謹,因為NIO的底層也是通過Socket實現(xiàn)的,但又想不出非常好的題目,就這樣吧。

主要內(nèi)容

Socket方式實現(xiàn)簡易聊天效果

NIO方式實現(xiàn)簡易聊天效果

兩種方式的性能對比


前言

預(yù)期效果,是客戶端之間進行“廣播”式聊天,類似于QQ群聊天。希望以后有機會,以此簡易版為基礎(chǔ),不斷演進,演練下在線聊天系統(tǒng)。

1.Socket方式實現(xiàn)簡易聊天效果

1.1服務(wù)端 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(;;){
                //發(fā)生阻塞,等待客戶端連接            
                Socket sc = ss.accept();
                nodes.add(sc);
                InetAddress addr = sc.getLocalAddress();
                System.out.println("create new session from "+addr.getHostName()+":"+sc.getPort()+"\n");            
               //針對一個Socket 客戶端 啟動兩個線程,分別是接收信息,發(fā)送信息
                new Thread(new ServerMessageReceiver(sc,nodes)).start();
                new ServerMessageSender(sc).start();
            }            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.2 消息接收端 ServerMessageReceiver.java

額外負責(zé)信息廣播

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;
    }
    /**
     * 信息廣播到其他節(jié)點
     */
    @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已關(guān)閉,無法獲取消息");
                    reader.close();
                    socket.close();
                    break;
                }
                content=reader.readLine();
                if(content!=null && content.equals("bye")){
                    System.out.println("對方請求關(guān)閉連接,無法繼續(xù)進行聊天");
                    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消息發(fā)送服務(wù)端 ServerMessageSender.java

主要作用:發(fā)送歡迎信息

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;
    }
/**
 * 只發(fā)送一個歡迎信息
 */
    @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 {
    // 監(jiān)聽端口號
    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服務(wù)器");
            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已關(guān)閉,無法獲取消息");
                    reader.close();
                    socket.close();
                    break;
                }
                content=reader.readLine();
                if(content.equals("bye")){
                    System.out.println("對方請求關(guān)閉連接,無法繼續(xù)進行聊天");
                    reader.close();
                    socket.close();
                    break;
                }
                System.out.println(content+"\n");
            }
            reader.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}

1.5 消息發(fā)送客戶端 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已關(guān)閉,無法發(fā)送消息");
                        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 效果

簡易版聊天系統(tǒng)實現(xiàn)  Socket VS NIO兩種實現(xiàn)方式

2.NIO方式實現(xiàn)簡易聊天效果

2.1服務(wù)端 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;

/**
 * 服務(wù)器端
 */
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 效果

簡易版聊天系統(tǒng)實現(xiàn)  Socket VS NIO兩種實現(xiàn)方式


3. 對比

從API操作上來看,NIO偏復(fù)雜,面向的是異步編程方式,重點圍繞Selector,SelectKey操作。

性能對比,主要簡單模擬下Echo情景:客戶端連接成功,服務(wù)端返回一條信息。

3.1Socket性能測試入口

可以關(guān)閉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 {
    // 監(jiān)聽端口號
    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 性能對比

次數(shù)
NIO
SOCKET(ms)
1000
525
637
2000
14111215
2000(休眠時間為100毫秒)
205928206313
5000
6731
2976

次數(shù)較少時,NIO性能較好。但隨著次數(shù)增加,性能下降非常厲害。(存疑)

當(dāng)通訊時間變長時,發(fā)現(xiàn)NIO性能又相對提高了。

可見一個技術(shù)的好壞,是和業(yè)務(wù)場景分不開的。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI