基于管道的即时通信项目实现原理分析

网络 通信技术
sevice只需往管道中中发送数据,等到池中有数据了,它自动会找你。你不必要关心数据怎么发送与接收,只需要关注你业务的处理。

项目实现原理

sevice只需往管道中(数据池)中发送数据,等到池中有数据了,它自动会找你。你不必要关心数据怎么发送与接收,只需要关注你业务的处理。

如下图

基于管道的即时通信项目实现原理分析

优点:

基于管道的实现是消息的发送或接受只需要发送到管道或者从管道读取,而不用关注如何通过Channer发送,这样则实现了service层与socket的解耦。

依赖于广播而不依赖于回调函数,与nio的异步非阻塞,真正实现线程的零等待。

缺点:

发送的数据很难通过会掉函数实现(或者根本不能),只能通过广播实现。

相关类介绍

● ClientMessagePool,ServiceMessagePool管道(数据池)

内部实现原理是一个链表队列,数据的增加读取对应队列中的压入队列,读取队列头元素

● Sevice

业务逻辑处理类,必须实现IMessageSevice接口,并向MessageObserver注册

● MessageObserver

内部有一个IMessageSevice的链表,保存各个实现IMessageSevice接口的Service,与Sevice 构成观察者模式,

会有一个线程专门监测MessagePool,一旦有数据,就交给MessageObserver。MessageObserver根据特定消息类推送给制定的service.

● SocketChannel

实现了一个SockenChannel的类,相当于一个客户端。从管道中(ClientMessagePool)中读取数据,一旦有数据,则将数据写入管道

● Selector

接收管道的注册,并根纷发条件,向指定的SocketChannel推动数据。也会根据过滤条件过滤数据。

代码实现

管道代码实现

  1. package com.pool;   
  2.    
  3. import java.util.Queue;   
  4. import java.util.concurrent.LinkedBlockingQueue;   
  5.    
  6. public class MessagePool {   
  7.     public static Queue<String> clintmessageQueue = new LinkedBlockingQueue<String>();    
  8.     public static Queue<String> serverMessageQueue = new LinkedBlockingQueue<String>();    
  9.        
  10. }   

接口

  1. package com.pool;   
  2.    
  3. public interface IMessagePool {   
  4.    
  5.     public void addMessage(String message);   
  6.    
  7.     public String pollMessage();   
  8.        
  9.     public boolean isEmpty();   
  10.    
  11. }  

实现类

  1. package com.pool.impl;   
  2.    
  3. import com.pool.IMessagePool;   
  4. import com.pool.MessagePool;   
  5.    
  6. public class ClientMessagePool implements IMessagePool {   
  7.     @Override   
  8.     public  void addMessage(String message) {   
  9.         MessagePool.clintmessageQueue.add(message);   
  10.     }   
  11.     @Override   
  12.     public  String pollMessage() {   
  13.         return MessagePool.clintmessageQueue.poll();   
  14.     }   
  15.        
  16.     @Override   
  17.     public boolean isEmpty() {   
  18.         if(MessagePool.clintmessageQueue.size()>0)   
  19.             return true;   
  20.         else   
  21.             return false;   
  22.     }   
  23. }   

