文章目录

  • 前言
  • 聊天室群聊
    • 1. 思路
    • 2. 代码
      • 1. handler,用于处理消息
        • 1. 好友聊天消息处理器
        • 2. 群聊消息处理器
        • 3. 创建群消息处理器
        • 4. 加入群聊消息处理器
        • 5. 查看群成员消息处理器
        • 6. 退出群消息处理器
        • 7. 登陆消息处理器
        • 8. 退出程序处理器
      • 2. 消息类,用于发送返回
        • 1. AbstractResponseMessage
        • 2. ChatRequestMessage
        • 3. ChatResponseMessage
        • 4. GroupChatRequestMessage
        • 5. GroupChatResponseMessage
        • 6. GroupCreateRequestMessage
        • 7. GroupCreateResponseMessage
        • 8. GroupJoinRequestMessage
        • 9. GroupJoinResponseMessage
        • 10. GroupMembersRequestMessage
        • 11. GroupMembersResponseMessage
        • 12. GroupQuitRequestMessage
        • 13. GroupQuitResponseMessage
        • 14. LoginRequestMessage
        • 15. LoginResponseMessage
        • 15. Message
        • 16. PingMessage
        • 17. PongMessage
      • 3. Session类,用于存储 channel
        • 1. Group:聊天室
        • 2. GroupSession:聊天室接口
        • 3. GroupSessionFactory
        • 4. GroupSessionFactoryImpl:群聊方法实现类
        • 5. Session:会话管理接口
        • 6. SessionFactory :会话工厂类
        • 7. SessionMemoryImpl:会话管理接口实现类
      • 4. 登陆类
        • 1. UserService:登陆管理接口
        • 2. UserServiceFactory:返回userService的实现类
        • 3. UserServiceMemoryImpl:用户管理接口的实现类
      • 5. 协议
        • 1. MessageCodecSharable: 协议累,消息按这种格式传递和解析
        • 2. 协议解码处理类, 配合上面的协议类一起用
      • 6. 服务端
      • 7. 客户端


前言

笔记基于黑马的Netty教学讲义加上自己的一些理解,感觉这是看过的视频中挺不错的,基本没有什么废话,视频地址:黑马Netty,这里的聊天室是用的黑马提供的资料,跟着黑马来敲的,补齐了视频中没有写的退出群聊和查看成员方法。

首页这里的代码没有涉及到数据库,对于注册等方法以后进行完善,其次里面的一些方法写得不是很完整,考虑的情况不全,但是只要达到学习的目的就够了,知道聊天室消息的流程就够了


聊天室群聊

1. 思路

  • 账号密码存在后台中,登陆完成之后和 Server 建立连接之后,把 channel 和 用户名字关联起来存到一个 map 中
  • 对于每个 handler 的处理我们都继承 SimpleChannelInboundHandler 这个类,对于不同的消息,进行不同的处理
  • 每个 handler 都是 @Sharable 的,要加上这个注解
  • 对于每个人和每个群聊都用 map 进行对 channel 的存储
  • 在发送消息的时候,群聊就是通过 map 找出所有的 channel,给出了自己的其他人发送消息
  • 对于每一个 client,为了防止异常的连接比如网络卡这些影响,我们需要定时发送心跳包给服务端



2. 代码

1. handler,用于处理消息

1. 好友聊天消息处理器

  1. 拿到好友姓名
  2. 通过好友姓名查出channel
  3. 发送消息
  4. 如果对方不在线就响应对方不在线给自己
@ChannelHandler.Sharable
public class ChatRequestHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage msg) throws Exception {//发给谁String to = msg.getTo();//找到channelChannel channel = SessionFactory.getSession().getChannel(to);//对方在线if(channel != null){channel.writeAndFlush(new ChatResponseMessage(msg.getFrom(), msg.getContent()));}//对方不在线else{ctx.writeAndFlush(new ChatResponseMessage(false, "对方用户不在线"));}}
}



2. 群聊消息处理器

  1. 获取群聊名字
  2. 获取发送的内容
  3. 获取这个群里面所有的成员的 channel
  4. 给出了自己的所有 channel 发送消息
@ChannelHandler.Sharable
public class GroupChatRequestMessageHandler extends SimpleChannelInboundHandler<GroupChatRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupChatRequestMessage msg) throws Exception {//内容String content = msg.getContent();//获取成员List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());if(membersChannel == null){ctx.writeAndFlush(new GroupChatResponseMessage(false, "群聊不存在"));}else{for (Channel channel : membersChannel) {if(!ctx.channel().equals(channel)){//不给自己发消息channel.writeAndFlush(new GroupChatResponseMessage(msg.getFrom(), msg.getContent()));}}}}
}



3. 创建群消息处理器

  1. 获取群聊名字
  2. 获取到要拉进去的人
  3. 创建一个 Group,create 方法进行存储到 map 了
  4. 给拉进群的人发送消息,通知他们被拉进群了
