使用Nio实现一个简易的群聊和单聊

服务端:接收客户端发送的消息,并进行转发。

package socket.demo2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
* 聊天服务端
* @author 一池春水倾半城
* @date 2019/10/22
*/
public class Server {
    private Selector selector;
    // 人数统计、昵称和主机地址记录
    private Map<String, String> users = new HashMap<>();
    ByteBuffer buffer = ByteBuffer.allocate(2048);

    public Server(int port) throws IOException {
        // 开启服务端通道
        ServerSocketChannel server = ServerSocketChannel.open();
        // 监听端口
        server.bind(new InetSocketAddress(port));
        // 切换非阻塞模式
        server.configureBlocking(false);
        // 开启选择器
        selector = Selector.open();
        // 选择器注册到服务端通道上
        server.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端启动...");
    }

    /**
     * 通过监听选择键来监听客户端连接
     * @throws IOException
     */
    public void listen() throws IOException {
        while(true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 移除已处理的选择键
                iterator.remove();
                // 处理选择键
                handle(key);
            }
            // 清空选择键
            selector.selectedKeys().clear();
        }
    }

    /**
     * 处理选择键
     * @param key
     * @throws IOException
     */
    private void handle(SelectionKey key) throws IOException {
        ServerSocketChannel server;
        SocketChannel client;
        if (key.isAcceptable()) {
            // 获取key对应的通道
            server = (ServerSocketChannel) key.channel();
            // 获取服务端连接
            client = server.accept();
            client.configureBlocking(false);
            // 注册到选择器,指定行为是"读"
            client.register(selector, SelectionKey.OP_READ);
            System.out.println("接收到来自 " + client.getRemoteAddress() + " 的新连接!");
            boardMsg("当前在线人数:" + users.size());
            write("\n欢迎来到本聊天室,请输入昵称:", client);
            key.interestOps(SelectionKey.OP_ACCEPT);
        } else if (key.isReadable()) {
            client = (SocketChannel) key.channel();
            try {
                String[] msg = rec(client).split("###");
                if (msg.length == 1) {      // 设置昵称
                    if (users.containsValue(msg[0])) {
                        write("昵称重复,请重新输入!", client);
                    } else {
                        users.put(client.getRemoteAddress().toString(), msg[0]);
                        write("hello " + msg[0], client);
                    }
                } else if (msg.length == 2) {
                    System.out.println(client.getRemoteAddress() + " named " + msg[0] + " said to all: " + msg[1]);
                    boardMsg(msg[0] + "说:" + msg[1]);
                } else if (msg.length == 3) {
                    System.out.println(client.getRemoteAddress() + " named " + msg[0] + " said to " + msg[2] + ": " + msg[1]);
                    p2pChat(msg[0] + "说:" + msg[1], msg[2], client);
                }
            } catch (Exception e) {
                String address = client.getRemoteAddress().toString();
                System.out.println(address + " 断开了连接!");
                client.close();
                String name = users.get(address);
                users.remove(address);
                boardMsg("用户 " + name + " 离开了!当前在线人数:" + users.size());
            }
        }
    }

    /**
     * 读消息
     * @param channel
     * @return
     * @throws IOException
     */
    private String rec(SocketChannel channel) throws IOException {
        buffer.clear();
        int count = channel.read(buffer);
        buffer.flip();
        return new String(buffer.array(), 0, count, StandardCharsets.UTF_8);
    }

    /**
     * 写消息
     * @param msg
     * @param channel
     * @throws IOException
     */
    private void write(String msg, SocketChannel channel) throws IOException {
        buffer.clear();
        buffer.put(msg.getBytes(StandardCharsets.UTF_8));
        buffer.flip();
        channel.write(buffer);
    }

    /**
     * 分发消息给全部客户端,群聊
     * @param msg
     * @throws IOException
     */
    private void boardMsg(String msg) throws IOException {
        for (SelectionKey key:selector.keys()) {
            Channel target = key.channel();
            if (target.isOpen() && target instanceof SocketChannel) {
                write(msg, (SocketChannel) target);
            }
        }
    }

    /**
     * 发送消息给指定客户端,单聊
     * @param msg
     * @param targetName
     * @param source
     * @throws IOException
     */
    private void p2pChat(String msg, String targetName, SocketChannel source) throws IOException {
        boolean flag = false;
        for (SelectionKey key:selector.keys()) {
            Channel target = key.channel();
            if (target.isOpen() && target instanceof SocketChannel) {
                SocketChannel tar = (SocketChannel) target;
                String name = users.get(tar.getRemoteAddress().toString());
                if (name.equals(targetName)) {
                    write(msg, (SocketChannel) target);
                    write(msg, source);
                    flag = true;
                    break;
                }
            }
        }
        if (!flag) {
            write("找不到该用户!", source);
        }
    }

    public static void main(String[] args) throws IOException {
        Server server = new Server(7777);
        server.listen();
    }

}