#p#客户端

  1. package com.socket;   
  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.SocketChannel;   
  9. import java.util.Iterator;   
  10.    
  11. import org.apache.commons.lang.ArrayUtils;   
  12.    
  13. import com.pool.IMessagePool;   
  14. import com.pool.impl.ClientMessagePool;   
  15. import com.util.PackageUtil;   
  16.    
  17. public class MySocket {   
  18.     private SocketChannel mSocketChannel;   
  19.    
  20.     private SelectionKey key;   
  21.    
  22.     public static String CHARSET = "utf-8";   
  23.    
  24.     public static String ADDRESS = "127.0.0.1";   
  25.    
  26.     public static int HOST = 34521;   
  27.    
  28.     protected Selector mSelector;   
  29.    
  30.     protected IMessagePool messagePool = new ClientMessagePool();;   
  31.    
  32.     ByteBuffer buffer;   
  33.    
  34.     public MySocket() {   
  35.         try {   
  36.             mSelector = Selector.open();   
  37.             initSocketChannel();   
  38.             initBassiness();   
  39.         } catch (Exception e) {   
  40.             e.printStackTrace();   
  41.         } finally {   
  42.             try {   
  43.                 key.channel().close();   
  44.             } catch (IOException e) {   
  45.                 e.printStackTrace();   
  46.             }   
  47.         }   
  48.    
  49.     }   
  50.    
  51.     /**  
  52.      * 业务逻辑  
  53.      *   
  54.      * @throws Exception  
  55.      */   
  56.     private void initBassiness() throws Exception {   
  57.         while (true) {   
  58.             checkWriteable();   
  59.             // 瞬时检测   
  60.             if (mSelector.select(100) > 0) {   
  61.                 Iterator<SelectionKey> keys = mSelector.selectedKeys()   
  62.                         .iterator();   
  63.                 while (keys.hasNext()) {   
  64.                     SelectionKey key = keys.next();   
  65.                     if (key.isReadable()) {   
  66.                         dispose4Readable(key);   
  67.                     }   
  68.                     if (key.isValid() && key.isWritable()) {   
  69.                         dispose4Writable(key);   
  70.                     }   
  71.                     keys.remove();   
  72.                 }   
  73.             }   
  74.         }   
  75.     }   
  76.    
  77.     /**  
  78.      * 可读请求  
  79.      *   
  80.      * @param key  
  81.      * @throws Exception  
  82.      */   
  83.     protected void dispose4Readable(SelectionKey key) throws Exception {   
  84.         SocketChannel mSocketChannel = ((SocketChannel) key.channel());   
  85.         buffer = ByteBuffer.allocate(1024);   
  86.         mSocketChannel.read(buffer);   
  87.         buffer.flip();   
  88.         this.unPacket(buffer.array(), key);   
  89.     }   
  90.    
  91.     /**  
  92.      * 可写请求  
  93.      *   
  94.      * @param key  
  95.      * @throws Exception  
  96.      */   
  97.     protected void dispose4Writable(SelectionKey key) throws Exception {   
  98.    
  99.         SocketChannel mSocketChannel = ((SocketChannel) key.channel());   
  100.         int value = 0;   
  101.         do{   
  102.             value = mSocketChannel.write(buffer);   
  103.         }while(value!=0);   
  104.         key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);   
  105.         key.interestOps(SelectionKey.OP_READ);   
  106.     }   
  107.    
  108.     /**  
  109.      * 解包  
  110.      *   
  111.      * @param buf  
  112.      * @return  
  113.      */   
  114.     public byte[] unPacket(byte[] buf, SelectionKey key) {   
  115.    
  116.         int len = buf.length;// 37   
  117.         int i;   
  118.         for (i = 0; i < len; i++) {   
  119.             if (len < i + PackageUtil.PACKAGEHEADERLENGTH   
  120.                     + PackageUtil.PACKAGESAVEDATALENGTH) {   
  121.                 break;   
  122.             }   
  123.             String tmp = new String(ArrayUtils.subarray(buf, i, i   
  124.                     + PackageUtil.PACKAGEHEADERLENGTH));   
  125.             if (tmp.equals(PackageUtil.PACKAGEHEADER)) {   
  126.                 int messageLength = PackageUtil.byte2Int(ArrayUtils.subarray(   
  127.                         buf, i + PackageUtil.PACKAGEHEADERLENGTH, i   
  128.                                 + PackageUtil.PACKAGEHEADERLENGTH   
  129.                                 + PackageUtil.PACKAGESAVEDATALENGTH));   
  130.    
  131.                 if (len < i + PackageUtil.PACKAGEHEADERLENGTH   
  132.                         + PackageUtil.PACKAGESAVEDATALENGTH + messageLength) {   
  133.                     break;   
  134.                 }   
  135.                 byte[] data = ArrayUtils.subarray(buf, i   
  136.                         + PackageUtil.PACKAGEHEADERLENGTH   
  137.                         + PackageUtil.PACKAGESAVEDATALENGTH, i   
  138.                         + PackageUtil.PACKAGEHEADERLENGTH   
  139.                         + PackageUtil.PACKAGESAVEDATALENGTH + messageLength);   
  140.                 String message = new String(data);   
  141.                 System.out.println(message);   
  142. //              Filter.filterRead(message, key, messagePool);   
  143.                 i += PackageUtil.PACKAGEHEADERLENGTH   
  144.                         + PackageUtil.PACKAGESAVEDATALENGTH + messageLength - 1;   
  145.             }   
  146.         }   
  147.         if (i == len) {   
  148.             return new byte[0];   
  149.         }   
  150.         return ArrayUtils.subarray(buf, i, buf.length);   
  151.     }   
  152.    
  153.     void initSocketChannel() throws Exception {   
  154.         mSocketChannel = SocketChannel.open();   
  155.         mSocketChannel.connect(new InetSocketAddress(ADDRESS, HOST));   
  156.         mSocketChannel.configureBlocking(false);   
  157.         key = mSocketChannel.register(mSelector, SelectionKey.OP_CONNECT|SelectionKey.OP_READ);   
  158.     }   
  159.    
  160.     void checkWriteable() {   
  161.         if (messagePool.isEmpty()) {   
  162.             String values = messagePool.pollMessage();   
  163.             System.out.println("                                   "+values);   
  164.             buffer = ByteBuffer.wrap(PackageUtil.packet(values.getBytes()));   
  165.             key.interestOps(SelectionKey.OP_WRITE);   
  166.         }   
  167.     }   
  168. }   