//不存在状态信息,可共享
@ChannelHandler.Sharable
public class GroupCreateRequestMessageHandler extends SimpleChannelInboundHandler<GroupCreateRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupCreateRequestMessage msg) throws Exception {String groupName = msg.getGroupName();Set<String> members = msg.getMembers();//群管理器GroupSession groupSession = GroupSessionFactory.getGroupSession();Group group = groupSession.createGroup(groupName, members);if(group == null){//向用户发送拉入群聊消息List<Channel> channels = groupSession.getMembersChannel(groupName);for (Channel channel : channels) {if(!channel.equals(ctx.channel())){channel.writeAndFlush(new GroupCreateResponseMessage(true, "您已被拉入群聊" + groupName));}}//发送成功消息ctx.writeAndFlush(new GroupCreateResponseMessage(true, "创建群聊成功"));}else{ctx.writeAndFlush(new GroupCreateResponseMessage(false, "群已经存在了"));}}
}



4. 加入群聊消息处理器

  1. 获取自己的channel
  2. 把自己添加到 group 的 map 中
  3. 拿到这个群里面的人的 channel
  4. 给除了自己的所有人发通知有人加群了
@ChannelHandler.Sharable
public class GroupJoinRequestMessageHandler extends SimpleChannelInboundHandler<GroupJoinRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupJoinRequestMessage msg) throws Exception {Channel channel = ctx.channel();//加入groupGroupSessionFactory.getGroupSession().joinMember(msg.getGroupName(), msg.getUsername());List<Channel> membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(msg.getGroupName());//发送消息for (Channel users : membersChannel) {if(!users.equals(channel)){users.writeAndFlush(new GroupJoinResponseMessage(true, msg.getUsername() + "加入了聊天室"));}}}
}



5. 查看群成员消息处理器

  1. 获取群聊名字
  2. 获取到所有的成员的 channel
  3. 通过 channel 找到对应的名字
@ChannelHandler.Sharable
public class GroupMembersRequestMessageHandler extends SimpleChannelInboundHandler<GroupMembersRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupMembersRequestMessage msg) throws Exception {String groupName = msg.getGroupName();//获取List<String> users = GroupSessionFactory.getGroupSession().getUsers(groupName);ctx.channel().writeAndFlush("组 " + groupName + " 的成员是:" + users.toString());}
}



6. 退出群消息处理器

  1. 拿到群聊名字
  2. 获取群里面的人的 channel
  3. 发送消息通知
@ChannelHandler.Sharable
public class GroupQuitRequestMessageHandler extends SimpleChannelInboundHandler<GroupQuitRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, GroupQuitRequestMessage msg) throws Exception {String username = msg.getUsername();String groupName = msg.getGroupName();//移除sessionGroupSessionFactory.getGroupSession().removeMember(groupName, username);for (Channel channel : GroupSessionFactory.getGroupSession().getMembersChannel(groupName)) {channel.writeAndFlush(new GroupQuitResponseMessage(true, "用户" + username + "退出了群聊"));}}
}



7. 登陆消息处理器

  1. 获取用户名密码,进行检测
  2. 把 channel 和 名字相对应起来存入 map 中
  3. 发通知
@ChannelHandler.Sharable
public class LoginRequestMessageHandler extends SimpleChannelInboundHandler<LoginRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage msg) throws Exception {String username = msg.getUsername();String password = msg.getPassword();boolean login = UserServiceFactory.getUserService().login(username, password);LoginResponseMessage message = null;if (login) {message = new LoginResponseMessage(true, "登陆成功");//保存channelSessionFactory.getSession().bind(ctx.channel(), username);} else {message = new LoginResponseMessage(false, "用户名或者密码不正确");}ctx.writeAndFlush(message);}
}



8. 退出程序处理器

