千家信息网

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

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,说是简单聊天系统,压根不能算是一个系统,顶多算个雏形。本文重点不在聊天系统设计和实现上,而是通过实现类似效果,展示下NIO 和Socket两种编程方式的差异性。说是Socket与NIO的编程方式,不太
千家信息网最后更新 2026年11月01日简易版聊天系统实现 Socket VS NIO两种实现方式

说是简单聊天系统,压根不能算是一个系统,顶多算个雏形。本文重点不在聊天系统设计和实现上,而是通过实现类似效果,展示下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 nodes= new ArrayList();    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 nodes= new ArrayList();    public ServerMessageReceiver(Socket sc,List 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      * @param args     */    public static  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 future = Executors.newFixedThreadPool(4).submit(                        new Callable() {                            @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 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 {        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
14111215
2000(休眠时间为100毫秒)
205928206313
5000
6731
2976

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

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

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

客户 客户端 消息 信息 UTF-8 方式 性能 效果 服务 简易 系统 广播 地址 次数 本机 编程 入口 口号 字典 对方 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 亿人帮互联网科技有限公司 广州百单互联网科技有限公司 互联网巨头科技公司 北京互联网络科技有限公司 互联网科技有限公司 翻译 甘肃拓世互联网科技有限公司 四个字互联网科技公司取名 顺网科技2021年互联网排名 互联网经济与金融科技 阿大互联网科技有限公司 崇明区互联网教育科技售后服务 注册互联网科技公司经营范围大全 成都互联网科技公司工作好不好 互联网行业科技股 互联网科技圈年度人物 富国互联网金融科技混合基金 风投北京互联网科技 互联网科技创新创业项目 互联网领先科技成果宣传视频 山东亿川互联网科技有限责任公司 上海卓越科技互联网 上海互联网科技有限公司 广州互联网络科技有限公司 苏州互联网科技贷款公司 赫美联合互联网科技 搜狐科技互联网 呼和浩特新华互联网科技学院 中和天地互联网科技有限公司 烟台灵鸽互联网科技有限公司 上海科技产业互联网平台
0