#p#服务器

  1. package com.socket;   
  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.util.Iterator;   
  11. import java.util.Set;   
  12.    
  13. import org.apache.commons.lang.ArrayUtils;   
  14.    
  15. import com.filter.Filter;   
  16. import com.pool.IMessagePool;   
  17. import com.pool.impl.ServerMessagePoll;   
  18. import com.util.PackageUtil;   
  19.    
  20. public class MyServerSocket {   
  21.    
  22.     private ServerSocketChannel mServerSocketChannel;   
  23.    
  24.     private static MyServerSocket serverSocket;   
  25.    
  26.     public static String CHARSET = "utf-8";   
  27.    
  28.     public static String ADDRESS = "127.0.0.1";   
  29.    
  30.     public static int HOST = 34521;   
  31.    
  32.     protected Selector mSelector;   
  33.    
  34.     protected IMessagePool messagePool = new ServerMessagePoll();;   
  35.    
  36.     ByteBuffer buffer;   
  37.    
  38.     private MyServerSocket() throws Exception {   
  39.         try {   
  40.             mSelector = Selector.open();   
  41.             initSocketChannel();   
  42.             initBassiness();   
  43.         } catch (Exception e) {   
  44.             e.printStackTrace();   
  45.         } finally {   
  46.             Set<SelectionKey> keys = mSelector.keys();   
  47.             {   
  48.                 for (SelectionKey key : keys) {   
  49.                     try {   
  50.                         key.channel().close();   
  51.                     } catch (IOException e) {   
  52.                         e.printStackTrace();   
  53.                         continue;   
  54.                     }   
  55.                 }   
  56.             }   
  57.         }   
  58.    
  59.     }   
  60.    
  61.     /**  
  62.      * 业务逻辑  
  63.      *   
  64.      * @throws Exception  
  65.      */   
  66.     private void initBassiness() throws Exception {   
  67.         while (true) {   
  68.             checkWriteable();   
  69.             // 瞬时检测   
  70.             if (mSelector.select() > 0) {   
  71.                 Iterator<SelectionKey> keys = mSelector.selectedKeys()   
  72.                         .iterator();   
  73.                 while (keys.hasNext()) {   
  74.                     SelectionKey key = keys.next();   
  75.                     if (key.isAcceptable()) {   
  76.                         dispose4Acceptable(key);   
  77.                     }   
  78.                     if (key.isReadable()) {   
  79.                         dispose4Readable(key);   
  80.                     }   
  81.                     if (key.isValid() && key.isWritable()) {   
  82.                         dispose4Writable(key);   
  83.                     }   
  84.    
  85.                     keys.remove();   
  86.                 }   
  87.             }   
  88.         }   
  89.     }   
  90.     /**  
  91.      * 响应读  
  92.      * @param key  
  93.      * @throws Exception  
  94.      */   
  95.     protected void dispose4Readable(SelectionKey key) throws Exception {   
  96.         SocketChannel mSocketChannel = ((SocketChannel) key.channel());   
  97.         buffer = ByteBuffer.allocate(1024);   
  98.         mSocketChannel.read(buffer);   
  99.         buffer.flip();   
  100.         this.unPacket(buffer.array(), key);   
  101.     }   
  102.    
  103.     /**  
  104.      * 可写请求  
  105.      *   
  106.      * @param key  
  107.      * @throws Exception  
  108.      */   
  109.     protected void dispose4Writable(SelectionKey key) throws Exception {   
  110.    
  111.         SocketChannel mSocketChannel = ((SocketChannel) key.channel());   
  112.         if(mSocketChannel.write(buffer)!=-1){   
  113.             buffer.clear();   
  114.         }   
  115.         key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);   
  116. //      key.interestOps(SelectionKey.OP_READ);   
  117.     }   
  118.     /**  
  119.      * 解包  
  120.      *   
  121.      * @param buf  
  122.      * @return  
  123.      */   
  124.     private byte[] unPacket(byte[] buf, SelectionKey key) {   
  125.    
  126.         int len = buf.length;// 37   
  127.         int i;   
  128.         for (i = 0; i < len; i++) {   
  129.             if (len < i + PackageUtil.PACKAGEHEADERLENGTH   
  130.                     + PackageUtil.PACKAGESAVEDATALENGTH) {   
  131.                 break;   
  132.             }   
  133.             String tmp = new String(ArrayUtils.subarray(buf, i, i   
  134.                     + PackageUtil.PACKAGEHEADERLENGTH));   
  135.             if (tmp.equals(PackageUtil.PACKAGEHEADER)) {   
  136.                 int messageLength = PackageUtil.byte2Int(ArrayUtils.subarray(   
  137.                         buf, i + PackageUtil.PACKAGEHEADERLENGTH, i   
  138.                                 + PackageUtil.PACKAGEHEADERLENGTH   
  139.                                 + PackageUtil.PACKAGESAVEDATALENGTH));   
  140.    
  141.                 if (len < i + PackageUtil.PACKAGEHEADERLENGTH   
  142.                         + PackageUtil.PACKAGESAVEDATALENGTH + messageLength) {   
  143.                     break;   
  144.                 }   
  145.                 byte[] data = ArrayUtils.subarray(buf, i   
  146.                         + PackageUtil.PACKAGEHEADERLENGTH   
  147.                         + PackageUtil.PACKAGESAVEDATALENGTH, i   
  148.                         + PackageUtil.PACKAGEHEADERLENGTH   
  149.                         + PackageUtil.PACKAGESAVEDATALENGTH + messageLength);   
  150.                 String message = new String(data);   
  151.                 System.out.println("server read message" + message);   
  152.                 Filter.filterRead(message, key, messagePool);   
  153.                 i += PackageUtil.PACKAGEHEADERLENGTH   
  154.                         + PackageUtil.PACKAGESAVEDATALENGTH + messageLength - 1;   
  155.             }   
  156.         }   
  157.         if (i == len) {   
  158.             return new byte[0];   
  159.         }   
  160.         return ArrayUtils.subarray(buf, i, buf.length);   
  161.     }   
  162.    
  163.     public static MyServerSocket newInstence() throws Exception {   
  164.    
  165.         if (serverSocket == null) {   
  166.             return new MyServerSocket();   
  167.         }   
  168.         return serverSocket;   
  169.     }   
  170.     /**  
  171.      * SocketChannel初始化  
  172.      * @throws Exception  
  173.      */   
  174.     void initSocketChannel() throws Exception {   
  175.         mServerSocketChannel = ServerSocketChannel.open();   
  176.         mServerSocketChannel.configureBlocking(false);   
  177.         mServerSocketChannel.bind(new InetSocketAddress(ADDRESS, HOST));   
  178.         mServerSocketChannel.register(mSelector, SelectionKey.OP_ACCEPT);   
  179.     }   
  180.    
  181.     void dispose4Acceptable(SelectionKey key) throws Exception {   
  182.         SocketChannel mSocketChannel = ((ServerSocketChannel) key.channel())   
  183.                 .accept();   
  184.         mSocketChannel.configureBlocking(false);   
  185.         mSocketChannel.register(mSelector, SelectionKey.OP_READ);   
  186.     }   
  187.     void checkWriteable() {   
  188.         if (messagePool.isEmpty()) {   
  189.             String value = messagePool.pollMessage();   
  190.             String result = Filter.filterWrite(value, mSelector);   
  191.             if (result != null) {   
  192.                 System.out.println("server:" + result);   
  193.                 buffer = ByteBuffer.wrap(PackageUtil.packet(result.getBytes()));   
  194.             }   
  195.         }   
  196.     }   
  197.    
  198. }   