  1. 两种情况,一种是连接断开,一种是异常
  2. 都要对应处理,把 channel 移除掉
@Slf4j
@ChannelHandler.Sharable
//我们独立写一个类,因为不是和聊天相关的了
public class QuitHandler extends ChannelInboundHandlerAdapter {//连接断开的时候会触发 Inactive@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {//从 SessionFactory 中移除channel,断开了SessionFactory.getSession().unbind(ctx.channel());log.debug("{} 已经断开", ctx.channel());}//异常是会触发@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {SessionFactory.getSession().unbind(ctx.channel());log.debug("{} 已经异常断开,异常是:{}", ctx.channel(), cause.getMessage());}
}



2. 消息类,用于发送返回

1. AbstractResponseMessage

@Data
@ToString(callSuper = true)
public abstract class AbstractResponseMessage extends Message {private boolean success;private String reason;public AbstractResponseMessage() {}public AbstractResponseMessage(boolean success, String reason) {this.success = success;this.reason = reason;}
}



2. ChatRequestMessage

@Data
@ToString(callSuper = true)
public class ChatRequestMessage extends Message {private String content;private String to;private String from;public ChatRequestMessage() {}public ChatRequestMessage(String from, String to, String content) {this.from = from;this.to = to;this.content = content;}@Overridepublic int getMessageType() {return ChatRequestMessage;}
}



3. ChatResponseMessage

@Data
@ToString(callSuper = true)
public class ChatResponseMessage extends AbstractResponseMessage {private String from;private String content;public ChatResponseMessage(boolean success, String reason) {super(success, reason);}public ChatResponseMessage(String from, String content) {this.from = from;this.content = content;}@Overridepublic int getMessageType() {return ChatResponseMessage;}
}



4. GroupChatRequestMessage

@Data
@ToString(callSuper = true)
public class GroupChatRequestMessage extends Message {private String content;private String groupName;private String from;public GroupChatRequestMessage(String from, String groupName, String content) {this.content = content;this.groupName = groupName;this.from = from;}@Overridepublic int getMessageType() {return GroupChatRequestMessage;}
}



5. GroupChatResponseMessage

@Data
@ToString(callSuper = true)
public class GroupChatResponseMessage extends AbstractResponseMessage {private String from;private String content;public GroupChatResponseMessage(boolean success, String reason) {super(success, reason);}public GroupChatResponseMessage(String from, String content) {this.from = from;this.content = content;}@Overridepublic int getMessageType() {return GroupChatResponseMessage;}
}



6. GroupCreateRequestMessage

@Data
@ToString(callSuper = true)
public class GroupCreateRequestMessage extends Message {private String groupName;private Set<String> members;public GroupCreateRequestMessage(String groupName, Set<String> members) {this.groupName = groupName;this.members = members;}@Overridepublic int getMessageType() {return GroupCreateRequestMessage;}
}



7. GroupCreateResponseMessage

@Data
@ToString(callSuper = true)
public class GroupCreateResponseMessage extends AbstractResponseMessage {public GroupCreateResponseMessage(boolean success, String reason) {super(success, reason);}@Overridepublic int getMessageType() {return GroupCreateResponseMessage;}
}



8. GroupJoinRequestMessage

@Data
@ToString(callSuper = true)
public class GroupJoinRequestMessage extends Message {private String groupName;private String username;public GroupJoinRequestMessage(String username, String groupName) {this.groupName = groupName;this.username = username;}@Overridepublic int getMessageType() {return GroupJoinRequestMessage;}
}



9. GroupJoinResponseMessage

@Data
@ToString(callSuper = true)
public class GroupJoinResponseMessage extends AbstractResponseMessage {public GroupJoinResponseMessage(boolean success, String reason) {super(success, reason);}@Overridepublic int getMessageType() {return GroupJoinResponseMessage;}
}



10. GroupMembersRequestMessage

@Data
@ToString(callSuper = true)
public class GroupMembersRequestMessage extends Message {private String groupName;public GroupMembersRequestMessage(String groupName) {this.groupName = groupName;}@Overridepublic int getMessageType() {return GroupMembersRequestMessage;}
}



11. GroupMembersResponseMessage

@Data
@ToString(callSuper = true)
public class GroupMembersResponseMessage extends Message {private Set<String> members;public GroupMembersResponseMessage(Set<String> members) {this.members = members;}@Overridepublic int getMessageType() {return GroupMembersResponseMessage;}
}



12. GroupQuitRequestMessage

@Data
@ToString(callSuper = true)
public class GroupQuitRequestMessage extends Message {private String groupName;private String username;public GroupQuitRequestMessage(String username, String groupName) {this.groupName = groupName;this.username = username;}@Overridepublic int getMessageType() {return GroupQuitRequestMessage;}
}



13. GroupQuitResponseMessage

@Data
@ToString(callSuper = true)
public class GroupQuitResponseMessage extends AbstractResponseMessage {public GroupQuitResponseMessage(boolean success, String reason) {super(success, reason);}@Overridepublic int getMessageType() {return GroupQuitResponseMessage;}
}



14. LoginRequestMessage

@Data
@ToString(callSuper = true)
public class LoginRequestMessage extends Message {private String username;private String password;public LoginRequestMessage() {}public LoginRequestMessage(String username, String password) {this.username = username;this.password = password;}@Overridepublic int getMessageType() {return LoginRequestMessage;}
}



15. LoginResponseMessage

@Data
@ToString(callSuper = true)
public class LoginResponseMessage extends AbstractResponseMessage {public LoginResponseMessage(boolean success, String reason) {super(success, reason);}@Overridepublic int getMessageType() {return LoginResponseMessage;}
}



15. Message

@Data
public abstract class Message implements Serializable {public static Class<?> getMessageClass(int messageType) {return messageClasses.get(messageType);}private int sequenceId;private int messageType;public abstract int getMessageType();public static final int LoginRequestMessage = 0;public static final int LoginResponseMessage = 1;public static final int ChatRequestMessage = 2;public static final int ChatResponseMessage = 3;public static final int GroupCreateRequestMessage = 4;public static final int GroupCreateResponseMessage = 5;public static final int GroupJoinRequestMessage = 6;public static final int GroupJoinResponseMessage = 7;public static final int GroupQuitRequestMessage = 8;public static final int GroupQuitResponseMessage = 9;public static final int GroupChatRequestMessage = 10;public static final int GroupChatResponseMessage = 11;public static final int GroupMembersRequestMessage = 12;public static final int GroupMembersResponseMessage = 13;public static final int PingMessage = 14;public static final int PongMessage = 15;private static final Map<Integer, Class<?>> messageClasses = new HashMap<>();static {messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);messageClasses.put(ChatRequestMessage, ChatRequestMessage.class);messageClasses.put(ChatResponseMessage, ChatResponseMessage.class);messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class);messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class);messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class);messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class);messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class);messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class);messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class);messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class);messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class);messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class);messageClasses.put(PingMessage, GroupMembersResponseMessage.class);messageClasses.put(PongMessage, GroupMembersResponseMessage.class);}
}



