前面三个章节,我们使用了Netty实现了DISCARD丢弃服务和回复以及自定义编码解码,这篇博客,我们要用Netty实现简单的聊天室功能。

Ps: 突然想起来大学里面有个课程实训,给予UDP还是TCP实现的聊天程序,简单的分析一下,那个实现和基于Netty的实现是不一样的,基于UDP或者TCP做的聊天室中只能是客户端向服务发送消息(当然基于UDP的也可以建立两个Channel来实现服务器和客户端的双向通道),然后客户端接收到消息,这里的服务器仅仅作为一个接收消息处理之的作用,并不能主动向客户端推送消息。

基于前面几个章节的知识,这里我们做一个简单的聊天室功能,我们简单的说一下需求:

  • 进入聊天室,服务器发送欢迎信息到该用户的客户端
  • 有新人进入或者退出聊天室,那么聊天室的其他用户都能接收到通知信息
  • 某位用户发送消息,其他用户的客户端显示格式为 [时间][发送用户的名称/地址]消息内容,自己的客户端显示[时间][You]消息内容

这篇博文仅仅实现服务端的代码,使用telent测试,客户端的作为下一篇阐述。

工具类代码

工具类就一个时间格式化的工具,如下:

package com.zhoutao123.simpleChat.utils;

import java.text.SimpleDateFormat;
import java.util.Date; public class DatetimeUtils { private static final SimpleDateFormat smf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String getNowDatetime(){
return smf.format(new Date());
}
}

服务端代码

服务处理适配器

和之前的代码一样,这里我们继承SimpleChannelInboundHandler,具体的解释在注释里面。

package com.zhoutao123.simpleChat.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor; import static com.zhoutao123.simpleChat.utils.DatetimeUtils.getNowDatetime; public class ServerHandle extends SimpleChannelInboundHandler<String> { // 创建ChannelGroup 用于保存连接的Channel
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); //当有新的Channel增加的时候
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 获取当前的Channel
Channel channel = ctx.channel();
//向其他Channel发送上线消息
channelGroup.writeAndFlush(String.format("[%s][服务器]\t用户:%s 加入聊天室!\n", getNowDatetime(), channel.remoteAddress()));
// 添加Channel到Group里面
channelGroup.add(channel);
// 向新用户发送欢迎信息
channel.writeAndFlush(String.format("你好,%s欢迎来到Netty聊天室\n", channel.remoteAddress()));
} @Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 用户退出后向全部Channel发送下线消息
channelGroup.writeAndFlush(String.format("[%s][服务器]\t用户:%s 离开聊天室!\n", getNowDatetime(), ctx.channel().remoteAddress()));
// 移除
channelGroup.remove(ctx.channel());
} @Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
// 服务器接收到新的消息后之后执行
// 获取当前的Channel
Channel currentChannel = ctx.channel();
// 遍历
for (Channel channel : channelGroup) {
String sendMessage = "";
// 如果是当前的用户发送You的信息,不是则发送带有发送人的信息
if (channel == currentChannel) {
sendMessage = String.format("[%s][You]\t%s\n", getNowDatetime(), msg);
} else {
sendMessage = String.format("[%s][%s]\t %s\n", getNowDatetime(), currentChannel.remoteAddress(), msg);
}
channel.writeAndFlush(sendMessage);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 发送异常的时候通知移除
Channel channel = ctx.channel();
channelGroup.writeAndFlush(String.format("[%s][服务器]\t 用户 %s 出现异常掉线!\n", getNowDatetime(), channel.remoteAddress()));
ctx.close();
}
}

处理器初始化

这里主要是配置一些编码器以及解码器以及我们自己定义的ServerHandleAdapter

package com.zhoutao123.simpleChat.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> { @Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new ServerHandle()); System.out.println("SimpleChatClient:"+ch.remoteAddress() +"连接上");
}
}

启动服务器

启动的代码和以前一致,没有打的改动.

package com.zhoutao123.simpleChat.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; public class Server { private final static int port = 8080; public static void main(String[] args) throws InterruptedException { NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup work = new NioEventLoopGroup(); try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, work)
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChatServerInitializer())
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("聊天服务已经启动.....");
ChannelFuture sync = serverBootstrap.bind(port).sync();
sync.channel().closeFuture().sync();
} finally {
work.shutdownGracefully();
boss.shutdownGracefully();
System.out.println("聊天服务已经被关闭");
}
}
}

TELNET测试

启动服务之后,我在Linux上使用Telnet命令来简单的测试了下,

这里创建了4个用户,发送了一些信息,可以观察一下:

Netty学习笔记(四) 简单的聊天室功能之服务端开发的更多相关文章

  1. Netty学习笔记(六) 简单的聊天室功能之WebSocket客户端开发实例

    在之前的Netty相关学习笔记中,学习了如何去实现聊天室的服务段,这里我们来实现聊天室的客户端,聊天室的客户端使用的是Html5和WebSocket实现,下面我们继续学习. 创建客户端 接着第五个笔记 ...

  2. Netty学习笔记(四)——实现dubbo的rpc

    1.rpc基本介绍 RPC ( Remote Procedure Call) -远程过程调用,是一个计算机通信协议.该协议允许运行于一台计算机的程序调用另一台计算机的子程序,两个或多个应用程序分布不同 ...

  3. 通过WebSocket实现一个简单的聊天室功能

    WebSocket WebSocket是一个协议,它是是基于TCP的一种新的网络协议,TCP协议是一种持续性的协议,和HTTP不同的是,它可以在服务器端主动向客户端推送消息.通过这个协议,可以在建立一 ...

  4. 玩转Node.js(四)-搭建简单的聊天室

    玩转Node.js(四)-搭建简单的聊天室 Nodejs好久没有跟进了,最近想用它搞一个聊天室,然后便偶遇了socket.io这个东东,说是可以用它来简单的实现实时双向的基于事件的通讯机制.我便看了一 ...

  5. ensorflow学习笔记四:mnist实例--用简单的神经网络来训练和测试

    http://www.cnblogs.com/denny402/p/5852983.html ensorflow学习笔记四:mnist实例--用简单的神经网络来训练和测试   刚开始学习tf时,我们从 ...

  6. ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁

    作者:Grey 原文地址: ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁 前置知识 完成ZooKeeper集群搭建以及熟悉ZooKeeperAPI基本使用 需求 当多个进 ...

  7. Netty 学习笔记(1)通信原理

    前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始.   Netty 的通信原理 Netty 底层 ...

  8. Netty学习笔记-入门版

    目录 Netty学习笔记 前言 什么是Netty IO基础 概念说明 IO简单介绍 用户空间与内核空间 进程(Process) 线程(thread) 程序和进程 进程切换 进程阻塞 文件描述符 文件句 ...

  9. Netty 学习(四):ChannelHandler 的事件传播和生命周期

    Netty 学习(四):ChannelHandler 的事件传播和生命周期 作者: Grey 原文地址: 博客园:Netty 学习(四):ChannelHandler 的事件传播和生命周期 CSDN: ...

随机推荐

  1. [Swift]LeetCode338. 比特位计数 | Counting Bits

    Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the ...

  2. [Swift]LeetCode424. 替换后的最长重复字符 | Longest Repeating Character Replacement

    Given a string that consists of only uppercase English letters, you can replace any letter in the st ...

  3. bilibili源码泄漏后,程序员们从代码里扒出来的彩蛋

    昨天bilibili又上热搜了,被某人在github上开了个账号,传了份整个后端代码到github,这是被人扒光了衣服看个精光啊. 这件事情,作为程序员的我们除了调侃和fork的同时,想一想,造成这个 ...

  4. 5.Git基础-撤销操作、标签的使用、Git别名

    1.撤销操作 1.1 修改上一次的提交(commit)--  git commit --amend 有时候我们在提交完成之后才发现有几个文件没有提交,或者发现提交信息填写错了,这时候可以使用 git ...

  5. C# listview展示表格格式

    有时候我们需要展示表格格式的数据,首先想到的是用datagridview控件,比如更改datagridview某一行的数据,这样操作起来就比较麻烦,而listview属于轻量级,刷新和更改相对来说效率 ...

  6. .NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着 ...

  7. Spring系列之手写一个SpringMVC

    目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 Spring系列之AOP的原理及手动实现 Spring系列之手写注解与配置文件的解析 引言 在前面的几个章节中我 ...

  8. Numpy 基础学习

    numpy.array() 功能:创建一个数据 vector = numpy.array([1,2,3,4]) matrix = numpy.array([1,2,3,4],[11,12,13,14] ...

  9. Spring Cloud-微服务架构集大成者

    本文不是讲解如何使用Spring Cloud的教程,而是探讨Spring Cloud是什么,以及它诞生的背景和意义. 1 背景 2008年以后,国内互联网行业飞速发展,我们对软件系统的需求已经不再是过 ...

  10. ELK-Elasticsearch安装

    1.安装JDK必须安装JDK并且要求版本至少是1.8以上包含1.8的版本. 2.创建 es 用户这里需要注意的是,es 规定 root 用户不能启动 es,所以需要创建一个用户来启动 es # 创建用 ...