一前言

在家休息没事,敲敲代码,用NIO写个简易的仿真聊天室。下面直接讲聊天室设计和编码。对NIO不了解的朋友,推荐一个博客,里面写的很棒:

https://javadoop.com/     里面有NIO的部分

二设计

1.进入的时候,提示输入聊天昵称,重复的话,重新输入,成功后进到聊天室。

2.成功进到聊天室,广播通知,XXX进到了聊天室;离开聊天室,XXX离开了聊天室。

3.@XXX 给XXX发消息,只有双方可以看到。

4.服务端收到的内容会转发给其他客户端。

三代码

目前版本(设计工程3,4有待改进)

服务端:

 package com.lee.demo.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;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set; public class ChatServer { private Selector selector = null;
private Charset charset = Charset.forName("UTF-8");
public static final int PORT = 8765;
private static String USER_CONTENT_SPILIT = "#";
private static HashSet<String> users = new HashSet<String>(); public void init() throws IOException {
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(PORT));
// 将其注册到 Selector 中,监听 OP_ACCEPT 事件
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT); while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
Set<SelectionKey> readyKeys = selector.selectedKeys();
// 遍历
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
dealWithKey(server, key); }
} } private void dealWithKey(ServerSocketChannel server, SelectionKey key) throws IOException {
String content = null;
if (key.isAcceptable()) {
// 有已经接受的新的到服务端的连接
SocketChannel socketChannel = server.accept(); // 有新的连接并不代表这个通道就有数据,
// 这里将这个新的 SocketChannel 注册到 Selector,监听 OP_READ 事件,等待数据
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
//将此对应的channel设置为准备接受其他客户端请求
key.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("Server is listening from client " + socketChannel.getRemoteAddress());
socketChannel.write(charset.encode("Please input your name: ")); } else if (key.isReadable()) {
// 有数据可读
// 上面一个 if 分支中注册了监听 OP_READ 事件的 SocketChannel
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int num = socketChannel.read(readBuffer);
if (num > 0) {
content = new String(readBuffer.array()).trim();
// 处理进来的数据...
System.out.println("Server is listening from client " +
socketChannel.getRemoteAddress() +
" data received is: " +
content);
/* ByteBuffer buffer = ByteBuffer.wrap("返回给客户端的数据...".getBytes());
socketChannel.write(buffer);*/
//将此对应的channel设置为准备下一次接受数据
key.interestOps(SelectionKey.OP_READ); String[] arrayContent = content.split(USER_CONTENT_SPILIT);
//注册用户
if(arrayContent != null && arrayContent.length ==1) {
String name = arrayContent[0];
if(users.contains(name)) {
socketChannel.write(charset.encode("system message: user exist, please change a name")); } else {
users.add(name);
int number = OnlineNum(selector);
String message = "welcome " + name + " to chat room! Online numbers:" + number;
broadCast(selector, null, message);
}
}
//注册完了,发送消息
else if(arrayContent != null && arrayContent.length >1){
String name = arrayContent[0];
String message = content.substring(name.length() + USER_CONTENT_SPILIT.length());
message = name + " say " + message;
if(users.contains(name)) {
//不回发给发送此内容的客户端
broadCast(selector, socketChannel, message);
}
}
} else if (num == -1) {
// -1 代表连接已经关闭
socketChannel.close();
}
}
} private void broadCast(Selector selector, SocketChannel except, String content) throws IOException {
//广播数据到所有的SocketChannel中
for(SelectionKey key : selector.keys())
{
Channel targetchannel = key.channel();
//如果except不为空,不回发给发送此内容的客户端
if(targetchannel instanceof SocketChannel && targetchannel!=except)
{
SocketChannel dest = (SocketChannel)targetchannel;
dest.write(charset.encode(content));
}
}
} public static int OnlineNum(Selector selector) {
int res = 0;
for(SelectionKey key : selector.keys())
{
Channel targetchannel = key.channel();
if(targetchannel instanceof SocketChannel)
res++;
}
return res;
} public static void main(String[] args) throws IOException {
new ChatServer().init();
} }

客户端:

 package com.lee.demo.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.Iterator;
import java.util.Scanner;
import java.util.Set; public abstract class ChatClient { private Selector selector = null;
public static final int port = 8765;
private Charset charset = Charset.forName("UTF-8");
private SocketChannel sc = null;
private String name = "";
private static String USER_EXIST = "system message: user exist, please change a name";
private static String USER_CONTENT_SPILIT = "#"; public void init() throws IOException
{
selector = Selector.open();
//连接远程主机的IP和端口
sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", port));
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
//开辟一个新线程来读取服务器端的数据
new Thread(new ClientThread()).start();
//在主线程中 从键盘读取数据输入到服务器端
Scanner scan = new Scanner(System.in);
try {
while (scan.hasNextLine()) {
String line = scan.nextLine();
if ("".equals(line))
continue; // 不允许发空消息
if ("".equals(name)) {
name = line;
line = name + USER_CONTENT_SPILIT;
} else {
line = name + USER_CONTENT_SPILIT + line;
}
sc.write(charset.encode(line));// sc既能写也能读,这边是写
}
} finally {
scan.close();
} }
private class ClientThread implements Runnable
{
public void run()
{
try
{
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
//可以通过这个方法,知道可用通道的集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey sk = (SelectionKey) keyIterator.next();
keyIterator.remove();
dealWithSelectionKey(sk);
}
}
}
catch (IOException io)
{}
} private void dealWithSelectionKey(SelectionKey sk) throws IOException {
if(sk.isReadable())
{
//使用 NIO 读取 Channel中的数据,这个和全局变量sc是一样的,因为只注册了一个SocketChannel
//sc既能写也能读,这边是读
SocketChannel sc = (SocketChannel)sk.channel(); ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
while(sc.read(buff) > 0)
{
buff.flip();
content += charset.decode(buff);
}
//若系统发送通知名字已经存在,则需要换个昵称
if(USER_EXIST.equals(content)) {
name = "";
}
System.out.println(content);
sk.interestOps(SelectionKey.OP_READ);
}
}
} }