16. PingMessage

public class PingMessage extends Message{@Overridepublic int getMessageType() {return PingMessage;}
}



17. PongMessage

public class PongMessage extends Message{@Overridepublic int getMessageType() {return PongMessage;}
}



3. Session类,用于存储 channel

1. Group:聊天室

@Data
/*** 聊天组,即聊天室*/
public class Group {// 聊天室名称private String name;// 聊天室成员private Set<String> members;public static final Group EMPTY_GROUP = new Group("empty", Collections.emptySet());public Group(String name, Set<String> members) {this.name = name;this.members = members;}
}



2. GroupSession:聊天室接口

/*** 聊天组会话管理接口*/
public interface GroupSession {/*** 创建一个聊天组, 如果不存在才能创建成功, 否则返回 null* @param name 组名* @param members 成员不能重复* @return 成功时返回组对象, 失败返回 null*/Group createGroup(String name, Set<String> members);/*** 加入聊天组* @param name 组名* @param member 成员名* @return 如果组不存在返回 null, 否则返回组对象*/Group joinMember(String name, String member);/*** 移除组成员* @param name 组名* @param member 成员名* @return 如果组不存在返回 null, 否则返回组对象*/Group removeMember(String name, String member);/*** 移除聊天组* @param name 组名* @return 如果组不存在返回 null, 否则返回组对象*/Group removeGroup(String name);/*** 获取组成员* @param name 组名* @return 成员集合, 没有成员会返回 empty set*/Set<String> getMembers(String name);/*** 获取组成员的 channel 集合, 只有在线的 channel 才会返回* @param name 组名* @return 成员 channel 集合*/List<Channel> getMembersChannel(String name);/*** 获取组成员的名字* @param name 组名*/List<String> getUsers(String name);
}



3. GroupSessionFactory

public abstract class GroupSessionFactory {private static GroupSession session = new GroupSessionMemoryImpl();public static GroupSession getGroupSession() {return session;}
}



4. GroupSessionFactoryImpl:群聊方法实现类

