TCP服务器端负责交换控制端和被控制端的数据,采用多端口模式设计,但是目前测试只使用一个端口。

具体流程为

step1:服务器QServer启动tcp服务线程,等待客户端链接

step2:当有客户端链接进来,创建一个客户端对象,存储到静态map里面,key为客户端识别号

step3:客户端链接成功,首先必须发送识别码,获取识别码以后,去数据库查询有没有相关的链接对象,如果有,查询对方的识别号,吧客户端对象存储到全局map,如果没有,直接关闭链接。

step4:在获取数据线程里面,一旦获取到数据,查询对方的id在静态map里面有没有出现,如果出现,就把数据转发过去。

QServer类主要实现服务器端监听,停止,客户端对象创建,销毁等功能,具体代码:

***
  * 通讯功能实现
  * @author qujia
  *
  */
 public class QServer {
     private static final Logger LOG = LoggerFactory.getLogger(QServer.class);
     
     /**
      * 存储服务器端对象集合,考虑到多个端口情况。因此使用一个map存储,key为端口字符串
      */
     private static Map<String, QServer> servers=new HashMap<>();
     
     
     private List<QClient> clients=new ArrayList<QClient>();
     /**
      * 统一的获取服务器的方法
      * @return
      */
     public static QServer getInstance(int port) {
         String key=""+port;//变成字串
         if(servers.containsKey(key)) {
             //如果已经存在了
             return servers.get(key);
         }
         else {
             //创建新的服务器端
             QServer s=new QServer(port);
             servers.put(key, s);            
             return s;
         }
         
     }
     /**
      * 端口号
      */
     private Integer port;//端口
     private boolean isFinished;//服务器是否结束
     private ServerSocket svr;//server对象
         /**
      * 构造方法,不能直接访问,需要通过getInstance访问
      * @param port
      */
     QServer(int port)
     {
         
         this.port=port;
         isFinished=true;
         this.start();//开始
         
     }
     
     
     public Integer getPort() {
         return port;
     }
     public void setPort(Integer port) {
     
         this.port = port;
             
         
     }    public void removeClient(QClient c) {
         try {
             LOG.info("client off line "+c.getAddress());
             //clients.remove(c);
             c.remove();
             clients.remove(c);
         }
         catch (Exception e) {
             // TODO: handle exception
         }
         
     }
     
     
     
     /**
      * 开始
      */
     public void start()
     {
         
         if(!isFinished)return;//防止重复启动
         
         LOG.info("start svr on port"+this.port);
         
         isFinished = false;
         try {
             //创建服务器套接字,绑定到指定的端口
             svr = new ServerSocket(port);
             //等待客户端连接
             while (!isFinished) {
                 Socket socket = svr.accept();//接受连接
                 //创建线程处理连接
                 QClient c = new QClient(socket);
                 c.start();//开始接收
                 clients.add(c);//该服务器端的集合存一下
             }
         } catch (IOException e) {
             isFinished = true;
         }    }
     
     
          
     
     /**
      * 停止
      */
     public void stop()
     {
         isFinished = true;
         for (QClient c : clients) {
            c.remove();//停止
         }
         try {
             if (svr != null) {
                 svr.close();
                 svr = null;
             }
             
             clients.clear();
         } catch (IOException e) {
             e.printStackTrace();
         }    }
     /**
      * 重新开始
      */
     public void reStart()
     {
         stop();//先停止
         
         start();//开始
     }
     
     }