#p#过滤器

  1. package com.filter;   
  2.    
  3. import java.nio.channels.SelectionKey;   
  4. import java.nio.channels.Selector;   
  5. import java.util.Set;   
  6.    
  7. import com.model.BaseModule;   
  8. import com.model.Chat;   
  9. import com.model.User;   
  10. import com.pool.IMessagePool;   
  11. import com.util.StringUtil;   
  12.    
  13. public class Filter {   
  14.     private static final String LOGIN = "login";   
  15.     private static BaseModule modul = new BaseModule();   
  16.        
  17.     private static SelectionKey selectionKey=null;   
  18.        
  19.     private static Selector selector = null;   
  20.        
  21.     /**  
  22.      * TODO 线程启动  
  23.      *   
  24.      * @param message  
  25.      * @return  
  26.      */   
  27.     public static void filterRead(String message, SelectionKey key,   
  28.             IMessagePool messagePool) {   
  29.         selectionKey = key;   
  30.         try {   
  31.                
  32.             BaseModule filterModul = (BaseModule) StringUtil.string2Bean(modul,   
  33.                     message);   
  34.             if(filterType(filterModul.getType())){   
  35.                 if(filterValue(filterModul.getMessage())){   
  36. //                  messagePool.addMessage(message);   
  37.                 }else{   
  38.                        
  39.                 }   
  40.             }else{   
  41.                 messagePool.addMessage(message);   
  42.             }   
  43.            
  44.         } catch (Exception e) {   
  45.             return;   
  46.         }   
  47.     }   
  48.        
  49.     public static String filterWrite(String message,Selector mSelector){   
  50.         selector = mSelector;   
  51.         return filter(message);   
  52.            
  53.     }   
  54.     private static String filter(String message){   
  55.         BaseModule filterModul = (BaseModule) StringUtil.string2Bean(modul,   
  56.                 message);   
  57.         Chat chat = (Chat) StringUtil.string2Bean(new Chat(),   
  58.                 filterModul.getMessage());   
  59.         Set<SelectionKey> keys=selector.keys();   
  60.         for(SelectionKey key:keys){   
  61.             String  markString=key.attachment()!=null?key.attachment().toString():null;   
  62.             if(markString!=null && markString.equals(chat.getTo())){   
  63.                 key.interestOps(SelectionKey.OP_WRITE);   
  64.                 return chat.getMessage();   
  65.             }   
  66.         }   
  67.         return null;   
  68.     }   
  69.     /**  
  70.      * 过滤类型  
  71.      * @param value  
  72.      * @return  
  73.      */   
  74.     private static boolean filterType(String value) {   
  75.         if (LOGIN.equals(value)) {   
  76.             return true;   
  77.         }   
  78.         return false;   
  79.     }   
  80.     /**  
  81.      * 过滤内容  
  82.      * @param value  
  83.      * @return  
  84.      */   
  85.     private static boolean filterValue(String value) {   
  86.         return filterLogin(value);   
  87.     }   
  88.    
  89.     private static boolean filterLogin(String value) {   
  90.         User user = (User) StringUtil.string2Bean(new User(), value);   
  91.         if (user.getUserName() != null) {   
  92.             selectionKey.attach(user.getUserName());   
  93.             return true;   
  94.         }   
  95.         return false;   
  96.     }   
  97.    
  98. }   