public class GroupSessionMemoryImpl implements GroupSession {private final Map<String, Group> groupMap = new ConcurrentHashMap<>();@Overridepublic Group createGroup(String name, Set<String> members) {Group group = new Group(name, members);return groupMap.putIfAbsent(name, group);}@Overridepublic Group joinMember(String name, String member) {return groupMap.computeIfPresent(name, (key, value) -> {value.getMembers().add(member);return value;});}@Overridepublic Group removeMember(String name, String member) {return groupMap.computeIfPresent(name, (key, value) -> {value.getMembers().remove(member);return value;});}@Overridepublic Group removeGroup(String name) {return groupMap.remove(name);}@Overridepublic Set<String> getMembers(String name) {return groupMap.getOrDefault(name, Group.EMPTY_GROUP).getMembers();}@Overridepublic List<Channel> getMembersChannel(String name) {//判断群聊存不存在if(groupMap.get(name) == null){return null;}return getMembers(name).stream().map(member -> SessionFactory.getSession().getChannel(member)).filter(Objects::nonNull).collect(Collectors.toList());}@Overridepublic List<String> getUsers(String name) {if(groupMap.get(name) == null){return null;}return new ArrayList<>(groupMap.get(name).getMembers());}
}



5. Session:会话管理接口

/*** 会话管理接口*/
public interface Session {/*** 绑定会话* @param channel 哪个 channel 要绑定会话* @param username 会话绑定用户*/void bind(Channel channel, String username);/*** 解绑会话* @param channel 哪个 channel 要解绑会话*/void unbind(Channel channel);/*** 获取属性* @param channel 哪个 channel* @param name 属性名* @return 属性值*/Object getAttribute(Channel channel, String name);/*** 设置属性* @param channel 哪个 channel* @param name 属性名* @param value 属性值*/void setAttribute(Channel channel, String name, Object value);/*** 根据用户名获取 channel* @param username 用户名* @return channel*/Channel getChannel(String username);
}



6. SessionFactory :会话工厂类

public abstract class SessionFactory {private static Session session = new SessionMemoryImpl();public static Session getSession() {return session;}
}



7. SessionMemoryImpl:会话管理接口实现类

里面的方法就是绑定 channel 和 name 的

public class SessionMemoryImpl implements Session {private final Map<String, Channel> usernameChannelMap = new ConcurrentHashMap<>();private final Map<Channel, String> channelUsernameMap = new ConcurrentHashMap<>();private final Map<Channel,Map<String,Object>> channelAttributesMap = new ConcurrentHashMap<>();@Overridepublic void bind(Channel channel, String username) {usernameChannelMap.put(username, channel);channelUsernameMap.put(channel, username);channelAttributesMap.put(channel, new ConcurrentHashMap<>());}@Overridepublic void unbind(Channel channel) {String username = channelUsernameMap.remove(channel);usernameChannelMap.remove(username);channelAttributesMap.remove(channel);}@Overridepublic Object getAttribute(Channel channel, String name) {return channelAttributesMap.get(channel).get(name);}@Overridepublic void setAttribute(Channel channel, String name, Object value) {channelAttributesMap.get(channel).put(name, value);}@Overridepublic Channel getChannel(String username) {return usernameChannelMap.get(username);}@Overridepublic String toString() {return usernameChannelMap.toString();}
}



4. 登陆类

1. UserService:登陆管理接口


/*** 用户管理接口*/
public interface UserService {/*** 登录* @param username 用户名* @param password 密码* @return 登录成功返回 true, 否则返回 false*/boolean login(String username, String password);
}



2. UserServiceFactory:返回userService的实现类

public abstract class UserServiceFactory {private static UserService userService = new UserServiceMemoryImpl();public static UserService getUserService() {return userService;}
}



3. UserServiceMemoryImpl:用户管理接口的实现类

public class UserServiceMemoryImpl implements UserService {private Map<String, String> allUserMap = new ConcurrentHashMap<>();{allUserMap.put("zhangsan", "123");allUserMap.put("lisi", "123");allUserMap.put("wangwu", "123");allUserMap.put("zhaoliu", "123");allUserMap.put("qianqi", "123");}@Overridepublic boolean login(String username, String password) {String pass = allUserMap.get(username);if (pass == null) {return false;}return pass.equals(password);}
}



5. 协议

1. MessageCodecSharable: 协议累,消息按这种格式传递和解析

@Slf4j
@ChannelHandler.Sharable
/*** 必须和 LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf 消息是完整的*/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {@Overrideprotected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {ByteBuf out = ctx.alloc().buffer();// 1. 4字节的魔数,最好和java那些不一样,自己独特的out.writeBytes(new byte[]{1, 2, 3, 4});// 2. 1字节的版本,out.writeByte(1);// 3. 1字节的序列化方式,我们约定jdk是0, json是1out.writeByte(0);// 4. 1字节的指令类型,聊天消息还是登陆消息等等,加以区分out.writeByte(msg.getMessageType());// 5. 4个字节的指令请求序号,目前还用不上out.writeInt(msg.getSequenceId());// 无意义,对齐填充out.writeByte(0xff);// 6. 获取内容的字节数组,使用序列化的方式,也可以使用转为JSON的方式//    这里选择了序列化的方式,因为上面序列化方式我们选择了JDKByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(msg);//oos把msg序列化结果写到ObjectOutputStream,ObjectOutputStream再把结果写到ByteArrayOutputStreambyte[] bytes = bos.toByteArray();// 7. 字节数组的长度out.writeInt(bytes.length);// 8. 写入内容out.writeBytes(bytes);//出了内容可变,其他都是固定的,字节大小:// 4+1+1+1+4+4 = 15个字节,我们使用out.writeByte(0xff) +1个字节凑够16个,仅仅是对齐用的,有个规定是2的n次方倍才叫专业outList.add(out);}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {int magicNum = in.readInt();byte version = in.readByte();byte serializerType = in.readByte();byte messageType = in.readByte();int sequenceId = in.readInt();in.readByte();int length = in.readInt();byte[] bytes = new byte[length];in.readBytes(bytes, 0, length);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));Message message = (Message) ois.readObject();//结果放到out里面传给下一个入站处理器就行了out.add(message);}
}



2. 协议解码处理类, 配合上面的协议类一起用

public class ProcotolFrameDecoder extends LengthFieldBasedFrameDecoder {public ProcotolFrameDecoder() {this(1024, 12, 4, 0, 0);}public ProcotolFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);}
}



6. 服务端

  • 添加各种 handler
  • 注意心跳机制的处理
  • 其他就是很常规的代码了