QClient主要实现客户端链接进来,会话判断,以及数据转发,主要代码:
/***
  * 客户端线程类
  * @author qujia
  *
  */
 public class QClient extends Thread {
     private static final Logger LOG = LoggerFactory.getLogger(QClient.class);
     
     /**
      * 存储客户端集合,key为客户端识别号
      */
     public static Map<String,QClient> clients=new HashMap();//
     /**
      * 查询会话使用,这个是不能用autowared,因为这个对象不是springcontenxt创建的
      */
     SessionService sessionSvr;
     
     /**
      * socket对象
      */
     private Socket socket;
     private InputStream in;
     private OutputStream out;
     byte[] buffer = new byte[1024];
     private String clientID;//客户端识别号
     private String targetID;//目标客户端识别号
    
     public  QClient(Socket socket) {
        LOG.info("clinet in "+socket.getInetAddress()+" port "+socket.getLocalPort());
        if(sessionSvr==null)sessionSvr=ContextTool.getBean(SessionService.class);//获取会话service
         this.socket = socket;
         try {
             in = socket.getInputStream();
             out = socket.getOutputStream();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
   
     
         @Override
     public void run() {
         LOG.info("Client start ");
         /**
          * 第一步等待客户端发来识别号
          */
         if(!isInterrupted() && socket.isConnected() ) {
                if (in == null) {
                    return;//直接返回
                }
              try {
                  int len=0;
                  while(len==0)len= in.available();//等待有数据
                  
                  LOG.info("Client data len= "+len);
                  if (len > 0) {      
                      
                      int size = in.read(buffer);//读取数据,第一次链接,就人为读取的是识别码
                      String id=new String(buffer,0,len);//转换为字符串
                      LOG.info("client id="+id);
                      this.clientID=id;//记录客户端id
                      
                      MySession sess=sessionSvr.getByClientId(id);//查询会话信息
                      if(sess==null) {
                          //没有会话
                         QServer.getInstance(socket.getLocalPort()).removeClient(this);//移除本链接
                           LOG.info("client no session"+id);
                         return;//直接结束
                          
                      }
                      else {
                          if(sess.getControlCode().equals(id)) {
                              this.targetID=sess.getControledCode();//对方是被控端
                            }
                          else {
                              this.targetID=sess.getControlCode();//对方是控制端
                          }
                          sess.setStatus(sess.getStatus()+1);
                          sessionSvr.update(sess);//更新链接
                           LOG.info("client target id="+this.targetID);
                      }
                      
                      
                      if(!clients.containsKey(id)) clients.put(id, this);//存储到集合
                     
                      
                  }
              }
              catch (Exception e) {
                 // TODO: handle exception
                  return;//直接返回,链接识别,需要重连
             }
             
             
         }
         
         /**
          * 其他数据直接转发
          */
         while (!isInterrupted() && socket.isConnected()  ) {
             
             
             
             if (in == null) {
                 break;
             }
             try {
                 int available = in.available();//有数据
                 if (available > 0) {                   
                     int size = in.read(buffer);
                     if (size > 0) {
                        //读取到了数据,直接转发给对应的客户端就行
                        if(clients.containsKey(this.targetID)){
                            //如果对方上线了
                            clients.get(this.targetID).sendData(buffer,size);//直接转发过去
                        }
                       
                     }
                 }
             } catch (IOException e) {
                 LOG.error(e.getLocalizedMessage());
                 e.printStackTrace();
                 break;
                 
             }
             
             try {
             Thread.sleep(50);
             }
             catch (Exception e) {
                 // TODO: handle exception
             }
         }
         
         this.remove();//清理资源
       
     }
     /**
      * 移除这个客户端
      */
     public void remove() {
         try {
             
             this.interrupt();//中断线程
             this.close();//关闭链接
             if(this.clientID!=null && clients.containsKey(this.clientID))
             clients.remove(this.clientID);//从集合移除
             
         }catch(Exception ex) {
             
         }
         
     }
     /**
      * 获取地址
      * @return
      */
     public String getAddress()
     {
         return socket.getInetAddress().toString();
     }
     /**
      * 发送数据方法
      * @param data 数据
      * @param len 长度
      */
     public void sendData(byte[] data,int len) {
         
         try {           
             
             out.write(data,0,len);//发送数据
             out.flush();
         }
         catch (Exception e) {
             // TODO: handle exception
             //e.printStackTrace();
             
         }
         
         
     }    void close() {
        try {
             if (in != null) {
                 in.close();
             }            if (out != null) {
                 out.close();
             }            if (socket != null) {
                 socket.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }StartListener类负责在程序启动的时候,完成一些默认的功能,或者设置
**
  * 实现启动完毕以后,马上启动一个服务器端
  * @author qujia
  *
  */
 @Component
 public class StartListener implements CommandLineRunner {    @Autowired
     ApplicationContext ctx;//获取spring容器
     
     
     public void run(String... args) throws Exception {
         // TODO Auto-generated method stub
         
         ContextTool.setContext(ctx);//存储到contextTool
         QServer.getInstance(7890);//启动默认的服务端口
         
         
     }}
ContextTool负责给非Spring创建的对象提供手动获取bean的功能,需要在程序启动的时候,设置Context
/***
  * 提供applicationcontext.
  * 类不是标准的bean,没法自动创建 
  * @author qujia
  *
  */
 public class ContextTool implements ApplicationContextAware, DisposableBean {
     private static ApplicationContext applicationContext =null;
     /**
      * 获取applicationContext
      */
     public static ApplicationContext getApplicationContext() {
         if (applicationContext == null) {
             throw new IllegalStateException("applicaitonContext属性未注入, 请在SpringBoot启动类中注册SpringContextHolder.");
         }else {
             return applicationContext;
         }
     }
     /**
      * 设备context
      * @param ctx
      */
     public static void setContext(ApplicationContext ctx) {
         applicationContext=ctx;
     }
     /**
      * 通过name获取 Bean
      * @param name 类名称
      * @return 实例对象
      */
     public static Object getBean(String name){
         return getApplicationContext().getBean(name);
     }    /**
      * 通过class获取Bean
      */
     public static <T> T getBean(Class<T> clazz){
         return getApplicationContext().getBean(clazz);
     }    /**
      * 通过name,以及Clazz返回指定的Bean
      */
     public static <T> T getBean(String name,Class<T> clazz){
         return getApplicationContext().getBean(name, clazz);
     }     @Override
     public void destroy() {
         applicationContext = null;
     }    @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         applicationContext=applicationContext;
         System.out.print(" context set "+applicationContext.getClass());
         
         
     }}


------------------------------------------------------------------------------------------------

测试结果:

java上编译QT java开发qt_java

 链接成功后,直接发送11111,由于没有这个会话,链接直接断开

java上编译QT java开发qt_客户端_02

 数据创建一个123到321的会话,两个客户端链接成功,并且可以相互发消息