service接口

  1. package com.service;   
  2.    
  3. public interface IMessageService {   
  4.        
  5.     void doMessage(String message);   
  6.    
  7. }   

#p#util

  1. package com.util;   
  2.    
  3.    
  4. import java.io.UnsupportedEncodingException;   
  5.    
  6. import org.apache.commons.lang.ArrayUtils;   
  7.    
  8. public class PackageUtil {   
  9.        
  10.     public static final String PACKAGEHEADER = "↨-↨";//消息长度   
  11.     public static final int PACKAGEHEADERLENGTH = 7;  //数据头长�?   
  12.     public static final int PACKAGESAVEDATALENGTH = 4; //数据长度站的位数   
  13.        
  14.     /**  
  15.      * 打包  
  16.      * @param pkg 要打包的字节数组  
  17.      * @return  
  18.      */   
  19.     public static byte[] packet(byte[] pkg) {   
  20.    
  21.         int intValue = pkg.length;   
  22.         byte[] b = new byte[4];   
  23.         for (int i = 0; i < 4; i++) {   
  24.             b[i] = (byte) (intValue >> 8 * (3 - i) & 0xFF);   
  25.             // System.out.print(Integer.toBinaryString(b[i])+" ");   
  26.             //System.out.println((b[i] & 0xFF) + " ");   
  27.         }   
  28.         try {   
  29.             byte[] newPkg = ArrayUtils.addAll(PackageUtil.PACKAGEHEADER.getBytes("utf-8"), b);   
  30.             newPkg = ArrayUtils.addAll(newPkg, pkg);   
  31.             return newPkg;   
  32.         } catch (UnsupportedEncodingException e) {   
  33.             e.printStackTrace();   
  34.         }   
  35.            
  36.         return null;   
  37.     }   
  38.        
  39.     /**  
  40.      * 字节数组转整形  
  41.      * @param b  
  42.      * @return  
  43.      */   
  44.     public static int byte2Int(byte[] b) {   
  45.         int intValue = 0;   
  46.         for (int i = 0; i < b.length; i++) {   
  47.             intValue += (b[i] & 0xFF) << (8 * (3 - i));   
  48.             // System.out.print(Integer.toBinaryString(intValue)+" ");   
  49.         }   
  50.         return intValue;   
  51.     }   
  52.        
  53.     /**  
  54.      * @param args  
  55.      */   
  56.     public static void main(String[] args) {   
  57.         // TODO Auto-generated method stub   
  58.     }   
  59. }   

