物联网Java服务端TCP通讯

主要是自己记录一下,刚开始学习这方面知识。对TCP通信理解的并不是特别透彻,只能通过代码一步一步深入:

本文主要功能是,传感器设备(包括可控制类电机)采集信息,以及发送指令,包括回传等功能。

废话不多说,老规矩,直接上代码:

package me.control;

import com.google.gson.JsonSyntaxException;
import me.control.bean.ChannelBean;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;

@Component
public class ControlServer implements ApplicationRunner {
    public static ControlServer controlServer;


//单例
    public static ControlServer getInstence() {
        if (controlServer == null) {
            controlServer = new ControlServer();
        }
        return controlServer;
    }

    private Selector selector = null;
    static final int port = 8888;
    private Charset charset = Charset.forName("UTF-8");
    private int bufferSize = 4096; //注意区块的大小

    //记录连接对象的容器(里面包含了该连接的全部信息)
    private List<ChannelBean> list = new ArrayList<>();


    public void init() throws IOException {
        selector = Selector.open();
        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(port));
        //非阻塞的方式
        server.configureBlocking(false);
        //注册到选择器上,设置为监听状态
        server.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("等待连接。。。");

        while (true) {
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            Set selectedKeys = selector.selectedKeys();  //获取所有链接
            Iterator keyIterator = selectedKeys.iterator();

            while (keyIterator.hasNext()) {
                SelectionKey sk = (SelectionKey) keyIterator.next();
                keyIterator.remove();

                dealWithSelectionKey(server, sk);//处理一个连接

            }
        }
    }

   
    public boolean dealWithSelectionKey(ServerSocketChannel server, SelectionKey sk) throws IOException {

        if (sk.isAcceptable()) {
            SocketChannel sc = server.accept();
            //非阻塞模式
            sc.configureBlocking(false);
            //注册选择器,并设置为读取模式,收到一个连接请求,然后起一个SocketChannel,并注册到selector上,之后这个连接的数据,就由这个SocketChannel处理
            if (sc == null) {
                return false;
            }
            sc.register(selector, SelectionKey.OP_READ);
            //将此对应的channel设置为准备接受其他客户端请求,加入一个新的客户端
            sk.interestOps(SelectionKey.OP_ACCEPT);
            System.out.println("time:" + formatTime.format(new Date()) + ", Server is accepted from a new client :" + sc.getRemoteAddress().toString().substring(1));

        }

        //处理来自客户端的数据读取请求
        if (sk.isReadable()) {
            //返回该SelectionKey对应的 Channel,其中有数据需要读取,则读取它送过来的数据
            SocketChannel sc = (SocketChannel) sk.channel();
            //获取数据
            ByteBuffer buff = ByteBuffer.allocate(bufferSize);
            String content = null;
            String s16 = null;
            try {

                while (sc.read(buff) > 0) {
                    buff.flip();
//客户端发送过来的数据有可能是指令,也有可能是回传,但统一都是16进制数据
//此处是把收到的信息 buff--》string
                    content = DataUtil.decodeKey(buff);
//此处是把收到的信息 buff--》byte[](类似于 “01 02 03 04 05 06”)--》16进制数据
                    s16 = DataUtil.BinaryToHexString(DataUtil.decodeValue(buff)).trim();
                }
               
                if (sc.read(buff) == -1) {
                    System.out.println(sc.socket().getRemoteSocketAddress() + "断开连接");
                    sc.close();
                    return false;
                }
                sk.interestOps(SelectionKey.OP_READ);//改为接受数据状态
            } catch (IOException io) {
                sk.cancel();
                System.out.println("read or write error " + io);
                if (sk.channel() != null) {
                    sk.channel().close();
                    //下线通知,更新这里,并更新数据库
                    this.clientDisconnect(sk);
                    return false;
                }
            }
            
            System.out.println("接收到数据:" + content + "  " + formatTime.format(new Date()) + "长度:" + content.length());
            System.out.println("接收到16进制数据为:" + s16 + "长度:" + s16.length());
            if (s16.length() == 5) {//注册(包括心跳包也是这个编号),这里因为我的所有设备都设置编号为长度5
                boolean isContant = false;
                for (ChannelBean channelBean : list) {
                    if (channelBean.getId().equals(s16)) {
                        System.out.println("已查到库中包含该设备,改变连接状态为true");
                        channelBean.setConnect(true);
                        channelBean.setSocketChannel(sc);
                        isContant = true;
                    }
                }
                if (!isContant) {
                    System.out.println("已查到库中不包含该设备,添加设备到库中并设置连接状态为true");
                    ChannelBean channelBean = new ChannelBean();

                    channelBean.setId(s16);
                    channelBean.setName("dtu");
                    channelBean.setConnect(true);
                    channelBean.setSocketChannel(sc);
                    list.add(channelBean);
                    System.out.println("库中包含" + list.size() + "个设备");
                }
            }else {//非注册信息(包括发送与回传)发送信息不在此处处理,单独处理;这里只负责回传(16进制很方便,因为数据的长度是固定的)
//此处根据通道的id来判断是哪个设备回传的信息
//根据信息长度,过滤错误信息 ,然后把得到的信息转为String并解析保存到该通道的容器中
                for (ChannelBean channelBean:list){
                    if (channelBean.getSocketChannel().equals(sc)){
                        if (channelBean.getId().equals("00 01")){//泵站

                            if (s16.length()==62){
                                System.out.println(channelBean.getId()+"收到查询信息:"+ s16);
                                channelBean.setMsg(HexToBeanUtil.getBZInfo(s16));
                            }
                        }else if(channelBean.getId().equals("00 55")){//泵站水位计
                            if (s16.length()==134){
                                System.out.println(channelBean.getId()+"收到查询信息:"+ s16);
                                channelBean.setMsg(transformBZFluviograph(s16)+"cm");
                            }

                        }else if (channelBean.getId().equals("00 54")){//田间水位计
                            if (s16.length()==23){
                                System.out.println(channelBean.getId()+"收到查询信息:"+ s16);
                                channelBean.setMsg(transformTJFluviograph(s16)+"mm");
                            }

                        }else  {//闸门开度
                            if (s16.length()==14){
                                System.out.println(channelBean.getId()+"收到查询信息:"+ s16);
                                channelBean.setMsg(HexToBeanUtil.getZMOpen(s16));
                            }
                        }

                    }
                }
            }
        }
        return true;
    }