@Slf4j
public class ChatServer {public static void main(String[] args) {NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();LoginRequestMessageHandler LOGIN_HANDLER = new LoginRequestMessageHandler();ChatRequestHandler CHAT_HANDLER = new ChatRequestHandler();GroupCreateRequestMessageHandler GROUP_CREATE_HANDLER = new GroupCreateRequestMessageHandler();GroupJoinRequestMessageHandler GROUP_JOIN_HANDLER = new GroupJoinRequestMessageHandler();GroupMembersRequestMessageHandler GROUP_MEMBERT_HANDLER = new GroupMembersRequestMessageHandler();GroupQuitRequestMessageHandler GROUP_QUIT_HANDLER = new GroupQuitRequestMessageHandler();GroupChatRequestMessageHandler CROUP_CHAR_HANDLER = new GroupChatRequestMessageHandler();QuitHandler QUIT_HANDLER = new QuitHandler();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {//空闲状态检测器:心跳检测,假死的连接占用的资源不能自动释放//用来判断是不是读空闲时间过程或者写空闲时间过长// 参数1:读    参数2:写   参数3:读写// 5秒内如果没有收到channel读入的数据,就会触发一个事件 IdleState.READER_IDLEch.pipeline().addLast(new IdleStateHandler(5, 8, 0));//心跳检测的处理器 ChannelDuplexHandler可以同时作为入站和出站处理器ch.pipeline().addLast(new ChannelDuplexHandler(){//用来触发特殊事件@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {IdleStateEvent event = (IdleStateEvent) evt;if(event.state() == IdleState.READER_IDLE ){//触发了读空闲事件,//log.debug("已经5秒了没有读到数据了");ctx.channel().close();}}});//ProcotolFrameDecoder() 提取出来作为一个单独的类,channel独有的,不能共享ch.pipeline().addLast(new ProcotolFrameDecoder());//ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);//我们用SimpleChannelInboundHandler对指定消息做操作//登陆处理器ch.pipeline().addLast(LOGIN_HANDLER);//好友聊天消息处理器ch.pipeline().addLast(CHAT_HANDLER);//群聊创建处理器ch.pipeline().addLast(GROUP_CREATE_HANDLER);//加入群聊处理器ch.pipeline().addLast(GROUP_JOIN_HANDLER);//群成员处理器ch.pipeline().addLast(GROUP_MEMBERT_HANDLER);//退出群聊处理器ch.pipeline().addLast(GROUP_QUIT_HANDLER);//群聊天处理器ch.pipeline().addLast(CROUP_CHAR_HANDLER);//正常异常退出客户端ch.pipeline().addLast(QUIT_HANDLER);}});Channel channel = serverBootstrap.bind(8080).sync().channel();channel.closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();}}}



7. 客户端

  • 通过菜单对不同的消息进行不同的处理
  • 使用 CountDownLatch 来完成线程间的通信
@Slf4j
public class ChatClient {public static void main(String[] args) {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);AtomicBoolean LOGIN = new AtomicBoolean(false);try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());//ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);// 3秒内如果没有发数据,就触发一个IdleState.,就会触发一个事件 IdleState.WRITER_IDLE写空闲ch.pipeline().addLast(new IdleStateHandler(0, 3, 0));//心跳检测的处理器 ChannelDuplexHandler可以同时作为入站和出站处理器ch.pipeline().addLast(new ChannelDuplexHandler(){//用来触发特殊事件@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {IdleStateEvent event = (IdleStateEvent) evt;if(event.state() == IdleState.WRITER_IDLE ){//触发了写空闲//发一个包证明还活着//log.debug("发一个心跳包,证明我还活着");ctx.writeAndFlush(new PingMessage());}}});ch.pipeline().addLast("clien handler", new ChannelInboundHandlerAdapter(){//接收服务器响应的消息,Nio线程调用的,而下面channelActive中输入是其他线程的,那如何让两个线程通信呢?//使用@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.debug("msg:{}", msg);//计数器 -1,为0,通知下面线程该运行了if(msg instanceof LoginResponseMessage){LoginResponseMessage response = (LoginResponseMessage) msg;if (response.isSuccess()) {LOGIN.set(true);}}//唤醒 System in线程WAIT_FOR_LOGIN.countDown();}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//连接建立后触发active事件,新弄一个线程接收用户在控制台的输入,向服务器发送各种线程//防止使用nio线程导致阻塞new Thread(()->{Scanner scanner = new Scanner(System.in);System.out.println("请输入用户名:");String username = scanner.nextLine();System.out.println("请输入密码:");String password = scanner.nextLine();//构造消息对象LoginRequestMessage message = new LoginRequestMessage(username, password);//发送消息ctx.writeAndFlush(message);System.out.println("等待后续操作....");try {//计数器等待WAIT_FOR_LOGIN.await();} catch (InterruptedException e) {e.printStackTrace();}//如果登陆失败if(!LOGIN.get()){ctx.channel().close();return;}//登陆成功就选择菜单while(true){System.out.println("==================================");System.out.println("send [username] [content]");System.out.println("gsend [group name] [content]");System.out.println("gcreate [group name] [m1,m2,m3...]");System.out.println("gmembers [group name]");System.out.println("gjoin [group name]");System.out.println("gquit [group name]");System.out.println("quit");System.out.println("==================================");String command = scanner.nextLine();String[] s = command.split(" ");switch(s[0]){case "send":{ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));break;}case "gsend":{ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));break;}case "gcreate":{//创建群组的时候可以一起把别人拉进来Set<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));set.add(username); //加入自己ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));break;}case "gmembers":{ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));break;}case "gjoin":{ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));break;}case "gquit":{ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));break;}case "quit":{ctx.channel().close();return;}default:throw new IllegalStateException("Unexpected value: " + s[0]);}}}, "System in").start();}});}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.closeFuture().sync();} catch (Exception e) {log.error("client error", e);} finally {group.shutdownGracefully();}}
}






如有错误,欢迎指出!!!

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Halcon缺陷检测系列

