说明:
发现写博客要好多精力,为了多留点时间晋升自己就直接点贴代码了,学习地址: https://netty.io/ 和netty官网例子https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example,想学的同学多跑跑官网.
我的想法
以下实现了,某个人针对某个人的私信聊天,点击发送只能是你指定的人收到消息,群发功能是所有人都能接收到消息,具体我先截下图(三个对象测试,请复制粘贴,直接硬怼)
演示
点击开启聊天后聊天
演示地址:
不见得哪天失效了…有哪位大哥服务器可用…
http://112.35.164.210:18089/
聊天服务端
直接注册成springboot的bean对象了,打包直接运行
public class JiayangApplication {
public static void main(String[] args) {
SpringApplication.run(JiayangApplication.class, args);
}
@Bean
public void chat() throws Exception{
EventLoopGroup mainGroup = new NioEventLoopGroup();
EventLoopGroup subGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(mainGroup,subGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WSServerInitialzer());//初始化器
ChannelFuture future = serverBootstrap.bind(8080).sync();
future.channel().closeFuture().sync();
}finally {
mainGroup.shutdownGracefully();
subGroup.shutdownGracefully();
}
}
}
聊天初始化配置
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// pipeline.addLast(new HttpServerCodec());
// pipeline.addLast(new StringDecoder());
// pipeline.addLast(new StringEncoder());
// //webcocket基于http协议,所以要有http编解码器
// pipeline.addLast(new HttpServerCodec());
// //对写大数据流的支持
// pipeline.addLast(new ChunkedWriteHandler());//支持大数据流
// //对httpmessage进行聚合,聚合成fullhttprequest或fullhttpresponse
// pipeline.addLast(new HttpObjectAggregator(1024*64));
// //==========================以上是用于支持http协议支持
// //websocket 服务器处理的协议,用于指定给客户端连接访问的路由:/ws
// //本handler会帮你处理一些繁重的复杂的事
// //会帮你处理握手动作 :handshaking(close,ping,pong)ping +pong = 心跳
// //对于websocket来讲,都是frames进行传输的,不同的数据类型对应的frames是不同的
// pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// //自定义的handler
// pipeline.addLast(new ChatHandler());
pipeline.addLast("http-decoder", new HttpServerCodec());
// 加入ObjectAggregator解码器,作用是他会把多个消息转换为单一的FullHttpRequest或者FullHttpResponse
pipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));
// 加入chunked 主要作用是支持异步发送的码流(大文件传输),但不专用过多的内存,防止java内存溢出
pipeline.addLast(new ChunkedWriteHandler());
// 加入自定义handler
pipeline.addLast( new ChatHandler());
// 加入webSocket的hanlder
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
}
}
客户端处理消息
import com.alibaba.fastjson.JSON;
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.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.net.URLDecoder;
import java.util.concurrent.ConcurrentHashMap;
/**
* 处理消息的handler
* TextWebSocketFrame:在netty中,适用于为websocket专门处理文本的对象,frame是消息的载体
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//用于记录和管理所有客户端
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
// concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象。(以后用数据库代替)
private static ConcurrentHashMap<String, Channel> webSocketMap = new ConcurrentHashMap<>();
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// //获取客户端消息
String content = msg.text();
Msgs msgs = JSON.parseObject(content, Msgs.class);
if(msgs.getType()==2){
clients.writeAndFlush(new TextWebSocketFrame(msgs.getFromUserId()+"(群发):"+ msgs.getMsg()));
}else{
String toUserId = msgs.getToUserId();
Channel channel = webSocketMap.get(toUserId);
if (channel == null || !channel.isActive()) {
// ctx.writeAndFlush(new TextWebSocketFrame("你说: "+msgs.getMsg()));
ctx.writeAndFlush(new TextWebSocketFrame(msgs.getToUserId() + " 不在线 "+ "\n"));
} else {
// ctx.writeAndFlush(new TextWebSocketFrame("你说: "+msgs.getMsg()));
channel.writeAndFlush(new TextWebSocketFrame(msgs.getFromUserId() + " : " + msgs.getMsg()));
}
}
// System.out.println("接收到的数据: " + content);
// //下面方法和上面一致
// clients.writeAndFlush(new TextWebSocketFrame("[服务器在: ]"+
// LocalDateTime.now()+"接收到消息, 消息为: "+content));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (null != msg && msg instanceof FullHttpRequest) {
//转化为http请求
FullHttpRequest request = (FullHttpRequest) msg;
//拿到请求地址
String uri = request.uri();
System.out.println(uri);
//判断是不是websocket请求,如果是拿出我们传递的参数(我的是token)
String origin = request.headers().get("Origin");
if (null == origin) {
ctx.close();
} else {
String userId = StringUtils.substringAfter(uri, "/ws/");
userId = URLDecoder.decode(userId, "UTF-8");
webSocketMap.put(userId, ctx.channel());
//重新设置请求地址
request.setUri("/ws");
clients.writeAndFlush(new TextWebSocketFrame(userId+" 上线了----------"));
clients.add(ctx.channel());
}
}
//接着建立请求
super.channelRead(ctx, msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel...活跃");
super.channelActive(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel...移除");
super.channelUnregistered(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel...不活跃");
super.channelInactive(ctx);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel...读取完毕");
super.channelReadComplete(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println("channel...用户事件触发");
super.userEventTriggered(ctx, evt);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel...可更改");
super.channelWritabilityChanged(ctx);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("助手类添加");
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("助手类移除");
super.handlerRemoved(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("捕获到异常");
super.exceptionCaught(ctx, cause);
}
}
@Data
class Msgs {
private String fromUserId;
private String toUserId;
private String msg;
private int type;
}
前端代码
渣渣的前端代码,手机看的效果还行
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>我的小黑屋</title>
<script src="js/jquery-2.1.1.min.js"></script>
</head>
<body>
<div style="background-color: #C9394A;text-align: center;">
<div id="tt" style="background-color: #C9394A;text-align: center;font-weight:800;font-size:3.75em;padding-top: 25px;align-items: center;
vertical-align: middle;
font-family: Georgia;">小黑屋</div>
<div style="background-color: #C9394A;text-align: center;">
<label>我的昵称:</label>
<input id="nm" placeholder="请输入你的名字" onkeyup="event.keyCode==13?sure():''"/>
<input type="button" value="确定" onclick="sure()" id="an"/>
</div>
<div style="background-color: #C9394A;text-align: center;">
<label>聊天对象:</label>
<input id="nm1" placeholder="请输入聊天对象" onkeyup="event.keyCode==13?sure1():''"/>
<input type="button" value="确定" onclick="sure1()" id="an1"/>
</div>
<div style="background-color: #C9394A;text-align: center;">
<input id="bu" type="button" value="开启聊天" style="width: 19.01rem;" onclick="click1()"/>
</div>
<div style="background-color: #C9394A;text-align: center;">
<label >发送消息:</label>
<input type="text" id="msgContent" placeholder="请输入你想发送的内容" onkeyup="event.keyCode==13?CHAT.chat():''"/>
<input id="in" type="button" value="发送" onclick="CHAT.chat()" />
<input type="button" value="群发" onclick="CHAT.chat1()" />
</div>
<div style="background-color: #C9394A;">
<label>接收消息:</label>
<div id="reveiveMsg"></div>
</div>
</div>
<script type="application/javascript">
function keyupt(e){
console.log(111)
var evt = window.event || e;
if (evt.keyCode == 13){
alert('11')
i
//回车事件
}
}
var msg = {
fromUserId :"",
toUserId : "",
msg : "",
type : 1
};
function click1(){
if(this.msg.fromUserId==""){
alert("昵称不能为空")
return
}
if(this.msg.toUserId == ""){
alert("聊天对象不能为空")
return
}
$("#bu").hide();
var that = this
window.CHAT={
socket: null,
init: function(){
if(window.WebSocket){
var m = document.getElementById("nm");
CHAT.socket = new WebSocket("ws://47.107.235.154:8080/ws/"+m.value);
CHAT.socket.onopen = function(){
console.log("连接建立成功...")
},
CHAT.socket.onclose = function(){
CHAT.socket.close()
console.log("连接关闭...")
},
CHAT.socket.onerror = function(){
console.log("连接发生错误...")
},
CHAT.socket.onmessage = function(e){
console.log("接收到消息..."+e.data);
var reveiveMsg = document.getElementById("reveiveMsg");
var html = reveiveMsg.innerHTML;
reveiveMsg.innerHTML = "<span style='background-color: green;display:block;text-align:block;margin-right:310px;margin-left:310px;float:left;'>"+e.data+"</span>"+"<br/>"+html
}
}else{
alert("浏览器不支持websocket协议...")
}
},
chat: function(){
var msg = document.getElementById("msgContent");
that.msg.msg = msg.value
if(msg.value==""){
alert('请输入内容')
return
}
reveiveMsg.innerHTML = "<span style='background-color: skyblue;display:block;text-align:block;margin-right:310px;margin-left:310px;float:right;'>你说: "+msg.value+"</span>"+ "<br/>"+reveiveMsg.innerHTML
that.msg.type = 1
console.log(JSON.stringify(that.msg))
CHAT.socket.send(JSON.stringify(that.msg));
document.getElementById("msgContent").value = "";
},
chat1: function(){
var msg = document.getElementById("msgContent");
that.msg.msg = msg.value
if(msg.value==""){
alert('请输入内容')
return
}
that.msg.type = 2
console.log(JSON.stringify(that.msg))
CHAT.socket.send(JSON.stringify(that.msg));
document.getElementById("msgContent").value = "";
},
}
CHAT.init();
}
function sure(){
var msg = document.getElementById("nm");
$('#nm').prop("disabled", msg.disabled?false:true)
$('#an').prop("value",msg.disabled?"修改":"确定")
if(msg.disabled){
this.msg.fromUserId = msg.value
}
}
function sure1(){
var msg = document.getElementById("nm1");
$('#nm1').prop("disabled", msg.disabled?false:true)
$('#an1').prop("value",msg.disabled?"修改":"确定")
if(msg.disabled){
this.msg.toUserId = msg.value
}else{
CHAT.socket.close()
$("#bu").show();
this.msg.toUserId = ""
document.getElementById("nm1").value = ""
}
}
</script>
<style>
div{
border:solid 2px #C9394A;
}
</style>
</body>
</html>
结语
用心写博客要好多时间,时间不太多,更新东西有点慢,有时间写博客的人,年长的要么是大佬,要么就是大神,年轻的天天发博客的人,要么是时间多到没地方用,要么就是有奉献精神,牺牲自己时间的人…