spring企业开发-Socket消息传输-第五篇

首先说明一下实现的功能:

1.C++编写客户端在用户第一次安装时,获取本机信息,并将信息传输到服务器端插入数据库

2.当用户计算机插入USB设备时,获取服务端白名单信息(即哪些是允许的USB设备)

3.当发现插入USB设备为非法设备时请求服务端,将违法设备信息插入数据库

以上就是实现的功能。

实现思路:

1.服务端设置监听,监听客户端请求并给予返回值信息

2.根据业务进行技术选型

我们知道这种服务端监听客户请求的技术有很多,例如:socket(TCP/UDP)、webservice+soap、Http+json

关于技术选型,现在大多数选择第三种Http+json因为比较简单。

但是呢这里选择第一种,具体原因就是历史遗留问题,因为这个项目就是实用UDP进行数据传输的。我也不能大动,正好就在这里复习一下Socket编程。

Socket和webservice区别?

网上说的什么都有,我觉得有一点很靠谱,就是Socket是长连接的,我们实用Socket传递消息,只要建立连接就可以一致发送消息,而不必进行第二次发送消息时再进行连接。后面的代码会展示这点。而我们实用webservice大多数都是调用接口,每次调用都需要把服务器地址啊、端口信息写入。如果追溯底层的话,可能webservice是封装的更好到Socket吧。

下面进行Web项目中的Socket到UDP编程

附上一个讲的很好的TCP/UDP教程,讲的很细致:http://blog.51cto.com/sihai/2071819

第一步:在web.xml文件中添加监听,监听的内容就是启动Socket服务端

<!--Socket服务监听-->
    <listener>
        <listener-class>com.youotech.usbmonitor.net.SocketListen</listener-class>
    </listener>

第二:创建监听类

然后自己创建SocketListen类,并实现ServletContextListener接口。

ServletContextListener接口作用:

在 Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期,当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。

public class SocketListen implements ServletContextListener {
    private SocketThread socketThread;
    private Logger log = LoggerFactory.getLogger(SocketListen.class);
    //初始化方法
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("初始化启动Socket服务...");
        if(socketThread == null){
            socketThread = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(SocketThread.class);
            socketThread.start();
        }
    }

    //销毁方法
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("将Socket服务关闭...");
        if(null != socketThread && !socketThread.isInterrupted()){
            socketThread.closeSocketServe();
            socketThread.interrupt();
        }
    }
}

其中SocketThread就是启动服务类,这里因为监听无法使用spring进行扫描,所以注入的这个服务端使用的IOC容器通过名称获取的方法获取。

好了我们看一下服务端代码:

package com.youotech.usbmonitor.net;

import com.youotech.usbmonitor.utils.PropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author:Yangsaijun
 * @Description:UDP协议开启服务
 * 将启用服务使用线程编写是为了在容器关闭时将该线程关闭
 * @Date Created in 14:56 2018/8/24
 */
@Component("socketThread")
public class SocketThread extends Thread{
    private Logger log = LoggerFactory.getLogger(SocketThread.class);
    //线程池
    private static ExecutorService threadPool = Executors.newFixedThreadPool(20);;
    //业务处理逻辑
    @Autowired
    private TaskHandleFactory taskHandleFactory;

    //private TaskHandle taskHandle;
    private Properties prop;
    private int port;
    private  DatagramSocket ds = null;//连接对象

    //创建Socket的UDP连接对象,只启动一次(在项目启动时)
    public SocketThread(){
        prop = PropertyUtil.loadProps();
        port = Integer.parseInt(prop.getProperty("server.port"));
        if(ds == null){
            try {
                //1.UDP服务端建立Socket连接,监听指定端口
                ds = new DatagramSocket(port);
                log.info("创建UDP连接成功...");
            }catch (SocketException e){
                log.error("创建Socket服务异常"+e);
            }
        }
    }

    @Override
    public void run() {
        DatagramPacket receiveDp = null; //接收数据包对象

        log.info("服务已启动...进入到长连接状态等待请求...");
        int i= 0;
        while(true){
            ++i;
            System.out.println("循环"+i);
            //2.初始化接收数据(循环需要对接收数据清空)
            byte[] bReceive = new byte[1024];
            receiveDp = new DatagramPacket(bReceive,bReceive.length);
            //3.接收
            try {
                ds.receive(receiveDp);
                //4.调用数据处理方法
                log.info("调用业务处理方法中...");
                taskHandleFactory.setDatagramPacket(receiveDp);
                threadPool.execute(taskHandleFactory); 
            } catch (IOException e) {
                log.error("服务端接收请求异常"+e);
            }
        }
    }