客户端:发送消息和读取消息

package socket.demo2;

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.StandardCharsets;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * @author 一池春水倾半城
 * @date 2019/10/22
 */
public class Client {
    static ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 记录昵称是否设置成功
    volatile static boolean success = false;
    // 用户昵称
    volatile static String name = "sxh";

    /**
     * 读消息
     * @param channel
     * @return
     * @throws IOException
     */
    private static String rec(SocketChannel channel) throws IOException {
        buffer.clear();
        int count = channel.read(buffer);
        buffer.flip();
        return new String(buffer.array(), 0, count, StandardCharsets.UTF_8);
    }

    /**
     * 写消息
     * @param msg
     * @param channel
     * @throws IOException
     */
    private static void write(String msg, SocketChannel channel) throws IOException {
        buffer.clear();
        buffer.put(msg.getBytes(StandardCharsets.UTF_8));
        buffer.flip();
        channel.write(buffer);
    }

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",7777));
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);

        // 开启新线程,从服务端读取消息
        new Thread(() -> {
            SocketChannel client = null;
            while (true) {
                try {
                    selector.select();
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isReadable()) {
                            client = (SocketChannel) key.channel();
                            String msg = rec(client);
                            // 昵称设置成功
                            if (msg.contains("hello")) {
                                // 标识置为true
                                success = true;
                                name = msg.substring(6);
                            }
                            System.out.println(msg);
                            key.interestOps(SelectionKey.OP_READ);
                        }
                    }
                    selectionKeys.clear();
                } catch (IOException e) {
                    if (client != null) {
                        try {
                            client.close();
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    }
                }
            }
        }).start();

        // 主线程,用于写消息给服务端
        Scanner scanner = new Scanner(System.in);
        String tmp = "";
        while (true) {
            tmp = scanner.nextLine();
            if (success) {  // 昵称设置成功,开始聊天
                // 单聊([消息]@[接收人])
                if (tmp.contains("@")) {
                    tmp = tmp.replace("@", "###");
                }
                write(name + "###" + tmp, socketChannel);
            } else {    // 昵称尚未设置成功,继续设置
                write(tmp, socketChannel);
            }
        }
    }
}

原文地址:https://www.cnblogs.com/sxhjoker/p/11719449.html

时间: 2024-08-29 02:33:10

使用Nio实现一个简易的群聊和单聊的相关文章

websocket实现群聊和单聊(转)

昨日内容回顾 1.Flask路由 1.endpoint="user" # 反向url地址 2.url_address = url_for("user") 3.methods = ["GET","POST"] # 允许请求进入视图函数的方式 4.redirect_to # 在进入视图函数之前重定向 5./index/<nid> # 动态参数路由 <int:nid> def index(nid) 6.str

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解 WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据. 我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而

Tinywebserver:一个简易的web服务器

