使用java和qt开发远程控制系统-TCP服务端开发

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());
        
        
    }

}
------------------------------------------------------------------------------------------------

测试结果:

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

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

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值