    Halcon缺陷检测系列Halcon缺陷检测系列Halcon缺陷检测系列 一 注塑吸嘴缺口检测 二 铣刀刀口破损缺陷检测 三 网状产品表面破损检测 四 手机摄像头图像表面的轻微缺陷检测 五 皮革纹理表面缺陷检测 六 化妆品标签褶皱检测 七 傅立叶求背景有噪声的钢轨宽度 八 定位出所有网格顶…...

    2024/4/14 6:13:20
  2. 论文笔记 Shape and Time Distortion Loss for Training DeepTime Series Forecasting Models

    0 摘要 本文解决了非平稳信号的时间序列预测和多个未来步骤预测的问题。为了处理这个具有挑战性的任务&#xff0c;我们引入了 DILATE&#xff08;包括形状和时间的失真损失&#xff0c;DIstortion Loss including shApe and TimE&#xff09;&#xff0c;这是一种用于训练深度…...

    2024/5/4 2:25:48
  3. JS基础 严格模式

    阅读目录undefinednull严格模式基本差异解构差异undefined 对声明但未赋值的变量返回类型为 undefined 表示值未定义。 let hd; console.log(typeof hd);对未声明的变量使用会报错&#xff0c;但判断类型将显示 undefined。 console.log(typeof wgchen); console.log(wgchen…...

    2024/5/4 1:07:16
  4. 常忘记问题:对象指针数组在函数间传递c/c++

    我是赛佳佳小白&#xff0c;高手可以出去了。。。 经常会在c中定义对象指针数组&#xff0c;然后有时会需要传递这整个数组&#xff0c;然后总是传递后像[]、 的方式使用&#xff0c;这个这个才是正解&#xff1a;传递 obj *name[x]&#xff0c;然后用 obj**类型去接name才是正…...

    2024/4/17 11:29:25
  5. CodeForce 1620 E. Replace the Numbers 【个人反思】

    首先读完题就能看出来&#xff0c;从后往前倒着处理操作 2 &#xff0c;因为某时的操作 2 只会对它前面的数产生已影响。而要考虑到某个数受到后面多个操作&#xff0c;比如 1->2 , 2->3, 这样&#xff0c;就类似于找该条链上祖先节点&#xff0c;考虑到用并查集维护。 …...

    2024/4/15 17:01:08
  6. LVM逻辑卷

    优势 支持不影响数据的情况&#xff0c;扩展容量支持快照&#xff0c; 便于备份数据 LVM操作指令 pv&#xff08;物理卷&#xff09; pvscan 查看pvdisplay /设备 查看pv详细信息pvcreate 设备文件 创建pvpvremove pvname 删除pvvg&#xff08;卷组&#xff09; vgsc…...

    2024/4/23 18:55:52
  7. 生产者消费者问题锁问题

    使用synchronized关键字实现生产者消费者问题 步骤&#xff1a; 判断是否需要等待业务通知 public class Demo {public static void main(String[] args) {Data data new Data();// 线程A,进行加1new Thread(()->{try {// 10 次操作for (int i 0; i < 10; i) {data.…...

    2024/4/19 13:02:29
  8. 树与二叉树

    二叉树性质 1. 2. 3. 4. 5. 二叉树的存储结构 1.顺序存储结构 2.链式存储结构 遍历方法 先序遍历 算法 status preordertraverse(bitree T) {if(TNULL)return OK;else{visit(T);preordertraverse(T->lchild);preordertraverse(T->rchild); }} 中序遍历 后序遍历 …...

    2024/5/1 4:43:42
  9. mysql查询数据为json格式的字段时,取json数据的某一个属性值

    // 其中json_column 是数据格式为json的字段,id是要取值的属性名 select json_column ->> $.id from tableName where json_column ! and json_column is not null;...

    2024/5/4 1:19:00
  10. JDK8新特性---上篇---Lambda,函数式接口,方法引用,optional

    目录 1.为何需要Lambda表达式 1.1 Lambda的格式 2.函数式接口 2.1 Predicate 2.2 Consumer 2.3 Function,r> 2.4 Supplier 2.5BinaryOperatpr 3.Optional 4.方法引用 4.1方法引用和lambda之间的关系 4.2格式 4.2.1对象名(引用名)::非静态方法(实例方法名) 4.…...

    2024/4/14 6:12:45
  11. Swoft 1.0 框架修复 group by 导致分页的总条数错误问题

    问题描述: 在使用包含group by语句的分页功能时,返回的查询结果总条数有误 问题原因: select count(*) from ... group by ... 时, 返回结果为分组后的每组数据条数 框架默认会获取请求结果的第一条数据内容,所以只会得到 分组结果后的第一个组的数据条数 解决方法: 在 \Swof…...

    2024/4/14 6:13:10
  12. github连接不上解决