StringUtil

  1. package com.util;   
  2.    
  3. import com.google.gson.Gson;   
  4.    
  5. public class StringUtil {   
  6.        
  7.     private static Gson json =new Gson();   
  8.     /**  
  9.      * 将字符串专为json  
  10.      * @param clazz  
  11.      * @param message  
  12.      * @return  
  13.      */   
  14.     public static Object string2Bean(Object clazz,String message){   
  15.         return json.fromJson(message, clazz.getClass());   
  16.     }   
  17.     /**  
  18.      * 将json专为字符串  
  19.      * @param clazz  
  20.      * @return  
  21.      */   
  22.     public static String bean2Json(Object clazz){   
  23.         return json.toJson(clazz);   
  24.     }   
  25.    
  26. }   

module

  1. package com.model;   
  2. /**  
  3.  * 默认包装  
  4.  * @author Administrator  
  5.  *  
  6.  */   
  7. public class BaseModule {   
  8.        
  9.     String type ;   
  10.        
  11.     String message;   
  12.    
  13.     public String getType() {   
  14.         return type;   
  15.     }   
  16.    
  17.     public void setType(String type) {   
  18.         this.type = type;   
  19.     }   
  20.    
  21.     public String getMessage() {   
  22.         return message;   
  23.     }   
  24.    
  25.     public void setMessage(String message) {   
  26.         this.message = message;   
  27.     }   
  28.    
  29. }   

这个为原型。后面会对具体细节进行实现以及原理进行讲解

责任编辑:林琳 来源: CSDN博客
相关推荐

2010-04-20 09:07:13

Unix操作系统

2009-02-26 16:40:49

企业 通信

2016-09-28 09:48:40

网易云信IM云服务

2014-12-23 13:47:25

2010-05-20 17:45:46

OCS 2007 R2

2011-10-20 22:25:49

网易即时通

2010-05-10 17:43:07

2023-10-30 17:48:30

架构设计通信

2016-10-11 13:58:03

2012-09-25 14:06:28

C#网络协议

2018-01-15 09:32:59

即时通信服务器架构

2014-11-14 11:07:48

支付即时通信Facebook

2014-11-26 17:56:44

BQ企业即时通

2011-07-19 09:18:53

2009-04-13 15:02:10

通信

2011-09-27 10:29:50

通信展通讯中国电信

2021-09-30 10:45:33

Linux进程通信

2023-07-23 08:35:13

Web网络

2021-08-14 09:23:03

即时通讯IM互联网

2011-06-30 10:50:24

即时通讯
点赞
收藏

51CTO技术栈公众号