自己生成一个类,将Client这个抽象类继承,执行就可以了。观看结果的时候,会有个小地方需要注意,就是Eclipse中,console的结果,需要开启多个控制台,要不会发生混乱。

方法:

第一步:

第二步:

左侧红框,pin console ,作用是锁定console,固定显示选择的线程的输出;
 右侧红框,作用是线程选择显示哪个线程的输出。

NIO 多人聊天室的更多相关文章

  1. Apache MiNa 实现多人聊天室

    Apache MiNa 实现多人聊天室 开发环境: System:Windows JavaSDK:1.6 IDE:eclipse.MyEclipse 6.6 开发依赖库: Jdk1.4+.mina-c ...

  2. 与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室

    原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  3. 与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室

    原文:与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  4. Spring整合DWR comet 实现无刷新 多人聊天室

    用dwr的comet(推)来实现简单的无刷新多人聊天室,comet是长连接的一种.通常我们要实现无刷新,一般会使用到Ajax.Ajax 应用程序可以使用两种基本的方法解决这一问题:一种方法是浏览器每隔 ...

  5. 多人聊天室(Java)

    第1部分 TCP和UDP TCP:是一种可靠地传输协议,是把消息按一个个小包传递并确认消息接收成功和正确才发送下一个包,速度相对于UDP慢,但是信息准确安全:常用于一般不要求速度和需要准确发送消息的场 ...

  6. 使用Go语言+Protobuf协议完成一个多人聊天室

    软件环境:Goland Github地址 一.目的 之前用纯逻辑垒完了一个可登入登出的在线多人聊天室(代码仓库地址),这次学习了Protobuf协议,于是想试着更新下聊天室的版本. 主要目的是为了掌握 ...

  7. 基于tcp和多线程的多人聊天室-C语言

    之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...

  8. java socket之多人聊天室Demo

    一.功能介绍 该功能实现了一个类似QQ的最简单多人聊天室,如下图所示. 二.目录结构 三.服务端 1)SocketServer类,该类是服务端的主类,主要负责创建聊天窗口,创建监听客户端的线程: pa ...

  9. Asp.net MVC + Signalr 实现多人聊天室

    Asp.net SignalR 简介: 首先简单介绍一下Signalr ,我也是刚接触,觉得挺好玩的,然后写了一个多人聊天室. Asp.net SignalR 是为Asp.net 开发人员提供的一个库 ...

随机推荐

  1. 安装Adobe Acrobat XI Pro

    从网上下载Adobe Acrobat XI Pro这款软件,下载后将其解压到我们的电脑上,然后找到setup.exe双击安装它,安装时选择“使用试用版本或订阅” 2 选择“自定义”   自定义安装组件 ...

  2. JavaScript -基础- 函数与对象(二)String

    一.判断数据类型typeof与判断对象类型instanceof 1.typeof typeof只能判断基础数据类型,无法判断引用数据类型 <script> var s="hell ...

  3. vue组件的使用和事件传递

    子组件与父组件的事件传递具体实现如下: 子组件: <template> <section class="xftz-data-list"> <div c ...

  4. ios 设置本地化显示的app名称

    内容的本地化这里不做介绍! 名称的本地化: 1.新建一个 Strings File文件,命名为InfoPlist,注意这里一定要命名为InfoPlist! 2.设置本地化信息:选择需要的语言! 3.填 ...

  5. Огонек--灯光--IPA--俄语

    苏联老歌总让人沉浸其中.

  6. 开发框架DevExtreme发布v18.2.4|附下载

    DevExtreme Complete Subscription是性能最优的 HTML5,CSS 和 JavaScript 移动.Web开发框架,可以直接在Visual Studio集成开发环境,构建 ...

  7. spring 配置 applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...

  8. maven 打包前 Junit 测试

    1. 在需要打包前测试的项目中添加依赖 <dependency> <groupId>junit</groupId> <artifactId>junit& ...

  9. 20165326 java第七周学习笔记

    第七周学习笔记 MySQL(数据管理系统)学习 知识点总结: 不能通过关闭MySQL数据库服务器所占用的命令行窗口来关闭MySQL数据库. 如果MySQL服务器和MySQL管理工具驻留在同一台计算机上 ...

  10. DM浅尝辄止

    都是大佬的笔记啊啊啊啊 dialog management 对话状态维护(dialog state tracking, DST) 生成系统决策(dialog policy) 系统行为(dialog a ...