首先用h5画一个简单的聊天页面,分别录入发送人的id,接受者的id,和需要发的消息,以及消息展示框
然后分别用小明,小红和小刚登录
现在小明给小红发消息,小红收的到,但小刚收不到实现点对点聊天
<div id="content" class="row-center">
<div id="chat-box" class="row-center">
div>
<div id="input-box">
class="chat-input" id="chat-input" placeholder="message">
id="myid" placeholder="myid">
id="friendid" placeholder="friendid">
div>
div>
<script type="text/javascript">
var ipaddress="127.0.0.1";
//新建socket对象
window.socket = new WebSocket("ws://"+ipaddress+":9999/ws");
//监听netty服务器消息并打印到页面上
socket.onmessage = function(event) {
var datas=event.data.split(",");
console.log("服务器消息===="+datas);
$("#chat-box").text(datas);
}
//将发送人接收人的id和要发生的消息发送出去
function send(){
console.log($("#chat-input").val())
var data=$("#myid").val()+","+$("#friendid").val()+","+$("#chat-input").val()
socket.send(data)
}
//登录事件
function login(){
var data=$("#myid").val();socket.send(data);
}
script>
public class imServer {
private io.netty.channel.Channel channel;
private EventLoopGroup bossGroup = new NioEventLoopGroup();
private EventLoopGroup workerGroup = new NioEventLoopGroup();
private static int port=9999;
//线程池设计的定时任务类
public imServer(int port) {
}
public void run() throws Exception {
try {
//创建ServerBootstrap实例
ServerBootstrap b = new ServerBootstrap();
//设置并绑定Reactor线程池
b.group(bossGroup, workerGroup)
//设置并绑定服务端Channel
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer(){
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("http-codec", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); // Http消息组装
pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // WebSocket通信支持
pipeline.addLast(new SocketHandler());//自定义处理类
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// System.out.println("WebsocketChatServer Start:" + port);
try {
ChannelFuture f = b.bind(port).sync();//// 服务器异步创建绑定
channel = f.channel();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
}
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
channel.closeFuture().syncUninterruptibly();
// System.out.println("WebsocketChatServer Stop:" + port);
}
}
public static void main(String[] args) throws Exception {
new imServer(port).run();
}
}
这些看不懂的自行百度
/**
* @author oj
* 消息处理类
*/
public class SocketHandler extends ChannelInboundHandlerAdapter {
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private WebSocketServerHandshaker handshaker;
private final String wsUri = "/ws";
//websocket握手升级绑定页面
String wsFactoryUri = "";
/*
* 握手建立
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.add(incoming);
}
/*
* 握手取消
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.remove(incoming);
}
/*
* channelAction
*
* channel 通道 action 活跃的
*
* 当客户端主动链接服务端的链接后,这个通道就是活跃的了。
*
*/
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// System.out.println(ctx.channel().localAddress().toString() + " 通道已激活!");
}
/*
* channelInactive
*
* channel 通道 Inactive 不活跃的
*
* 当客户端主动断开服务端的链接后,这个通道就是不活跃的。
*
*/
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// System.out.println(ctx.channel().localAddress().toString() + " 通道不活跃!");
}
/*
* 功能:读取 h5页面发送过来的信息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {// 如果是HTTP请求,进行HTTP操作
handleHttpRequest(ctx, (FullHttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {// 如果是Websocket请求,则进行websocket操作
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
/*
* 功能:读空闲时移除Channel
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent evnet = (IdleStateEvent) evt;
// 判断Channel是否读空闲, 读空闲时移除Channel
if (evnet.state().equals(IdleState.READER_IDLE)) {
UserInfoManager.removeChannel(ctx.channel());
}
}
ctx.fireUserEventTriggered(evt);
}
/*
* 功能:处理HTTP的代码
*/
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws UnsupportedEncodingException {
// 如果HTTP解码失败,返回HHTP异常
if (req instanceof HttpRequest) {
HttpMethod method = req.getMethod();
// 如果是websocket请求就握手升级
if (wsUri.equalsIgnoreCase(req.getUri())) {
System.out.println(" req instanceof HttpRequest");
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
wsFactoryUri, null, false);
handshaker = wsFactory.newHandshaker(req);
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), req);
}
}
}
}
/*
* 处理Websocket的代码
*/
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// 判断是否是关闭链路的指令
//System.out.println("websocket get");
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
// 判断是否是Ping消息
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
// 文本消息,不支持二进制消息
if (frame instanceof TextWebSocketFrame) {
// 返回应答消息
String requestmsg = ((TextWebSocketFrame) frame).text();
System.out.println("websocket消息======"+requestmsg);
String[] array= requestmsg.split(",");
// 将通道加入通道管理器
UserInfoManager.addChannel(ctx.channel(),array[0]);
UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel());
if (array.length== 3) {
// 将信息返回给h5
String sendid=array[0];String friendid=array[1];String messageid=array[2];
UserInfoManager.broadcastMess(friendid,messageid,sendid);
}
}
}
/**
* 功能:服务端发生异常的操作
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
private static ConcurrentMap userInfos = new ConcurrentHashMap<>();
/**
* 登录注册 channel
*
*
*/
public static void addChannel(Channel channel,String uid) {
String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);
UserInfo userInfo = new UserInfo();
userInfo.setUserId(uid);
userInfo.setAddr(remoteAddr);
userInfo.setChannel(channel);
userInfos.put(channel, userInfo);
}
/**
* 普通消息
*
* @param message
*/
public static void broadcastMess(String uid,String message,String sender) {
if (!BlankUtil.isBlank(message)) {
try {
rwLock.readLock().lock();
Set keySet = userInfos.keySet();
for (Channel ch : keySet) {
UserInfo userInfo = userInfos.get(ch);
if (!userInfo.getUserId().equals(uid) ) continue;
String backmessage=sender+","+message;
ch.writeAndFlush(new TextWebSocketFrame(backmessage));
/* responseToClient(ch,message);*/
}
} finally {
rwLock.readLock().unlock();
}
}
}
public class UserInfo {
private String userId; // UID
private String addr; // 地址
private Channel channel;// 通道
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
每个客户端通过websocket连接到后台服务器都是一条通道,只要每个人都登录的时候就将channel信息注册保存,当需要向具体某个人发消息的时候,只需要遍历出收件人的channel再发送信息即可
public static void addChannel(Channel channel,String uid) {
String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);
UserInfo userInfo = new UserInfo();
userInfo.setUserId(uid);
userInfo.setAddr(remoteAddr);
userInfo.setChannel(channel);
userInfos.put(channel, userInfo);
}
public static void broadcastMess(String uid,String message,String sender) {
if (!BlankUtil.isBlank(message)) {
try {
Set keySet = userInfos.keySet();
for (Channel ch : keySet) {
UserInfo userInfo = userInfos.get(ch);
if (!userInfo.getUserId().equals(uid) ) continue;
String backmessage=sender+","+message;
ch.writeAndFlush(new TextWebSocketFrame(backmessage));
/* responseToClient(ch,message);*/
}
} finally {
}
}
}