首先说明一下实现的功能:
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是无连接状态的。好了关于网络编程,有许多知识点需要我们去学习,这里就简单记录这次开发。