NIOChatServer.java 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package cn.hhj.nio.chat;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.Channel;
  6. import java.nio.channels.SelectionKey;
  7. import java.nio.channels.Selector;
  8. import java.nio.channels.ServerSocketChannel;
  9. import java.nio.channels.SocketChannel;
  10. import java.nio.charset.Charset;
  11. import java.util.HashSet;
  12. import java.util.Iterator;
  13. import java.util.Set;
  14. /**
  15. * 网络多客户端聊天室
  16. * 功能1: 客户端通过Java NIO连接到服务端,支持多客户端的连接
  17. * 功能2:客户端初次连接时,服务端提示输入昵称,如果昵称已经有人使用,提示重新输入,如果昵称唯一,则登录成功,之后发送消息都需要按照规定格式带着昵称发送消息
  18. * 功能3:客户端登录后,发送已经设置好的欢迎信息和在线人数给客户端,并且通知其他客户端该客户端上线
  19. * 功能4:服务器收到已登录客户端输入内容,转发至其他登录客户端。
  20. */
  21. public class NIOChatServer {
  22. private int port = 8080;
  23. private Charset charset = Charset.forName("UTF-8");
  24. //用来记录在线人数,以及昵称
  25. private static HashSet<String> users = new HashSet<String>();
  26. private static String USER_EXIST = "系统提示:该昵称已经存在,请换一个昵称";
  27. //相当于自定义协议格式,与客户端协商好
  28. private static String USER_CONTENT_SPILIT = "#@#";
  29. private Selector selector = null;
  30. public NIOChatServer(int port) throws IOException{
  31. this.port = port;
  32. ServerSocketChannel server = ServerSocketChannel.open();
  33. server.bind(new InetSocketAddress(this.port));
  34. server.configureBlocking(false);
  35. selector = Selector.open();
  36. server.register(selector, SelectionKey.OP_ACCEPT);
  37. System.out.println("服务已启动,监听端口是:" + this.port);
  38. }
  39. /*
  40. * 开始监听
  41. */
  42. public void listen() throws IOException{
  43. while(true) {
  44. int wait = selector.select();
  45. if(wait == 0) continue;
  46. Set<SelectionKey> keys = selector.selectedKeys(); //可以通过这个方法,知道可用通道的集合
  47. Iterator<SelectionKey> iterator = keys.iterator();
  48. while(iterator.hasNext()) {
  49. SelectionKey key = (SelectionKey) iterator.next();
  50. iterator.remove();
  51. process(key);
  52. }
  53. }
  54. }
  55. public void process(SelectionKey key) throws IOException {
  56. if(key.isAcceptable()){
  57. ServerSocketChannel server = (ServerSocketChannel)key.channel();
  58. SocketChannel client = server.accept();
  59. //非阻塞模式
  60. client.configureBlocking(false);
  61. //注册选择器,并设置为读取模式,收到一个连接请求,然后起一个SocketChannel,并注册到selector上,之后这个连接的数据,就由这个SocketChannel处理
  62. client.register(selector, SelectionKey.OP_READ);
  63. //将此对应的channel设置为准备接受其他客户端请求
  64. key.interestOps(SelectionKey.OP_ACCEPT);
  65. // System.out.println("有客户端连接,IP地址为 :" + sc.getRemoteAddress());
  66. client.write(charset.encode("请输入你的昵称"));
  67. }
  68. //处理来自客户端的数据读取请求
  69. if(key.isReadable()){
  70. //返回该SelectionKey对应的 Channel,其中有数据需要读取
  71. SocketChannel client = (SocketChannel)key.channel();
  72. ByteBuffer buff = ByteBuffer.allocate(1024);
  73. StringBuilder content = new StringBuilder();
  74. try{
  75. while(client.read(buff) > 0) {
  76. buff.flip();
  77. content.append(charset.decode(buff));
  78. }
  79. // System.out.println("从IP地址为:" + sc.getRemoteAddress() + "的获取到消息: " + content);
  80. //将此对应的channel设置为准备下一次接受数据
  81. key.interestOps(SelectionKey.OP_READ);
  82. }catch (IOException io){
  83. key.cancel();
  84. if(key.channel() != null) {
  85. key.channel().close();
  86. }
  87. }
  88. if(content.length() > 0) {
  89. String[] arrayContent = content.toString().split(USER_CONTENT_SPILIT);
  90. //注册用户
  91. if(arrayContent != null && arrayContent.length == 1) {
  92. String nickName = arrayContent[0];
  93. if(users.contains(nickName)) {
  94. client.write(charset.encode(USER_EXIST));
  95. } else {
  96. users.add(nickName);
  97. int onlineCount = onlineCount();
  98. String message = "欢迎 " + nickName + " 进入聊天室! 当前在线人数:" + onlineCount;
  99. broadCast(null, message);
  100. }
  101. }
  102. //注册完了,发送消息
  103. else if(arrayContent != null && arrayContent.length > 1) {
  104. String nickName = arrayContent[0];
  105. String message = content.substring(nickName.length() + USER_CONTENT_SPILIT.length());
  106. message = nickName + " 说 " + message;
  107. if(users.contains(nickName)) {
  108. //不回发给发送此内容的客户端
  109. broadCast(client, message);
  110. }
  111. }
  112. }
  113. }
  114. }
  115. public int onlineCount() {
  116. int res = 0;
  117. for(SelectionKey key : selector.keys()){
  118. Channel target = key.channel();
  119. if(target instanceof SocketChannel){
  120. res++;
  121. }
  122. }
  123. return res;
  124. }
  125. public void broadCast(SocketChannel client, String content) throws IOException {
  126. //广播数据到所有的SocketChannel中
  127. for(SelectionKey key : selector.keys()) {
  128. Channel targetchannel = key.channel();
  129. //如果client不为空,不回发给发送此内容的客户端
  130. if(targetchannel instanceof SocketChannel && targetchannel != client) {
  131. SocketChannel target = (SocketChannel)targetchannel;
  132. target.write(charset.encode(content));
  133. }
  134. }
  135. }
  136. public static void main(String[] args) throws IOException {
  137. new NIOChatServer(8080).listen();
  138. }
  139. }