自己想了一下怎么实现,就写了,没有深究是否合理.更多处理没有写下去,例如收件人不在线,应该保存在数据库,等下一次连接的时候刷新map,再把数据发送过去,图片发送也没有做,也没有用json格式

socket很奇怪,我用客户端连接上了服务器,没有发送消息的情况下,断开电脑网络,是不会出现问题,然后在把电脑网络连接上,通讯依然正常,正常断开也不出问题,但是用idea直接按stop键,那么服务端就会出问题了,读取事件会一直为true,造成死循环,消耗CPU,所以必须要判断一下客户端连接是否断开了

只需要把客户端代码启动几个,修改一些userName以及收件人,就可以测试,实现类似QQ微信即时通讯,聊天功能

服务端代码

  1. package serversocketchannel;
  2.  
  3. import java.io.IOException;
  4. import java.net.InetSocketAddress;
  5. import java.nio.ByteBuffer;
  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.Iterator;
  12. import java.util.concurrent.ConcurrentHashMap;
  13.  
  14. /**
  15. *
  16. * @author ZhenWeiLai
  17. *
  18. */
  19. public class ServerSocketChannelNonBlocking {
  20. private static ServerSocketChannel serverSocketChannel = null;
  21. private static Charset charset = Charset.forName("GBK");//设置编码集,用于编码,解码
  22. private static Selector selector = null;
  23. //保存客户端的map
  24. private static final ConcurrentHashMap<String,SocketChannel> clientSockets = new ConcurrentHashMap<>();
  25. static{
  26. try {
  27. serverSocketChannel = ServerSocketChannel.open();
  28. serverSocketChannel.socket().setReuseAddress(true);
  29. serverSocketChannel.socket().bind(new InetSocketAddress(8000));
  30. serverSocketChannel.configureBlocking(false);//设置为非阻塞
  31. selector = Selector.open();//实例化一个选择器
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36.  
  37. public static void main(String[] args) {
  38. service();
  39. }
  40.  
  41. private static void service(){
  42. SocketChannel clientChannel = null;
  43. SelectionKey selectionKey = null;
  44. SocketChannel targetChannel = null;
  45. try {
  46. serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);//服务端监听连接
  47. while(true){
  48. selector.select();//阻塞至有新的连接就开始处理
  49. Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
  50. while(selectionKeys.hasNext()){
  51. selectionKey = selectionKeys.next();
  52. if(selectionKey.isAcceptable()){//如果事件是连接事件
  53. ServerSocketChannel serverChannel = (ServerSocketChannel)selectionKey.channel();//获取事件绑定的channel
  54. clientChannel = serverChannel.accept();//连接获取带客户端信息的socketChannel
  55. clientChannel.configureBlocking(false);//客户设置为非阻塞,因为非阻塞才支持选择器.避免盲等浪费资源
  56. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//作为每一个客户端的附件缓冲器
  57. /**
  58. * 只监听读事件,这里千万别监听写事件,因为只要连接有效,那么写事件会一直为true,导致死循环,很耗资源
  59. * 可以跟serverSocket用同一个选择器,因为绑定的channel不同
  60. */
  61. clientChannel.register(selector,SelectionKey.OP_READ,byteBuffer);
  62. }else if(selectionKey.isReadable()){//只要有客户端写入,那么就可以处理
  63. //获取客户端附件,也就是写入的数据
  64. ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
  65. //从selectionKey获取客户端的channel
  66. SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
  67. //把附件读出,解码为字符串
  68. String msg = read(socketChannel,byteBuffer);
  69. //这里用了->分割收件人,->后面跟着的字符串是收件人
  70. if(msg.indexOf("->")!=-1){
  71. //内容
  72. String content = msg.substring(0,msg.lastIndexOf("->"));
  73. //从map里获取收件人的socket
  74. targetChannel = clientSockets.get(msg.substring(msg.lastIndexOf("->")+2));
  75. //实例化一个缓冲区,用来写出到收件人的socketChannel
  76. ByteBuffer temp = ByteBuffer.allocate(1024);
  77. temp.put(charset.encode(content));
  78. //写出
  79. handleWrite(targetChannel,temp);
  80. }else{
  81. //如果内容没有收件人,那么视为第一次连接,客户端发过来的userName,作为KEY存入MAP
  82. clientSockets.put(msg,socketChannel);
  83. }
  84. }
  85. selectionKeys.remove();
  86. }
  87. }
  88. } catch (IOException e) {
  89. try {
  90. if(selectionKey!=null)selectionKey.cancel();
  91. if(clientChannel!=null){
  92. clientChannel.shutdownInput();
  93. clientChannel.shutdownOutput();
  94. clientChannel.close();
  95. }
  96. if(targetChannel!=null){
  97. targetChannel.shutdownInput();
  98. targetChannel.shutdownOutput();
  99. targetChannel.close();
  100. }
  101. } catch (IOException e1) {
  102. // TODO Auto-generated catch block
  103. e1.printStackTrace();
  104. }
  105. e.printStackTrace();
  106. }
  107.  
  108. }
  109.  
  110. private static String read(SocketChannel socketChannel,ByteBuffer byteBuffer){
  111. //重置position limit为写入做准备
  112. byteBuffer.clear();
  113. try {
  114. int flag =socketChannel.read(byteBuffer);
  115. //判断客户端是否断开连接
  116. if(flag==-1){
  117. //如果客户端无故断开,一定要关闭,否则读取事件一直为true造成死循环,非常耗资源
  118. socketChannel.close();
  119. }
  120. } catch (IOException e) {
  121. try {
  122. socketChannel.close();
  123. } catch (IOException e1) {
  124. e1.printStackTrace();
  125. }
  126. e.printStackTrace();
  127. }
  128. //position =0 limit等于有效下标,为写出做准备
  129. byteBuffer.flip();
  130. return charset.decode(byteBuffer).toString();
  131. }
  132.  
  133. //写出
  134. private static void handleWrite(SocketChannel socketChannel,ByteBuffer byteBuffer){
  135. synchronized (byteBuffer) {
  136. byteBuffer.flip();
  137. try {
  138. socketChannel.write(byteBuffer);
  139. } catch (IOException e) {
  140. try {
  141. socketChannel.close();
  142. } catch (IOException e1) {
  143. e1.printStackTrace();
  144. }
  145. e.printStackTrace();
  146. }
  147. }
  148. }
  149. }

客户端代码

  1. package socketchannel;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.net.InetSocketAddress;
  7. import java.net.SocketAddress;
  8. import java.nio.ByteBuffer;
  9. import java.nio.channels.ClosedChannelException;
  10. import java.nio.channels.SelectionKey;
  11. import java.nio.channels.Selector;
  12. import java.nio.channels.SocketChannel;
  13. import java.nio.charset.Charset;
  14. import java.util.Iterator;
  15.  
  16. /**
  17. * Created by lzw on 17-2-28.
  18. */
  19. public class SocketChannelNonBlockingClient {
  20. private static Charset charset = Charset.forName("GBK");
  21. private static ByteBuffer receiveBuffer = ByteBuffer.allocate(10240);
  22. private static ByteBuffer sendBuffer = ByteBuffer.allocate(10240);
  23. private static SocketChannel socketChannel = null;
  24. private static Selector selector = null;
  25. private static String userName = "client1";//客户端名
  26. private static String targetName = "client2";//收件人名
  27.  
  28. public static void main(String[] args) {
  29. try {
  30. socketChannel = SocketChannel.open();
  31. //连接到服务端
  32. SocketAddress socketAddress = new InetSocketAddress("19.95.103.112",8000);
  33. selector = Selector.open();//实例化一个选择器
  34. socketChannel.configureBlocking(false);//设置为非阻塞
  35. //先监听一个连接事件
  36. socketChannel.register(selector,SelectionKey.OP_CONNECT);
  37. //连接
  38. socketChannel.connect(socketAddress);
  39. //jdk 1.8的lambda表达式,用一个线程监控控制台输入
  40. new Thread(()->{
  41. try {
  42. receiveFromUser();
  43. } catch (IOException e) {
  44. e.printStackTrace();
  45. }
  46. }).start();
  47.  
  48. talk();
  49.  
  50. } catch (IOException e) {
  51. // TODO Auto-generated catch block
  52. e.printStackTrace();
  53. }
  54. }
  55.  
  56. private static void talk(){
  57. try {
  58. while(true){
  59. selector.select();//阻塞直到连接事件
  60. Iterator<SelectionKey> readyKeys = selector.selectedKeys().iterator();
  61. while(readyKeys.hasNext()){
  62. SelectionKey key =readyKeys.next();
  63. if(key.isConnectable()){
  64. //非阻塞的情况下可能没有连接完成,这里调用finishConnect阻塞至连接完成
  65. socketChannel.finishConnect();
  66. //连接完成以后,先发送自己的userName以便保存在服务端的客户端map里面
  67. synchronized (sendBuffer){
  68. SocketChannel socketChannel1 = (SocketChannel)key.channel();
  69. sendBuffer.clear();
  70. sendBuffer.put(charset.encode(userName));
  71. send(socketChannel1);
  72. socketChannel.register(selector,SelectionKey.OP_READ);//仅监听一个读取事件
  73. }
  74.  
  75. }else if(key.isReadable()){
  76. //处理读事件
  77. receive(key);
  78. }
  79. readyKeys.remove();
  80. }
  81. }
  82. } catch (ClosedChannelException e) {
  83. try {
  84. socketChannel.close();
  85. } catch (IOException e1) {
  86. e1.printStackTrace();
  87. }
  88. e.printStackTrace();
  89. } catch (IOException e) {
  90. // TODO Auto-generated catch block
  91. e.printStackTrace();
  92. }
  93.  
  94. }
  95.  
  96. /**
  97. * 从控制台获取用户输入
  98. * @throws IOException
  99. */
  100. private static void receiveFromUser() throws IOException{
  101. //阻塞直到控制台有输入
  102. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  103. for(String msg = br.readLine();msg!=null&&!msg.equals("bye");msg = br.readLine()){
  104. //同步锁避免线程竞争
  105. synchronized (sendBuffer) {
  106. sendBuffer.clear();
  107. //编码
  108. sendBuffer.put(charset.encode(msg));
  109. //分割副
  110. sendBuffer.put(charset.encode("->"));
  111. //目标名
  112. sendBuffer.put(charset.encode(targetName));
  113. send(socketChannel);
  114. }
  115. }
  116. }
  117. /**
  118. * 接收服务端的数据
  119. * @param key
  120. */
  121. private static void receive(SelectionKey key) throws IOException {
  122. //获取服务端的channel
  123. SocketChannel channel = (SocketChannel) key.channel();
  124. //为写入缓冲器做准备position=0,limit=capacity
  125. receiveBuffer.clear();
  126. //从服务端的channel把数据读入缓冲器
  127. channel.read(receiveBuffer);
  128. //position=0,limit=有效下标最后一位
  129. receiveBuffer.flip();
  130. //解码
  131. String msg = charset.decode(receiveBuffer).toString();
  132. //输出到控制台
  133. System.out.println(msg);
  134. }
  135.  
  136. /**
  137. * 发送到服务端
  138. */
  139. private static void send(SocketChannel sendChannel) throws IOException {
  140. if(sendBuffer.remaining()!=0){
  141. synchronized (sendBuffer){
  142. sendBuffer.flip();
  143. sendChannel.write(sendBuffer);
  144. }
  145. }
  146. }
  147. }

java socket 模拟im 即时通讯的更多相关文章

  1. 一款Java开源的Springboot即时通讯 IM,附源码

    # 开篇 电商平台最不能缺的就是即时通讯,例如通知类下发,客服聊天等.今天,就来给大家分享一个开源的即时通讯系统.如对文章不感兴趣可直接跳至文章末尾,有获取源码链接的方法. 但文章内容是需要你简单的过 ...

  2. Java Socket 模拟HTTP请求

    public static void main(String[] args) { try { String url = "192.168.1.103"; Socket socket ...

  3. TCP UDP Socket 即时通讯 API 示例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. java SSM框架 代码生成器 快速开发平台 websocket即时通讯 shiro redis

    A代码编辑器,在线模版编辑,仿开发工具编辑器,pdf在线预览,文件转换编码 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 , ...

  5. 通过python的socket库实现简易即时通讯小程序

    前言 最近学习了一下有关tcp协议和socket有关的知识,看到许多socket实战都喜欢教如何做一个聊天程序,于是想着试试能不能不看教程自己写一个.当然我没太多时间做一个像qq一样的ui界面,所以做 ...

  6. java socket通讯(二)处理多个客户端连接

    通过java socket通讯(一) 入门示例,就可以实现服务端和客户端的socket通讯,但是上一个例子只能实现一个服务端和一个客户端之间的通讯,如果有多个客户端连接服务端,则需要通过多线程技术来实 ...

  7. Socket网络通讯开发总结之:Java 与 C进行Socket通讯 + [备忘] Java和C之间的通讯

    Socket网络通讯开发总结之:Java 与 C进行Socket通讯 http://blog.sina.com.cn/s/blog_55934df80100i55l.html (2010-04-08 ...

  8. node.js和socket.io纯js实现的即时通讯实例分享

    在这个例子中,其实node.js并没有真正起到服务器的作用,因为我们这里可以直接运行client.html文件,而不用输入url请求,当 然,要想输入url请求页面内容还需要加入请求静态文件的代码.这 ...

  9. iOS开发之即时通讯之Socket(AsyncSocket)

    1.AsyncSocket介绍 如果需要在项目中像QQ微信一样做到即时通讯,必须使用socket通讯. iOS中Socket编程的方式: BSD Socket: BSD Socket 是UNIX系统中 ...

随机推荐

  1. js二维码插件总结

    jquery.qrcode.js生成二维码插件&转成图片格式 http://blog.csdn.net/u011127019/article/details/51226104

  2. awk说明书(转)

    ref:http://blog.chinaunix.net/uid-429659-id-122573.html awk使用手册 作者:awk使用手册什么是awk? 你可能对UNIX比较熟悉,但你可能对 ...

  3. 4.ES核心慨念

    一. 和lucene的关系 lucene是最先进,功能最强大的搜索库.但是使用复杂(要深入理解其中原理. elasticsearch,基于lucene,隐藏复杂性,提供简单易用的restful api ...

  4. 监听器应用【统计网站人数、自定义session扫描器、踢人小案例】

    从第一篇已经讲解过了监听器的基本概念,以及Servlet各种的监听器.这篇博文主要讲解的是监听器的应用. 统计网站在线人数 分析 我们在网站中一般使用Session来标识某用户是否登陆了,如果登陆了, ...

  5. 理解JavaScript中的作用域

     什么是变量,什么是作用域? 变量:简单来说就是在特定时间内保存特定值的一个名字而已,由于不存在定义某个变量必须要保存某种数据类型值的规则,所以变量的值及其数据类型可以在脚本生命周期内任意改变,变量可 ...

  6. dubbo refrence bean(服务引用)

    在xml上写一个dubbo标签就可以把远程的服务引用到本地使用: <dubbo:reference id="buyFoodService" interface="c ...

  7. JDBC学习笔记(四)

    减少各个Dao类间的重复代码,有以下几种方式: 写一个DBConnectionManager,将公共的查询逻辑做成方法,将sql语句作为参数传递给方法. public class DBConnecti ...

  8. SDN期末作业

    期末项目 代码仓库:传送门 视频:组长已经发送给朱老师 选题:负载均衡场景3 选题内容: 该拓扑是数据中心拓扑的一部分,其中h1是数据中心外的一台客户机,h2-h5是数据中心内的服务器,请根据该拓扑实 ...

  9. .net使用AsposeWord导出word table表格

    本文为原创,转载请注明出处 1.前言 .net平台下导出word文件还可以使用Microsoft.Office.Interop和NPOI,但是这两者都有缺点,微软的Office.Interop组件需要 ...

  10. Xcode的SVN提示"The request timed out."的解决方案

    问题描述 在利用Xcode的SourceControl进行SVN代码检出时,确认输入地址.帐号密码都正确的情况下,总是提示"The request timed out.".该问题的 ...