    参考&#xff1a;https://www.cnblogs.com/liang715200/p/15173771.html...

    2024/4/14 6:13:05
  13. 控制台查看java版本信息没有反应

    jdk安装后在控制台输入java,javac或java -version后没有反应的解决办法 环境:win10,jdk1.8.0_202版本 问题描述:电脑以前安装过jdk,重新卸载安装后,在控制台输入java,javac和java -version都没有反应. jdk是官网下载的,环境变量也是按照网上的教程来走的. 如图: 解决思路:没…...

    2024/4/14 6:12:45
  14. leetcode第494目标和动态规划

    leetcode第494目标和动态规划 动态规划: 这个题目的思路很奇特,寻找一个正子集和一个负子集&#xff0c;两者相加为数组之和&#xff0c;两者相减为target可以解出正数组之和&#xff0c;就转换成了01背包&#xff0c;每个数字只能用一次。题目就转换为了&#xff0c;装满容量…...

    2024/4/17 8:58:07
  15. 【数据分析】python数据分析—生成数据表

    使用 python 进行数据导入前需要先导入 pandas 库 import pandas as pd生成数据表用到的是 pandas 库中的 DateFrame 函数 导入数据表 way1: df pd.DataFrame(pd.read_excel(a.xlsx, sheet_nameSheet1))way2: filename (D:\\pythonProject\\a.xlsx) df pd.read_excel(fi…...

    2024/4/16 19:12:54
  16. 如何在word、pdf、ppt中导入字体

    右击 “个性化” → “字体” → 将下载好的字体拖入“添加字体” 1. 桌面右击 选择“个性化” 2. 点击“字体” 3. 将下载好的字体拖入“添加字体” 完成&#xff01;√ &#xff08;这里给大家推荐一个免费的字体下载网站 方正字库官网 (foundertype.com)&#xff09; …...

    2024/4/14 6:13:36
  17. 获取 TypeScript enum 类型的键名称

    目录背景解决方法参考链接背景 在 TypeScript 中&#xff0c;通常基于数值来创建 enum 类型&#xff0c;例如&#xff1a; enum Direction {Up 1,Down,Left,Right, }这样的话&#xff0c;Direction.Up、Direction.Down、Direction.Left、Direction.Right 的取值就分别为 1、…...

    2024/4/14 6:13:56
  18. edge_edge的亮点功能盘点

    文章目录edge_edge的亮点功能盘点edge比chrome方便的地方edge功能文档沉浸式阅读阅读背景主题调整语法提示可选的多种语音风格的朗读页面内级别的书签:集锦(collection)edge_edge的亮点功能盘点 edge比chrome方便的地方 同步书签/密码/拓展不需要魔法移动端:edge for android…...

    2024/5/4 6:02:01
  19. 【每日一题见微知著】BFS-次短路——到达目的地的第二短时间-Hard(今天这个难度也对啦)

    ⭐️寒假新坑——代码之狐的每日做题笔记 2045. 到达目的地的#第二短时间-Hard 题目描述&#xff1a; 城市用一个 双向连通 图表示&#xff0c;图中有 n 个节点&#xff0c;从 1 到 n 编号&#xff08;包含 1 和 n&#xff09;。图中的边用一个二维整数数组 edges 表示&#…...

    2024/4/20 7:05:50
  20. SimCSE: Simple Contrastive Learning of Sentence Embeddings(EMNLP 2021)

    SimCSE: Simple Contrastive Learning of Sentence Embeddings(EMNLP 2021) Motivation 无监督的SimCSE将句子过两次预训练模型得到两个不同的embedding作为正例对&#xff0c;将同一batch中的其他句子作为负例&#xff0c;模型在所有负例中预测出唯一的正例。有监督的SimCSE…...

    2024/5/4 6:15:42

最新文章

  1. 华为手机 鸿蒙系统-android studio识别调试设备,开启adb调试权限

    1.进入设置-关于手机-版本号&#xff0c;连续点击7次 认证&#xff1a;有锁屏密码需要输入密码&#xff0c; 开启开发者配置功能ok 进入开发者配置界面 打开调试功能 重新在androd studio查看可运行running devices显示了&#xff0c; 不行的话&#xff0c;重启一下android …...

    2024/5/4 6:16:10
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 汽车疲劳测试试验平台技术要求(北重厂家)

    汽车疲劳测试试验平台技术要求通常包括以下几个方面&#xff1a; 车辆加载能力&#xff1a;测试平台需要具备足够的承载能力&#xff0c;能够同时测试多种车型和不同重量的车辆。 动力系统&#xff1a;测试平台需要具备稳定可靠的动力系统&#xff0c;能够提供足够的力和速度来…...

    2024/5/3 8:56:17
  4. 谷粒商城实战(008 缓存)

    Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第151p-第p157的内容 简介 数据库承担落盘&#xff08;持久化&#xff09;工作 拿map做缓存 这种是本地缓存&#xff0c;会有一些问题 分布…...

    2024/5/3 1:39:35
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/3 23:10:03
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/4 2:59:34
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57