这是学习网络编程后写的一个练手的小程序,可以帮助复习I/O模型,epoll使用,线程池,HTTP协议等内容. 程序代码是基于<Linux高性能服务器编程>一书编写的. 首先回顾程序中的核心内容和主要问题,最后给出相关代码. 0. 功能和I/O模型 实现简易的HTTP服务端,现仅支持GET方法,通过浏览器访问可以返回相应内容. I/O模型采用Reactor(I/O复用 + 非阻塞I/O) + 线程池. 使用epoll事件循环用作事件通知,如果listenfd上可读,则调用accept,把新建的f

一个简易的web服务器:Tinywebserver

这是学习网络编程后写的一个练手的小程序,可以帮助复习I/O模型,epoll使用,线程池,HTTP协议等内容. 程序代码是基于<Linux高性能服务器编程>一书编写的. 首先回顾程序中的核心内容和主要问题,最后给出相关代码. 0. 功能和I/O模型 实现简易的HTTP服务端,现仅支持GET方法,通过浏览器访问可以返回相应内容. I/O模型采用Reactor(I/O复用 + 非阻塞I/O) + 线程池. 使用epoll事件循环用作事件通知,如果listenfd上可读,则调用accept,把新建的f

C 基于UDP实现一个简易的聊天室

引言 本文是围绕Linux udp api 构建一个简易的多人聊天室.重点看思路,帮助我们加深 对udp开发中一些api了解.相对而言udp socket开发相比tcp socket开发注意的细节要少很多. 但是水也很深. 本文就当是一个demo整合帮助开发者回顾和继续了解 linux udp开发的基本流程. 首先我们来看看 linux udp 和 tcp的异同. /* 这里简单比较一下TCP和UDP在编程实现上的一些区别: TCP流程 建立一个TCP连接需要三次握手,而断开一个TCP则需要四个

Parallel Python——一个简易的分布式计算系统

如何搭建一个快速的分布式计算平台?Parallel python提供了简易的方式来实现此目的. Parallel Python(http://www.parallelpython.com/content/view/15/30/#QUICKCLUSTERS)是Python进行分布式计算的开源模块,能够将计算压力分布到多核CPU或集群的多台计算机上,能够非常方便的在内网中搭建一个自组织的分布式计算平台. 在不同节点运行服务器程序,并自动发现运行服务器的节点,命令如下: node-1> ./ppser

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(三) 之 实现单聊,群聊,发送图片,文件。

上篇讲解了如何搭建聊天服务器,以及客户端js怎么和layui的语法配合.服务器已经连接上了,那么聊天还会远吗? 进入正题,正如上一篇提到的我们用 Client.Group(groupId)的方法向客户端推送消息.本篇就先不把业务搞复杂了,就默认现在两个用户都各自打开了对方的聊天窗口,那么聊天过程是这样的. 同理,B给A发消息也是这个流程,因为无论如何,A(ID)和B(ID)都会按照规则生成同一个组名.其中由于LayIM已经帮我们在客户端做好了发送消息并且将消息展示在面板上,所以我们要做的就是当接

宝塔面板+Fikker+BBR算法+CloudXNS---搭建一个简易的全球CDN缓存节点给网站加速

一.组件简介1)宝塔面板 宝塔面板是一款服务器管理软件,支持windows和linux系统,可以通过Web端轻松管理服务器,提升运维效率.例如:创建管理网站.FTP.数据库,拥有可视化文件管理器,可视化软件管理器,可视化CPU.内存.流量监控图表,计划任务等功能.我们在这里只用到它的LNMP/LAMP一键安装功能. linux(centos)版:yum install -y wget && wget -O install.sh http://download.bt.cn/install/i

使用genvent.socket实施群聊/单聊模式

使用genvent.socket实施群聊 from flask import Flask, request, render_template from gevent.pywsgi import WSGIServer from geventwebsocket.handler import WebSocketHandler from geventwebsocket.websocket import WebSocket import json user_dict = {} #设置一个公共变量 app