用Java代码实现一个在线聊天功能案例

直接用 NIO 实现一个多人聊天案例,话不多说,直接上代码。

  1package com.mobaijun.TCP;
  2
  3import java.io.IOException;
  4import java.net.InetSocketAddress;
  5import java.nio.ByteBuffer;
  6import java.nio.channels.*;
  7import java.text.SimpleDateFormat;
  8import java.util.Date;
  9import java.util.Iterator;
 10
 11/**
 12 * @Author:Auser·杰
 13 * @DATE:2019/10/7 22:59
 14 */
 15public class ChatServer {
 16    private Selector selector;
 17    private ServerSocketChannel listenerChannel;
 18    //服务器端口
 19    private static final int PORT = 9999;
 20
 21    public ChatServer() {
 22        try {
 23            // 得到选择器
 24            selector = Selector.open();
 25            // 打开监听通道
 26            listenerChannel = ServerSocketChannel.open();
 27            // 绑定端口
 28            listenerChannel.bind(new InetSocketAddress(PORT));
 29            // 设置为非阻塞模式
 30            listenerChannel.configureBlocking(false);
 31            // 将选择器绑定到监听通道并监听 accept 事件
 32            listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
 33            printInfo("Chat Server is ready.......");
 34        } catch (IOException e) {
 35            e.printStackTrace();
 36        }
 37    }
 38
 39    public void start() {
 40        try {
 41            //不停轮询
 42            while (true) {
 43                //获取就绪 channel
 44                int count = selector.select();
 45                if (count > 0) {
 46                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
 47                    while (iterator.hasNext()) {
 48                        SelectionKey key = iterator.next();
 49                        // 监听到 accept
 50                        if (key.isAcceptable()) {
 51                            SocketChannel sc = listenerChannel.accept();
 52                            //非阻塞模式
 53                            sc.configureBlocking(false);
 54                            //注册到选择器上并监听 read
 55                            sc.register(selector, SelectionKey.OP_READ);
 56                            System.out.println(sc.getRemoteAddress().toString().substring(1) + "上线了...");
 57                            //将此对应的 channel 设置为 accept,接着准备接受其他客户端请求
 58                            key.interestOps(SelectionKey.OP_ACCEPT);
 59                        }
 60                        //监听到 read
 61                        if (key.isReadable()) {
 62                            readMsg(key); //读取客户端发来的数据
 63                        }
 64                        //一定要把当前 key 删掉,防止重复处理
 65                        iterator.remove();
 66                    }
 67                } else {
 68                    System.out.println("独自在寒风中等候...");
 69                }
 70            }
 71        } catch (IOException e) {
 72            e.printStackTrace();
 73        }
 74    }
 75
 76    private void readMsg(SelectionKey key) {
 77        SocketChannel channel = null;
 78        try {
 79            // 得到关联的通道
 80            channel = (SocketChannel) key.channel();
 81            //设置 buffer 缓冲区
 82            ByteBuffer buffer = ByteBuffer.allocate(1024);
 83            //从通道中读取数据并存储到缓冲区中
 84            int count = channel.read(buffer);
 85            //如果读取到了数据
 86            if (count > 0) {
 87                //把缓冲区数据转换为字符串
 88                String msg = new String(buffer.array());
 89                printInfo(msg);
 90                //将关联的 channel 设置为 read,继续准备接受数据
 91                key.interestOps(SelectionKey.OP_READ);
 92                BroadCast(channel, msg); //向所有客户端广播数据
 93            }
 94            buffer.clear();
 95        } catch (IOException e) {
 96            try {
 97                //当客户端关闭 channel 时,进行异常如理
 98                printInfo(channel.getRemoteAddress().toString().substring(1) + "下线了...");
 99                key.cancel(); //取消注册
100                channel.close(); //关闭通道
101            } catch (IOException e1) {
102                e1.printStackTrace();
103            }
104        }
105    }
106
107    public void BroadCast(SocketChannel except, String msg) throws IOException {
108        System.out.println("发送广播...");
109        //广播数据到所有的 SocketChannel 中
110        for (SelectionKey key : selector.keys()) {
111            Channel targetchannel = key.channel();
112            //排除自身
113            if (targetchannel instanceof SocketChannel && targetchannel != except) {
114                SocketChannel dest = (SocketChannel) targetchannel;
115                //把数据存储到缓冲区中
116                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
117                //往通道中写数据
118                dest.write(buffer);
119            }
120        }
121    }
122
123    private void printInfo(String str) {
124        //往控制台打印消息
125        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
126        System.out.println("[" + sdf.format(new Date()) + "] -> " + str);
127    }
128
129    public static void main(String[] args) {
130        ChatServer server = new ChatServer();
131        server.start();
132    }
133}
  • 上述代码使用了 NIO 编写了一个聊天程序的服务器端,可以接受客户端发来的数据,并能把数据广播给所有客户端。
 1package com.mobaijun.TCP;
 2
 3import java.io.IOException;
 4import java.net.InetSocketAddress;
 5import java.nio.ByteBuffer;
 6import java.nio.channels.SelectionKey;
 7import java.nio.channels.Selector;
 8import java.nio.channels.SocketChannel;
 9import java.util.Iterator;
