@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>netty-project</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<name>Netty聊天室网页版</name>
|
||||
|
||||
<groupId>com.xjs</groupId>
|
||||
<artifactId>netty-springboot</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--整合web模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--整合模板引擎 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,26 @@
|
||||
package com.lagou;
|
||||
|
||||
import com.lagou.netty.NettyWebSocketServer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class NettySpringbootApplication implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private NettyWebSocketServer webSocketServer;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(NettySpringbootApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
//启动Netty服务端
|
||||
new Thread(webSocketServer).start();
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.lagou.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "netty")
|
||||
@Data
|
||||
public class NettyConfig {
|
||||
|
||||
//netty监听的端口
|
||||
private int port ;
|
||||
|
||||
//websocket访问路径
|
||||
private String path;
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.lagou.controller;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
public class ChatController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String chat() {
|
||||
return "chat";
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.lagou.netty;
|
||||
|
||||
import com.lagou.config.NettyConfig;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
/**
|
||||
* Netty服务器
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
@Component
|
||||
public class NettyWebSocketServer implements Runnable {
|
||||
|
||||
@Autowired
|
||||
private NettyConfig nettyProperties;
|
||||
|
||||
@Autowired
|
||||
private WebSocketChannelInit webSocketChannelInit;
|
||||
|
||||
private EventLoopGroup boosGroup = new NioEventLoopGroup(1);
|
||||
|
||||
private EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
|
||||
|
||||
/**
|
||||
* 资源关闭---在容器销毁时关闭
|
||||
*/
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
boosGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
//1、创建服务端启动助手
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
//2、设置线程组
|
||||
bootstrap.group(boosGroup, workerGroup);
|
||||
|
||||
//3、设置参数
|
||||
bootstrap.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler(LogLevel.DEBUG))
|
||||
.childHandler(webSocketChannelInit);
|
||||
|
||||
//4、启动
|
||||
ChannelFuture future = bootstrap.bind(nettyProperties.getPort()).sync();
|
||||
|
||||
System.out.println("---Netty服务端启动成功---");
|
||||
|
||||
future.channel().closeFuture().sync();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
boosGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.lagou.netty;
|
||||
|
||||
import com.lagou.config.NettyConfig;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
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;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
@Component
|
||||
public class WebSocketChannelInit extends ChannelInitializer {
|
||||
|
||||
@Autowired
|
||||
private NettyConfig nettyConfig;
|
||||
|
||||
@Autowired
|
||||
private WebSocketHandler webSocketHandler;
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
//对http协议支持
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
|
||||
//对大数据流支持
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
|
||||
//post请求分三部分、request line / request header / message body
|
||||
pipeline.addLast(new HttpObjectAggregator(8000));
|
||||
|
||||
//将http协议升级为ws协议 websocket支持
|
||||
pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath()));
|
||||
|
||||
pipeline.addLast(webSocketHandler);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.lagou.netty;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义处理类
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
|
||||
|
||||
private static List<Channel> channelList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 读就绪事件
|
||||
* @param ctx
|
||||
* @param msg websocket数据是帧的形式处理
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
|
||||
|
||||
System.out.println("msg="+msg.text());
|
||||
|
||||
Channel channel = ctx.channel();
|
||||
|
||||
for (Channel channel1 : channelList) {
|
||||
//排除自身通道
|
||||
if (channel != channel1) {
|
||||
channel1.writeAndFlush(new TextWebSocketFrame(msg.text() ));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道未就绪 --channel下线
|
||||
* @param ctx
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
Channel channel = ctx.channel();
|
||||
|
||||
//当有客户端断开连接的时候,就移出对应的通道
|
||||
channelList.remove(channel);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道就绪事件
|
||||
* @param ctx
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
Channel channel = ctx.channel();
|
||||
|
||||
//当有新的客户端连接的时候,将通道放入集合
|
||||
channelList.add(channel);
|
||||
System.out.println("[Server]:"+channel.remoteAddress().toString().substring(1)+"在线");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理事件
|
||||
* @param ctx
|
||||
* @param cause
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
cause.printStackTrace();
|
||||
Channel channel = ctx.channel();
|
||||
System.out.println("[Server]:"+channel.remoteAddress().toString().substring(1)+"异常");
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
server:
|
||||
port: 8070
|
||||
|
||||
netty:
|
||||
port: 8071
|
||||
path: /chat
|
||||
|
||||
resources:
|
||||
static-locations:
|
||||
- classpath:/static/
|
||||
spring:
|
||||
thymeleaf:
|
||||
cache: false
|
||||
checktemplatelocation: true
|
||||
enabled: true
|
||||
encoding: UTF-8
|
||||
mode: HTML5
|
||||
prefix: classpath:/templates/
|
||||
suffix: .html
|
@ -0,0 +1,454 @@
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input {
|
||||
background: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
body,
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #323232;
|
||||
font: 14px/1.4em 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif;
|
||||
background: #f5f5f5 url('img/bg.jpg') no-repeat center;
|
||||
background-size: cover;
|
||||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#chat {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#chat .sidebar {
|
||||
float: left;
|
||||
color: #f4f4f4;
|
||||
background-color: #2e3238;
|
||||
}
|
||||
|
||||
.m-card {
|
||||
padding: 9pt;
|
||||
border-bottom: 1px solid #24272c;
|
||||
height: 65px;
|
||||
}
|
||||
.m-card header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.m-card .avatar {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.m-card .avatar,
|
||||
.m-card .name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.m-card .name {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 15px;
|
||||
font-size: 1pc;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.m-list {
|
||||
height: 535px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.m-list li {
|
||||
padding: 9pt 15px;
|
||||
border-bottom: 1px solid #292c33;
|
||||
cursor: pointer;
|
||||
-webkit-transition: background-color .1s;
|
||||
transition: background-color .1s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.m-list .avatar {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.m-list .avatar,
|
||||
.m-list .name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.m-list .name {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 15px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.m-list li.active {
|
||||
background-color: hsla(0, 0%, 100%, .1);
|
||||
}
|
||||
|
||||
#chat .main {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #eee;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#chat .main,
|
||||
#chat .sidebar {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#chat .m-message {
|
||||
height: calc(100% - 10pc);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.m-message {
|
||||
padding: 10px 15px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.m-message .pager_container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.m-message .mask {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.m-message .self {
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.m-message .self .avatar {
|
||||
float: right;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
|
||||
.m-message .self .text {
|
||||
background-color: rgb(155, 255, 101);
|
||||
}
|
||||
|
||||
.m-message li {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.m-message .time {
|
||||
margin: 7px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.m-message .time > span {
|
||||
display: inline-block;
|
||||
padding: 0 18px;
|
||||
font-size: 9pt;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
background-color: #dcdcdc;
|
||||
}
|
||||
|
||||
.m-message .avatar {
|
||||
float: left;
|
||||
margin: 10px 10px 0 0;
|
||||
border-radius: 3px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.m-message .text {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5px 10px;
|
||||
min-height: 30px;
|
||||
line-height: 2;
|
||||
font-size: 9pt;
|
||||
text-align: left;
|
||||
word-break: break-all;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
max-width: 500px
|
||||
}
|
||||
|
||||
.m-message .text:before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: 100%;
|
||||
border: 6px solid transparent;
|
||||
border-right-color: #fafafa;
|
||||
}
|
||||
|
||||
.m-message .user_name {
|
||||
height: 15px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#chat .m-text {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 160px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.m-message .self .text:before {
|
||||
right: inherit;
|
||||
left: 100%;
|
||||
border-right-color: transparent;
|
||||
border-left-color: rgb(155, 255, 101);
|
||||
}
|
||||
|
||||
.m-text .handler {
|
||||
padding-left: 20px;
|
||||
height: 30px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.m-text .handler img {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.m-text textarea {
|
||||
padding: 10px 10px 20px 10px;
|
||||
height: calc(100% - 70px);
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: 0;
|
||||
font-family: Micrsofot Yahei;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.m-text .pager_btn {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.m-text button {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.m-text .arrow_box {
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
padding: 0 5px;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
right: 15px;
|
||||
border: 1px solid #666;
|
||||
border-radius: 3px;
|
||||
box-sizing: content-box;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.m-text .arrow_box .arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid #666;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
|
||||
.receive_list {
|
||||
height: 180px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#pager_result > div {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#pager_result .top img {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#already_receive_top {
|
||||
border-bottom: 1px solid rgb(233, 230, 230);
|
||||
line-height: 40px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.receive_list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.receive_list li img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
vertical-align: center;
|
||||
margin-right: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.receive_list li div {
|
||||
flex: 1;
|
||||
border-bottom: 1px solid rgb(233, 230, 230);
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#receive_time {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.receive_list li div p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.text_right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.flex_between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 320px) and (max-width: 640px) {
|
||||
#chat {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 320px;
|
||||
}
|
||||
.m-card .name,.m-list .name {
|
||||
margin: 0 0 0 5px!important;
|
||||
|
||||
}
|
||||
#chat .main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#chat .sidebar {
|
||||
width: 100px;
|
||||
}
|
||||
.m-card {
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
}
|
||||
.m-card .avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
#user_list li .avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.pager_result #pager_result, .m-pager #pager {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -150px;
|
||||
margin-top: -250px;
|
||||
}
|
||||
.get_pager #get_pager {
|
||||
margin-left: -100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px)and (max-width: 992px) {
|
||||
#chat {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#chat .main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#chat .sidebar {
|
||||
width: 150px;
|
||||
}
|
||||
.pager_result #pager_result, .m-pager #pager {
|
||||
margin-top: -250px;
|
||||
}
|
||||
.get_pager #get_pager {
|
||||
margin-left: -50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
#chat {
|
||||
margin: 20px auto;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
#chat .main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#chat .sidebar {
|
||||
width: 200px;
|
||||
}
|
||||
.pager_result #pager_result, .m-pager #pager {
|
||||
margin-top: -300px;
|
||||
}
|
||||
.get_pager #get_pager {
|
||||
margin-left: -50px;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 688 B |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 955 B |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,328 @@
|
||||
/*
|
||||
jQuery tagEditor v1.0.3
|
||||
Copyright (c) 2014 Simon Steinberger / Pixabay
|
||||
GitHub: https://github.com/Pixabay/jQuery-tagEditor
|
||||
License: http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
(function($){
|
||||
// modified autoGrowInput - stackoverflow.com/questions/931207
|
||||
// lets input fields grow dynamically on change
|
||||
$.fn.autoGrowInput=function(o){o=$.extend({maxWidth:250,minWidth:20,comfortZone:0},o);this.filter('input:text').each(function(){var minWidth=o.minWidth||$(this).width(),val=' ',input=$(this),comfortZone=o.comfortZone?o.comfortZone:parseInt(parseInt($(this).css('fontSize'))*0.9),dummy=$('<dummy/>').css({position:'absolute',top:-9999,left:-9999,width:'auto',fontSize:input.css('fontSize'),fontFamily:input.css('fontFamily'),fontWeight:input.css('fontWeight'),letterSpacing:input.css('letterSpacing'),whiteSpace:'nowrap'}),check=function(){if(val===(val=input.val()))return;dummy.html(val.replace(/&/g,'&').replace(/\s/g,' ').replace(/</g,'<').replace(/>/g,'>'));var newWidth=dummy.width()+comfortZone;if(newWidth>o.maxWidth)newWidth=o.maxWidth;if(newWidth<o.minWidth)newWidth=o.minWidth;if(newWidth!=input.width())input.width(newWidth);};dummy.insertAfter(input);$(this).bind('keyup keydown blur focus autogrow',check);});return this;};
|
||||
|
||||
// code.accursoft.com/caret/overview
|
||||
// used to set cursor position in input fields
|
||||
!function(e){e.fn.caret=function(e){var t=this[0],n="true"===t.contentEditable;if(0==arguments.length){if(window.getSelection){if(n){t.focus();var o=window.getSelection().getRangeAt(0),r=o.cloneRange();return r.selectNodeContents(t),r.setEnd(o.endContainer,o.endOffset),r.toString().length}return t.selectionStart}if(document.selection){if(t.focus(),n){var o=document.selection.createRange(),r=document.body.createTextRange();return r.moveToElementText(t),r.setEndPoint("EndToEnd",o),r.text.length}var e=0,c=t.createTextRange(),r=document.selection.createRange().duplicate(),a=r.getBookmark();for(c.moveToBookmark(a);0!==c.moveStart("character",-1);)e++;return e}return 0}if(-1==e&&(e=this[n?"text":"val"]().length),window.getSelection)n?(t.focus(),window.getSelection().collapse(t.firstChild,e)):t.setSelectionRange(e,e);else if(document.body.createTextRange)if(n){var c=document.body.createTextRange();c.moveToElementText(t),c.moveStart("character",e),c.collapse(!0),c.select()}else{var c=t.createTextRange();c.move("character",e),c.select()}return n||t.focus(),e}}(jQuery);
|
||||
|
||||
// plugin with val as parameter for public methods
|
||||
$.fn.tagEditor = function(options, val, next){
|
||||
|
||||
// build options dictionary with default values
|
||||
var blur_result, o = $.extend({}, $.fn.tagEditor.defaults, options), selector = this;
|
||||
|
||||
// store regex and default delimiter in options for later use
|
||||
o.dregex = new RegExp('['+o.delimiter.replace('-', '\-')+']', 'g');
|
||||
|
||||
// public methods
|
||||
if (typeof options == 'string') {
|
||||
|
||||
// depending on selector, response may contain tag lists of multiple editor instances
|
||||
var response = [];
|
||||
selector.each(function(){
|
||||
// the editor is the next sibling to the hidden, original field
|
||||
var el = $(this), o = el.data('options'), ed = el.next('.tag-editor');
|
||||
if (options == 'getTags')
|
||||
response.push({field: el[0], editor: ed, tags: ed.data('tags')});
|
||||
else if (options == 'addTag') {
|
||||
// insert new tag
|
||||
$('<li><div class="tag-editor-spacer"> '+o.delimiter[0]+'</div><div class="tag-editor-tag"></div><div class="tag-editor-delete"><i></i></div></li>').appendTo(ed).find('.tag-editor-tag')
|
||||
.html('<input type="text" maxlength="'+o.maxLength+'">').addClass('active').find('input').val(val).blur();
|
||||
if (next != 'blur') ed.click();
|
||||
else $('.placeholder', ed).remove();
|
||||
} else if (options == 'removeTag') {
|
||||
// trigger delete on matching tag, then click editor to create a new tag
|
||||
$('.tag-editor-tag', ed).filter(function(){return $(this).html()==val;}).closest('li').find('.tag-editor-delete').click();
|
||||
if (next != 'blur') ed.click();
|
||||
} else if (options == 'destroy') {
|
||||
el.css('display', o.elDisplay).removeData('options').next('.tag-editor').remove();
|
||||
}
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// delete selected tags on backspace, delete, ctrl+x
|
||||
function delete_selected_tags(e){
|
||||
if (e.which == 8 || e.which == 46 || e.ctrlKey && e.which == 88) {
|
||||
var sel = getSelection(), el = $(sel.getRangeAt(0).commonAncestorContainer);
|
||||
if (el.hasClass('tag-editor')) {
|
||||
var tags = [], splits = sel.toString().split(el.prev().data('options').dregex);
|
||||
for (i=0; i<splits.length; i++){ var tag = $.trim(splits[i]); if (tag) tags.push(tag); }
|
||||
$('.tag-editor-tag', el).each(function(){
|
||||
if (~$.inArray($(this).html(), tags)) $(this).closest('li').find('.tag-editor-delete').click();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (window.getSelection) $(document).off('keydown.tag-editor').on('keydown.tag-editor', delete_selected_tags);
|
||||
|
||||
return selector.each(function(){
|
||||
var el = $(this), tag_list = [];
|
||||
|
||||
// create editor (ed) instance
|
||||
o.elDisplay = el.css('display'); // store for destroy method
|
||||
el.css('display', 'none');
|
||||
var ed = $('<ul '+(o.clickDelete ? 'oncontextmenu="return false;" ' : '')+'class="tag-editor"></ul>').insertAfter(el);
|
||||
el.data('options', o); // set data on hidden field
|
||||
|
||||
// add dummy item for min-height on empty editor
|
||||
ed.append('<li style="width:.1px"> </li>');
|
||||
|
||||
// markup for new tag
|
||||
var new_tag = '<li><div class="tag-editor-spacer"> '+o.delimiter[0]+'</div><div class="tag-editor-tag"></div><div class="tag-editor-delete"><i></i></div></li>';
|
||||
|
||||
// helper: update global data
|
||||
function set_placeholder(){
|
||||
if (o.placeholder && !tag_list.length && !$('.deleted, .placeholder, input', ed).length)
|
||||
ed.append('<li class="placeholder"><div>'+o.placeholder+'</div></li>');
|
||||
}
|
||||
|
||||
// helper: update global data
|
||||
function update_globals(init){
|
||||
var old_tags = tag_list.toString();
|
||||
tag_list = $('.tag-editor-tag:not(.deleted)', ed).map(function(i, e) {
|
||||
var val = $.trim($(this).hasClass('active') ? $(this).find('input').val() : $(e).text());
|
||||
if (val) return val;
|
||||
}).get();
|
||||
ed.data('tags', tag_list);
|
||||
el.val(tag_list.join(o.delimiter[0]));
|
||||
// change callback except for plugin init
|
||||
if (!init) if (old_tags != tag_list.toString()) o.onChange(el, ed, tag_list);
|
||||
set_placeholder();
|
||||
}
|
||||
|
||||
ed.click(function(e){
|
||||
// do not create tag when user selects tags by text selection
|
||||
if (window.getSelection && getSelection() != '') return;
|
||||
|
||||
blur_result = true
|
||||
$('input:focus', ed).blur();
|
||||
if (!blur_result) return false;
|
||||
blur_result = true
|
||||
|
||||
// always remove placeholder on click
|
||||
$('.placeholder', ed).remove();
|
||||
|
||||
// calculate tag closest to click position
|
||||
var d, dist = 99999, closest_tag, loc;
|
||||
$('.tag-editor-tag', ed).each(function(){
|
||||
var tag = $(this), to = tag.offset(), tag_x = to.left, tag_y = to.top;
|
||||
if (e.pageY >= tag_y && e.pageY <= tag_y+tag.height()) {
|
||||
if (e.pageX < tag_x) loc = 'before', d = tag_x - e.pageX;
|
||||
else loc = 'after', d = e.pageX - tag_x - tag.width();
|
||||
if (d < dist) dist = d, closest_tag = tag;
|
||||
}
|
||||
});
|
||||
if (loc == 'before')
|
||||
$(new_tag).insertBefore(closest_tag.closest('li')).find('.tag-editor-tag').click();
|
||||
else if (loc == 'after')
|
||||
$(new_tag).insertAfter(closest_tag.closest('li')).find('.tag-editor-tag').click();
|
||||
else // empty editor
|
||||
$(new_tag).appendTo(ed).find('.tag-editor-tag').click();
|
||||
return false;
|
||||
});
|
||||
|
||||
ed.on('click', '.tag-editor-delete', function(e){
|
||||
// delete icon is hidden when input is visible; place cursor near invisible delete icon on click
|
||||
if ($(this).prev().hasClass('active')) { $(this).closest('li').find('input').caret(-1); return false; }
|
||||
|
||||
var li = $(this).closest('li'), tag = li.find('.tag-editor-tag');
|
||||
if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false;
|
||||
tag.addClass('deleted').animate({width: 0}, 175, function(){ li.remove(); set_placeholder(); });
|
||||
update_globals();
|
||||
return false;
|
||||
});
|
||||
|
||||
// delete on right mouse click or ctrl+click
|
||||
if (o.clickDelete)
|
||||
ed.on('mousedown', '.tag-editor-tag', function(e){
|
||||
if (e.ctrlKey || e.which > 1) {
|
||||
var li = $(this).closest('li'), tag = li.find('.tag-editor-tag');
|
||||
if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false;
|
||||
tag.addClass('deleted').animate({width: 0}, 175, function(){ li.remove(); set_placeholder(); });
|
||||
update_globals();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ed.on('click', '.tag-editor-tag', function(e){
|
||||
// delete on right click or ctrl+click -> exit
|
||||
if (o.clickDelete && (e.ctrlKey || e.which > 1)) return false;
|
||||
|
||||
if (!$(this).hasClass('active')) {
|
||||
var tag = $(this).html();
|
||||
// guess cursor position in text input
|
||||
var left_percent = Math.abs(($(this).offset().left - e.pageX)/$(this).width()), caret_pos = parseInt(tag.length*left_percent),
|
||||
input = $(this).html('<input type="text" maxlength="'+o.maxLength+'" value="'+tag+'">').addClass('active').find('input');
|
||||
input.data('old_tag', tag).focus().autoGrowInput().trigger('autogrow').caret(caret_pos);
|
||||
if (o.autocomplete) {
|
||||
var aco = o.autocomplete;
|
||||
// extend user provided autocomplete select method
|
||||
var ac_select = 'select' in aco ? o.autocomplete.select : '';
|
||||
aco.select = function(){ if (ac_select) ac_select(); setTimeout(function(){ $('.active', ed).find('input').trigger('autogrow'); }, 20); };
|
||||
input.autocomplete(aco);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// helper: split into multiple tags, e.g. after paste
|
||||
function split_cleanup(input){
|
||||
var li = input.closest('li'), sub_tags = input.val().replace(/ +/, ' ').split(o.dregex), old_tag = input.data('old_tag');
|
||||
var old_tags = tag_list.slice(0); // copy tag_list
|
||||
for (i in sub_tags) {
|
||||
tag = $.trim(sub_tags[i]).slice(0, o.maxLength);
|
||||
if (tag) {
|
||||
if (o.forceLowercase) tag = tag.toLowerCase();
|
||||
o.beforeTagSave(el, ed, old_tags, old_tag, tag);
|
||||
// remove duplicates
|
||||
if (~$.inArray(tag, old_tags))
|
||||
$('.tag-editor-tag', ed).each(function(){ if ($(this).html() == tag) $(this).closest('li').remove(); });
|
||||
old_tags.push(tag);
|
||||
li.before('<li><div class="tag-editor-spacer"> '+o.delimiter[0]+'</div><div class="tag-editor-tag">'+tag+'</div><div class="tag-editor-delete"><i></i></div></li>');
|
||||
}
|
||||
}
|
||||
input.attr('maxlength', o.maxLength).removeData('old_tag').val('').focus();
|
||||
update_globals();
|
||||
}
|
||||
|
||||
ed.on('blur', 'input', function(e){
|
||||
var input = $(this), old_tag = input.data('old_tag'), tag = $.trim(input.val().replace(/ +/, ' ').replace(o.dregex, o.delimiter[0]));
|
||||
if (!tag) {
|
||||
if (old_tag && o.beforeTagDelete(el, ed, tag_list, old_tag) === false) {
|
||||
input.val(old_tag).trigger('autogrow').focus();
|
||||
blur_result = false;
|
||||
update_globals();
|
||||
return;
|
||||
}
|
||||
try { input.closest('li').remove(); } catch(e){}
|
||||
if (old_tag) update_globals();
|
||||
}
|
||||
else if (tag.indexOf(o.delimiter[0])>=0) { split_cleanup(input); return; }
|
||||
else if (tag != old_tag) {
|
||||
if (o.forceLowercase) tag = tag.toLowerCase();
|
||||
o.beforeTagSave(el, ed, tag_list, old_tag, tag);
|
||||
// remove duplicates
|
||||
$('.tag-editor-tag:not(.active)', ed).each(function(){ if ($(this).html() == tag) $(this).closest('li').remove(); });
|
||||
}
|
||||
input.parent().html(tag).removeClass('active');
|
||||
if (tag != old_tag) update_globals();
|
||||
set_placeholder();
|
||||
});
|
||||
|
||||
var pasted_content;
|
||||
ed.on('paste', 'input', function(e){
|
||||
$(this).removeAttr('maxlength');
|
||||
pasted_content = $(this);
|
||||
setTimeout(function(){ split_cleanup(pasted_content); }, 30);
|
||||
});
|
||||
|
||||
// keypress delimiter
|
||||
var inp;
|
||||
ed.on('keypress', 'input', function(e){
|
||||
if (o.delimiter.indexOf(String.fromCharCode(e.which))>=0) {
|
||||
inp = $(this);
|
||||
setTimeout(function(){ split_cleanup(inp); }, 20);
|
||||
}
|
||||
});
|
||||
|
||||
ed.on('keydown', 'input', function(e){
|
||||
var $t = $(this);
|
||||
// left/up key + backspace key on empty field
|
||||
if ((e.which == 37 || !o.autocomplete && e.which == 38) && !$t.caret() || e.which == 8 && !$t.val()) {
|
||||
var prev_tag = $t.closest('li').prev('li').find('.tag-editor-tag');
|
||||
if (prev_tag.length) prev_tag.click().find('input').caret(-1);
|
||||
else if ($t.val()) $(new_tag).insertBefore($t.closest('li')).find('.tag-editor-tag').click();
|
||||
return false;
|
||||
}
|
||||
// right/down key
|
||||
else if ((e.which == 39 || !o.autocomplete && e.which == 40) && ($t.caret() == $t.val().length)) {
|
||||
var next_tag = $t.closest('li').next('li').find('.tag-editor-tag');
|
||||
if (next_tag.length) next_tag.click().find('input').caret(0);
|
||||
else if ($t.val()) ed.click();
|
||||
return false;
|
||||
}
|
||||
// tab key
|
||||
else if (e.which == 9) {
|
||||
if (e.shiftKey) { // jump left
|
||||
var prev_tag = $t.closest('li').prev('li').find('.tag-editor-tag');
|
||||
if (prev_tag.length) prev_tag.click().find('input').caret(0);
|
||||
else if ($t.val()) $(new_tag).insertBefore($t.closest('li')).find('.tag-editor-tag').click();
|
||||
} else { // jump right
|
||||
var next_tag = $t.closest('li').next('li').find('.tag-editor-tag');
|
||||
if (next_tag.length) next_tag.click().find('input').caret(0);
|
||||
else if ($t.val()) ed.click();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// del key
|
||||
else if (e.which == 46 && (!$.trim($t.val()) || ($t.caret() == $t.val().length))) {
|
||||
var next_tag = $t.closest('li').next('li').find('.tag-editor-tag');
|
||||
if (next_tag.length) next_tag.click().find('input').caret(0);
|
||||
else if ($t.val()) ed.click();
|
||||
return false;
|
||||
}
|
||||
// enter key
|
||||
else if (e.which == 13) {
|
||||
var next_tag = $t.closest('li').next('li').find('.tag-editor-tag');
|
||||
if (next_tag.length) next_tag.click().find('input').caret(0);
|
||||
else if ($t.val()) ed.click();
|
||||
return false;
|
||||
}
|
||||
// pos1
|
||||
else if (e.which == 36 && !$t.caret()) ed.find('.tag-editor-tag').first().click();
|
||||
// end
|
||||
else if (e.which == 35 && $t.caret() == $t.val().length) ed.find('.tag-editor-tag').last().click();
|
||||
// esc
|
||||
else if (e.which == 27) {
|
||||
$t.val($t.data('old_tag') ? $t.data('old_tag') : '').blur();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// create initial tags
|
||||
var tags = o.initialTags.length ? o.initialTags : el.val().split(o.dregex);
|
||||
for (i in tags) {
|
||||
var tag = $.trim(tags[i].replace(/ +/, ' '));
|
||||
if (tag) {
|
||||
if (o.forceLowercase) tag = tag.toLowerCase();
|
||||
tag_list.push(tag);
|
||||
ed.append('<li><div class="tag-editor-spacer"> '+o.delimiter[0]+'</div><div class="tag-editor-tag">'+tag+'</div><div class="tag-editor-delete"><i></i></div></li>');
|
||||
}
|
||||
}
|
||||
update_globals(true); // true -> no onChange callback
|
||||
|
||||
// init sortable
|
||||
if (o.sortable && $.fn.sortable) ed.sortable({
|
||||
distance: 5, cancel: '.tag-editor-spacer, input', helper: 'clone',
|
||||
update: function(){ update_globals(); }
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.tagEditor.defaults = {
|
||||
initialTags: [],
|
||||
maxLength: 50,
|
||||
delimiter: ',;',
|
||||
placeholder: '',
|
||||
forceLowercase: true,
|
||||
clickDelete: false,
|
||||
sortable: true, // jQuery UI sortable
|
||||
autocomplete: null, // options dict for jQuery UI autocomplete
|
||||
|
||||
// callbacks
|
||||
onChange: function(){},
|
||||
beforeTagSave: function(){},
|
||||
beforeTagDelete: function(){}
|
||||
};
|
||||
}(jQuery));
|
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>聊天室</title>
|
||||
<link rel="stylesheet" href="/css/chat.css">
|
||||
<script src="/js/jquery.1.7.2.min.js"></script>
|
||||
<script src="/js/chat.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="chat">
|
||||
<div class="sidebar">
|
||||
<div class="m-card">
|
||||
<header>
|
||||
<p class="name" >姓名:</p>
|
||||
<p class="name" id="username"></p>
|
||||
</header>
|
||||
</div>
|
||||
<div class="m-list">
|
||||
<ul id="user_list">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="m-message">
|
||||
<ul id="msg_list">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-text">
|
||||
<textarea placeholder="按 Enter 发送" id="my_test"></textarea>
|
||||
<div class="pager_btn">
|
||||
<button id="send">发送</button>
|
||||
</div>
|
||||
<div class="arrow_box">
|
||||
发送内容不能为空
|
||||
<div class="arrow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>xjs-study</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
<name>Netty项目</name>
|
||||
<modules>
|
||||
<module>netty-springboot</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>netty-project</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
<name>学习</name>
|
||||
<modules>
|
||||
<module>xjs-study-base</module>
|
||||
<module>netty-project</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>xjs-study</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -0,0 +1,33 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
public class BlockingQueueDemo {
|
||||
|
||||
public static void main(String[] args) {
|
||||
//创建阻塞队列
|
||||
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
|
||||
|
||||
//第一组
|
||||
System.out.println(blockingQueue.add("A"));
|
||||
|
||||
System.out.println(blockingQueue.add("B"));
|
||||
|
||||
System.out.println(blockingQueue.add("C"));
|
||||
|
||||
//System.out.println(blockingQueue.add("D"));
|
||||
|
||||
|
||||
System.out.println(blockingQueue.remove());
|
||||
System.out.println(blockingQueue.remove());
|
||||
System.out.println(blockingQueue.remove());
|
||||
//System.out.println(blockingQueue.remove());
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-08
|
||||
*/
|
||||
public class CompleableFutureDemo {
|
||||
|
||||
public static void main(String[] args) throws ExecutionException, InterruptedException {
|
||||
CompletableFuture<Void> c =CompletableFuture.runAsync(()->{
|
||||
System.out.println(Thread.currentThread().getName());
|
||||
});
|
||||
c.get();
|
||||
|
||||
|
||||
CompletableFuture<String> cc =CompletableFuture.supplyAsync(()->{
|
||||
System.out.println(Thread.currentThread().getName());
|
||||
return Thread.currentThread().getName();
|
||||
});
|
||||
|
||||
cc.whenComplete((t,u)->{
|
||||
System.out.println(t); //返回值
|
||||
System.out.println(u); //异常
|
||||
}).get();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
public class DeadLock {
|
||||
|
||||
static Object a = new Object();
|
||||
static Object b = new Object();
|
||||
|
||||
public static void main(String[] args) {
|
||||
new Thread(()->{
|
||||
synchronized (a) {
|
||||
System.out.println("A");
|
||||
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
synchronized (b) {
|
||||
System.out.println("B");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
new Thread(()->{
|
||||
synchronized (b) {
|
||||
System.out.println("a");
|
||||
|
||||
synchronized (a) {
|
||||
System.out.println("b");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Lock关键字学习
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-03-02
|
||||
*/
|
||||
public class LockTicket {
|
||||
|
||||
//票总数
|
||||
private int number = 3000;
|
||||
|
||||
//创建可重入锁
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
|
||||
//卖票方法
|
||||
public void sale() {
|
||||
//上锁
|
||||
lock.lock();
|
||||
|
||||
try {
|
||||
if (number > 0) {
|
||||
System.out.println(Thread.currentThread().getName() + ":卖出:" + (number--) + "剩下:" + number);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
LockTicket lockTicket = new LockTicket();
|
||||
|
||||
new Thread(() -> {
|
||||
for (int i = 0; i < 9999; i++) {
|
||||
lockTicket.sale();
|
||||
}
|
||||
|
||||
}, "A").start();
|
||||
|
||||
new Thread(() -> {
|
||||
for (int i = 0; i < 9999; i++) {
|
||||
lockTicket.sale();
|
||||
}
|
||||
|
||||
}, "B").start();
|
||||
|
||||
new Thread(() -> {
|
||||
for (int i = 0; i < 9999; i++) {
|
||||
lockTicket.sale();
|
||||
}
|
||||
|
||||
}, "C").start();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,128 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 创建资源类
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
|
||||
class ShareResource {
|
||||
|
||||
//定义标志位
|
||||
private int flag = 1; //1 AA 2 BB 3 CC
|
||||
|
||||
//创建Lock锁
|
||||
private Lock lock = new ReentrantLock();
|
||||
|
||||
//创建3个condition
|
||||
private Condition c1 = lock.newCondition();
|
||||
private Condition c2 = lock.newCondition();
|
||||
private Condition c3 = lock.newCondition();
|
||||
|
||||
|
||||
public void print5(int loop) throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
while (flag != 1) {
|
||||
c1.await();
|
||||
}
|
||||
//干活
|
||||
for (int i = 1; i <=5; i++) {
|
||||
System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);
|
||||
|
||||
}
|
||||
flag = 2;
|
||||
c1.signal();
|
||||
|
||||
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void print10(int loop) throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
while (flag != 2) {
|
||||
c1.await();
|
||||
}
|
||||
//干活
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);
|
||||
|
||||
}
|
||||
flag = 3;
|
||||
c1.signal();
|
||||
|
||||
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void print15(int loop) throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
while (flag != 3) {
|
||||
c1.await();
|
||||
}
|
||||
//干活
|
||||
for (int i = 1; i <=15; i++) {
|
||||
System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);
|
||||
|
||||
}
|
||||
flag = 1;
|
||||
c1.signal();
|
||||
|
||||
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class ThreadDemo3 {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ShareResource shareResource = new ShareResource();
|
||||
|
||||
new Thread(() ->{
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
try {
|
||||
shareResource.print5(i);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
},"AA").start();
|
||||
|
||||
new Thread(() ->{
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
try {
|
||||
shareResource.print10(i);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
},"BB").start();
|
||||
|
||||
new Thread(() ->{
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
try {
|
||||
shareResource.print15(i);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
},"CC").start();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* 线程不安全List特点
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
public class ThreadDemo4 {
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
//创建ArrayList
|
||||
//List<String> list = new Vector<>(); //通过Vector解决
|
||||
|
||||
//List<String> list = Collections.synchronizedList(new ArrayList<>()); //Collections工具类解决
|
||||
|
||||
CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>(); //写时复制技术解决
|
||||
|
||||
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
new Thread(() -> {
|
||||
//像集合中添加内容
|
||||
list.add(UUID.randomUUID().toString().substring(0, 5));
|
||||
|
||||
System.out.println(list);
|
||||
|
||||
}, String.valueOf(i)).start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.xjs.juc;
|
||||
|
||||
/**
|
||||
* 练习juc
|
||||
* @author xiejs
|
||||
* @since 2022-03-02
|
||||
*/
|
||||
public class Ticket {
|
||||
|
||||
private int number = 30;
|
||||
|
||||
public synchronized void sale() {
|
||||
|
||||
if (number > 0) {
|
||||
System.out.println(Thread.currentThread().getName()+":卖出:"+(number--)+"剩下:"+number);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class SaleTicket {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Ticket ticket = new Ticket();
|
||||
|
||||
new Thread(() ->{
|
||||
|
||||
//调用卖票方法
|
||||
for (int i = 0; i < 40; i++) {
|
||||
ticket.sale();
|
||||
}
|
||||
|
||||
},"AA").start();
|
||||
|
||||
new Thread(() ->{
|
||||
|
||||
//调用卖票方法
|
||||
for (int i = 0; i < 40; i++) {
|
||||
ticket.sale();
|
||||
}
|
||||
|
||||
},"BB").start();
|
||||
|
||||
new Thread(() ->{
|
||||
|
||||
//调用卖票方法
|
||||
for (int i = 0; i < 40; i++) {
|
||||
ticket.sale();
|
||||
}
|
||||
|
||||
},"CC").start();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.xjs.juc.callable;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
public class Demo1 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new Thread(new MyThread1(),"AA").start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
class MyThread1 implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyThread2 implements Callable{
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return 200;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package com.xjs.juc.poll;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
public class ThreadPollDemo1 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
//一池多线程
|
||||
|
||||
ExecutorService pool = Executors.newFixedThreadPool(100);
|
||||
|
||||
try {
|
||||
//10个请求
|
||||
|
||||
for (int i = 1; i <= 10000000; i++) {
|
||||
|
||||
//执行
|
||||
pool.execute(()->{
|
||||
System.out.println(Thread.currentThread().getName()+"办理业务");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
|
||||
}finally {
|
||||
pool.shutdown();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.xjs.juc.poll;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-08
|
||||
*/
|
||||
public class ThreadPoolDemo2 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
||||
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
|
||||
2, //保持线程数
|
||||
5, //最大线程数量
|
||||
2L, //线程保持时间
|
||||
TimeUnit.SECONDS, //保持时间单位
|
||||
new ArrayBlockingQueue<>(3), //阻塞队列
|
||||
Executors.defaultThreadFactory(), //线程工厂
|
||||
new ThreadPoolExecutor.AbortPolicy() //拒绝策略
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.xjs.juc.readwrite;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-07
|
||||
*/
|
||||
public class ReadWriteLockDemo {
|
||||
|
||||
private volatile Map<String, String> map = new HashMap();
|
||||
|
||||
//放数据
|
||||
public void put(String key, Object value) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.xjs.netty.chat;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
/**
|
||||
* 聊天室处理类
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
public class NettyChatClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
/**
|
||||
* 通道读取就绪事件
|
||||
* @param ctx
|
||||
* @param msg
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.xjs.netty.chat;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 聊天室业务处理类
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
public class NettyChatServerHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
private static List<Channel> channelList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 通道就绪事件
|
||||
* @param ctx
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
Channel channel = ctx.channel();
|
||||
|
||||
//当有新的客户端连接的时候,将通道放入集合
|
||||
channelList.add(channel);
|
||||
|
||||
System.out.println("[Server]:"+channel.remoteAddress().toString().substring(1)+"在线");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道读取事件
|
||||
* @param ctx
|
||||
* @param msg
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
//当前发送消息的通道,当前发送的客户端连接
|
||||
Channel channel = ctx.channel();
|
||||
|
||||
for (Channel channel1 : channelList) {
|
||||
//排除自身通道
|
||||
if (channel != channel1) {
|
||||
channel1.writeAndFlush("[" + channel.remoteAddress().toString().substring(1) + "]说:" + msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通道未就绪 --channel下线
|
||||
* @param ctx
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
Channel channel = ctx.channel();
|
||||
|
||||
//当有客户端断开连接的时候,就移出对应的通道
|
||||
channelList.remove(channel);
|
||||
|
||||
System.out.println("[Server]"+channel.remoteAddress().toString().substring(1)+"下线");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 异常处理事件
|
||||
* @param ctx
|
||||
* @param cause
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
cause.printStackTrace();
|
||||
Channel channel = ctx.channel();
|
||||
System.out.println("[Server]:"+channel.remoteAddress().toString().substring(1)+"异常");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package com.xjs.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息解码器
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
public class MessageDecoder extends MessageToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, Object msg, List out) throws Exception {
|
||||
System.out.println("正在进行消息解码...");
|
||||
|
||||
ByteBuf byteBuf = (ByteBuf) msg;
|
||||
|
||||
//传递到下一个handler
|
||||
out.add(byteBuf.toString(CharsetUtil.UTF_8));
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.xjs.netty.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* http服务器处理类
|
||||
* @author xiejs
|
||||
* @since 2022-03-09
|
||||
*/
|
||||
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
|
||||
|
||||
/**
|
||||
* 读取就绪事件
|
||||
* @param ctx
|
||||
* @param msg
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
|
||||
|
||||
//1、判断请求是不是http请求
|
||||
if (msg instanceof HttpRequest) {
|
||||
|
||||
DefaultHttpRequest request = (DefaultHttpRequest) msg;
|
||||
|
||||
System.out.println("浏览器请求路径:"+request.uri());
|
||||
|
||||
if ("/favicon.ico".equals(request.uri())) {
|
||||
System.out.println("图标不响应");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer("Hello,I am Netty Server !", CharsetUtil.UTF_8);
|
||||
|
||||
//2、给浏览器响应
|
||||
DefaultFullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1 ,HttpResponseStatus.OK,byteBuf
|
||||
);
|
||||
|
||||
//2.1、设置响应头
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,
|
||||
byteBuf.readableBytes());
|
||||
|
||||
ctx.writeAndFlush(response);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.xjs.nio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-03-08
|
||||
*/
|
||||
public class CreateBufferDemo {
|
||||
|
||||
public static void main(String[] args) {
|
||||
//创建指定长度的缓冲区
|
||||
|
||||
ByteBuffer allocate = ByteBuffer.allocate(5);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
System.out.println(allocate.get()); //从缓冲区中拿取数据
|
||||
}
|
||||
|
||||
//System.out.println(allocate.get());
|
||||
|
||||
//创建有内容的缓冲区
|
||||
ByteBuffer wrap = ByteBuffer.wrap("lagou".getBytes());
|
||||
for (int i = 0; i < 5; i++) {
|
||||
System.out.println(wrap.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.xjs.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* nio客户端
|
||||
* @author xiejs
|
||||
* @since 2022-03-08
|
||||
*/
|
||||
public class NioClient {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
//打开通道
|
||||
SocketChannel socketChannel = SocketChannel.open();
|
||||
|
||||
//设置连接ip和端口
|
||||
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9998));
|
||||
|
||||
//写出数据
|
||||
socketChannel.write(ByteBuffer.wrap("还钱".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
//读取服务端返回的数据
|
||||
ByteBuffer allocate = ByteBuffer.allocate(1024);
|
||||
int read = socketChannel.read(allocate);
|
||||
System.out.println("服务端消息:"+new String(allocate.array(),0,read,StandardCharsets.UTF_8));
|
||||
|
||||
//释放资源
|
||||
socketChannel.close();
|
||||
}
|
||||
}
|