    /***
     * 线程销毁方法
     */
    public void closeSocketServe(){
        if(null != ds && !ds.isClosed()){
            ds.close();
        }
    }

}
 


解释下上面代码:在项目启动时,spring扫面该类,使用其中无参数构造方法,里面是通过指定端口开启UDP服务。

该类作为线程,在监听器启动时,该线程即启动,里面的while循环是等待客户端请求,一旦有请求,会新开启一个线程进行逻辑执行。同时新启动的线程由线程池进行管理。因为接受请求是阻塞方法,所以不必担心无限循环。

这里还是有个问题:

假设服务端已经启动,那么客户端请求是否像Web的request一样,每一个请求都是重新开启一个线程呢?

答案是否定的,也就是说只有一个主要线程用于接收监听,就好比两个客户端请求,请求1到达while循环时,这时请求2到来,此时请求2是被堵塞的,直到请求1到达线程池执行位置,请求2才开始进入while循环。所以我很担心当极限情况下,有10000个客户端同时请求,可能会造成长时间堵塞。

第三:创建业务处理逻辑

/**
 * @Author:Yangsaijun
 * @Description
 * @Date Created in 14:15 2018/9/4
 */
@Component("taskHandleFactory")
@Scope("prototype")
public class TaskHandleFactory implements Runnable{
    private Logger log = LoggerFactory.getLogger(TaskHandleFactory.class);
    //UDP接收数据包信息
    @Autowired
    private TaskHandle taskHandle;
    //创建对象需传入数据包
    private DatagramPacket datagramPacket;
    public void setDatagramPacket(DatagramPacket datagramPacket){
        this.datagramPacket = datagramPacket;
    }

    @Override
    public void run() {
        //获取数据包中信息
        byte[] receiveBuf = datagramPacket.getData();
        //获取客户端请求信息
        String requestData = new String(receiveBuf);
        log.info("客户端请求信息为:"+requestData);
        JSONObject jsonObj = JSONObject.parseObject(requestData.trim());
        String interfaceNo = jsonObj.getString("sign");
        SocketAddress clientAdd = datagramPacket.getSocketAddress();
        String response = taskHandle.handle(interfaceNo,jsonObj);

        //对客户端请求进行反馈
        log.info("反馈客户端信息:" + response + "\n客户端地址" + clientAdd);
        NetUtil.send(response, clientAdd);//发送UDP数据报包到客户端
    }
}

里面就是根据请求数据然后返回结果,具体的代码就不粘贴了。

下面说说客户端代码:

第四步:客户端代码

package client.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
 * @Author:Yangsaijun
 * @Description
 * @Date Created in 14:42 2018/8/27
 */
public class UDPClient {
    public static void main(String[] args) {
        try {
            String msg = "";
            DatagramSocket daSocket = new DatagramSocket();
            //查询白名单
           msg = "{\"sign\":\"whiteList\",\"operator_system\":\"\"}";
        
            //访问服务地址
            String IP = "127.0.0.1";
            int port = 3306;
            byte[] by = msg.getBytes();
            //将服务器IP转化为InetAddress对象
            try {
                InetAddress server = InetAddress.getByName(IP);
                DatagramPacket sendDp = new DatagramPacket(by,by.length,server,port);
                try {

                        daSocket.send(sendDp);
                        //获取服务器返回信息
                        byte[] byResp = new byte[1024];
                        DatagramPacket receive = new DatagramPacket(byResp,byResp.length);
                        //接收数据
                        daSocket.receive(receive);
                        //输出内容
                        byte[] b = receive.getData();
                        int len = receive.getLength();
                        String s = new String(b,0,len);
                        System.out.println(s);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }

        } catch (SocketException e) {
            e.printStackTrace();
        }

    }
}

记住UDP是无连接状态的。好了关于网络编程,有许多知识点需要我们去学习,这里就简单记录这次开发。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值