10import java.util.Set;
11
12/**
13 * @Author:Auser·杰
14 * @DATE:2019/10/7 23:05
15 */
16public class ChatClient {
17    private final String HOST = "127.0.0.1"; //服务器地址
18    private int PORT = 9999; //服务器端口
19    private Selector selector;
20    private SocketChannel socketChannel;
21    private String userName;
22
23    public ChatClient() throws IOException {
24        //得到选择器
25        selector = Selector.open();
26        //连接远程服务器
27        socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
28        //设置非阻塞
29        socketChannel.configureBlocking(false);
30        //注册选择器并设置为 read
31        socketChannel.register(selector, SelectionKey.OP_READ);
32        //得到客户端 IP 地址和端口信息,作为聊天用户名使用
33        userName = socketChannel.getLocalAddress().toString().substring(1);
34        System.out.println("---------------Client(" + userName + ") is ready---------------");
35    }
36
37    //向服务器端发送数据
38    public void sendMsg(String msg) throws Exception {
39        //如果控制台输入 bye 就关闭通道,结束聊天
40        if (msg.equalsIgnoreCase("bye")) {
41            socketChannel.close();
42            socketChannel = null;
43            return;
44        }
45        msg = userName + "说: " + msg;
46        try {
47            //往通道中写数据
48            socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
49        } catch (IOException e) {
50            e.printStackTrace();
51        }
52    }
53
54    //从服务器端接收数据
55    public void receiveMsg() {
56        try {
57            int readyChannels = selector.select();
58            if (readyChannels > 0) { //有可用通道
59                Set selectedKeys = selector.selectedKeys();
60                Iterator keyIterator = selectedKeys.iterator();
61                while (keyIterator.hasNext()) {
62                    SelectionKey sk = (SelectionKey) keyIterator.next();
63                    if (sk.isReadable()) {
64                        //得到关联的通道
65                        SocketChannel sc = (SocketChannel) sk.channel();
66                        //得到一个缓冲区
67                        ByteBuffer buff = ByteBuffer.allocate(1024);
68                        //读取数据并存储到缓冲区
69                        sc.read(buff);
70                        //把缓冲区数据转换成字符串
71                        String msg = new String(buff.array());
72                        System.out.println(msg.trim());
73                    }
74                    keyIterator.remove(); //删除当前 SelectionKey,防止重复处理
75                }
76            } else {
77                System.out.println("人呢?都去哪儿了?没人聊天啊...");
78            }
79        } catch (IOException e) {
80            e.printStackTrace();
81        }
82    }
83}
  • 上述代码通过是 NIO 编写了一个聊天程序的客户端,可以向服务器端发送数据,并能接收服务器广播的数据。
 1package com.mobaijun.TCP;
 2
 3import java.util.Scanner;
 4
 5/**
 6 * @Author:Auser·杰
 7 * @DATE:2019/10/7 23:07
 8 */
 9public class TestMoBai {
10    public static void main(String[] args) throws Exception {
11        //创建一个聊天客户端对象
12        ChatClient chatClient = new ChatClient();
13        new Thread() { //单独开一个线程不断的接收服务器端广播的数据
14            @Override
15            public void run() {
16                while (true) {
17                    chatClient.receiveMsg();
18                    try { //间隔 3 秒
19                        sleep(3000);
20                    } catch (InterruptedException e) {
21                        e.printStackTrace();
22                    }
23                }
24            }
25        }.start();
26        Scanner scanner = new Scanner(System.in);
27        //在控制台输入数据并发送到服务器端
28        while (scanner.hasNextLine()) {
29            String msg = scanner.nextLine();
30            chatClient.sendMsg(msg);
31        }
32    }
33}
  • 上述代码运行了聊天程序的客户端,并在主线程中发送数据,在另一个线程中不断接收服务器端的广播数据,该代码运行一次就是一个聊天客户端,可以同时运行多个聊天客户端,聊天效果如下图所示。

本文分享自微信公众号 - 框架师(mohu121)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券