/**
     * 泵站水位计
     * @param hex
     * @return
     */
    public String transformBZFluviograph(String hex){
        int str = Integer.parseInt(hex.substring(9,11)+hex.substring(6,8),16);
        return str+"";
    }

    /**
     * 田间水位计
     * @param hex
     * @return
     */
    public String transformTJFluviograph(String hex){
        int str = Integer.parseInt(hex.substring(15,17)+hex.substring(12,14),16);
        return str+"";
    }

    /**
     * 下线处理的过程
     * <p>
     * 1、socket断开,channel断开
     * 2、userlist表除名,如果可能,给互联用户下线通知 下线的逻辑为广播一下,然后让其他人做对比......
     * 3、数据库进行更新
     * 4、日志记录
     **/
    private void clientDisconnect(SelectionKey sk) {
        for (ChannelBean channelBean : list) {
            if (channelBean.getSocketChannel().equals(sk)) {
                channelBean.setConnect(false);
            }
        }
        //TODO 设计下线格式
        try {
            this.broadCastInfo(selector, (SocketChannel) sk.channel(), "下线");
            sk.channel().close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //TODO 数据库更新
        //这里对数据库的操作以后再做......,即下线的影响,以后再处理......
        //TODO 日志记录
    }
//发送信息处理,外部调用 id是设备编号  info是信息内容

    public String sendToClient(String id, String info) throws IOException {
        for (ChannelBean channelBean : list) {
            if (channelBean.getId().equals(id)) {
                if (channelBean.isConnect()) {
                    System.out.println("发送信息:"+info);
//把数据转为16进制发送
                  channelBean.getSocketChannel().write(DataUtil.encodeValue(DataUtil.hexStrToBinaryStr(info)));
                    return "send success";
                } else {
                    return "this device has disconnect";
                }
            }
        }
        return "this device has no connect";
    }

//给所有设备发送信息
    public void broadCastInfo(Selector selector, SocketChannel selfChannel, String info) throws IOException {
        //广播数据到所有的SocketChannel中
        for (SelectionKey key : selector.keys()) {
            Channel targetchannel = key.channel();
            //如果except不为空,不回发给发送此内容的客户端
            if (targetchannel instanceof SocketChannel) {
                SocketChannel dest = (SocketChannel) targetchannel;
                dest.write(DataUtil.encodeValue(DataUtil.hexStrToBinaryStr(info)));
            }
        }
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {

        ControlServer controlServer = ControlServer.getInstence();
        controlServer.init();
    }
}
package me.control.bean;

import java.nio.channels.SocketChannel;

//通道集合
public class ChannelBean {
    private String id;//设备编号
    private String name;//设备名称
    private SocketChannel socketChannel;//通信通道
    private Object msg;//最近的返回数据
    private boolean isConnect;//是否连接

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public SocketChannel getSocketChannel() {
        return socketChannel;
    }

    public void setSocketChannel(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }

    public boolean isConnect() {
        return isConnect;
    }

    public void setConnect(boolean connect) {
        isConnect = connect;
    }

    public Object getMsg() {
        return msg;
    }

    public void setMsg(Object msg) {
        this.msg = msg;
    }
}

这基本解决了服务器与设备之间的通信,比较粗糙,望指点,下一篇把用到的一些工具转换函数补上。

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,您想了解关于Java多线程TCP通讯物联网服务端的应用吗? 首先,TCP通讯是指通过TCP协议进行的网络通讯。在物联网服务端,我们可以使用Java的Socket类和ServerSocket类来实现TCP通讯。具体实现,我们可以使用多线程来提高服务端的并发处理能力。 以下是一个简单的示例代码,实现了一个基于多线程的物联网Java服务端TCP通讯: ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class IoTServer { public static void main(String[] args) throws IOException { int port = 8888; ServerSocket serverSocket = new ServerSocket(port); System.out.println("服务器已启动,等待客户端连接..."); while (true) { Socket socket = serverSocket.accept(); System.out.println("客户端已连接,IP地址为:" + socket.getInetAddress().getHostAddress()); new Thread(new IoTServerHandler(socket)).start(); } } static class IoTServerHandler implements Runnable { private Socket socket; public IoTServerHandler(Socket socket) { this.socket = socket; } @Override public void run() { try { // 获取输入流 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 获取输出流 PrintWriter out = new PrintWriter(socket.getOutputStream()); String line; while ((line = in.readLine()) != null) { System.out.println("客户端:" + line); out.println("服务端已收到消息:" + line); out.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` 以上代码,我们使用ServerSocket监听指定端口,并在接收到客户端连接后开启一个新线程处理该连接。在IoTServerHandler,我们通过输入流读取客户端发送的消息,并通过输出流向客户端发送响应。 当然,这只是一个简单的示例,实际应用我们需要根据具体需求进行更加细致的设计和实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值