package com.wm.box.service.udp;
import com.wm.box.config.BoxUdpApplication;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.net.*;
public class BoxUdpServer {
private static final Logger logger = Logger.getLogger(BoxUdpServer.class);
private byte[] buffer = new byte[1024];
private DatagramSocket ds = null;
private DatagramPacket packet = null;
private InetSocketAddress socketAddress = null;
private String orgIp;
public static BoxUdpServer getInstance(){
return BoxUdpServer.BoxUdpNettyServerHolder.INSTANCE;
}
private static final class BoxUdpNettyServerHolder{
static final BoxUdpServer INSTANCE = new BoxUdpServer();
}
/**
* 构造函数,绑定主机和端口.
* @param host 主机
* @param port 端口
* @throws Exception
*/
public boolean start(String host, int port) {
try {
socketAddress = new InetSocketAddress(host, port);
ds = new DatagramSocket(socketAddress);
logger.info("udp server socket服务端启动!");
return true;
} catch (SocketException e) {
logger.error("udp server socket服务端启动异常",e);
return false;
}
}
public String getOrgIp() {
return orgIp;
}
/**
* 设置超时时间,该方法必须在bind方法之后使用.
* @param timeout 超时时间
* @throws Exception
*/
public void setSoTimeout(int timeout) throws Exception {
ds.setSoTimeout(timeout);
}
/**
* 获得超时时间.
* @return 返回超时时间.
* @throws Exception
*/
public int getSoTimeout() throws Exception {
return ds.getSoTimeout();
}
/**
* 绑定监听地址和端口.
* @param host 主机IP
* @param port 端口
* @throws SocketException
*/
public void bind(String host, int port) throws SocketException {
socketAddress = new InetSocketAddress(host, port);
ds = new DatagramSocket(socketAddress);
}
/**
* 接收数据包,该方法会造成线程阻塞.
* @return 返回接收的数据串信息
* @throws IOException
* @throws ClassNotFoundException
*/
public byte[] receive() throws IOException, ClassNotFoundException {
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
orgIp = packet.getAddress().getHostAddress();
return buffer;
}
/**
* 将响应包发送给请求端.
* @throws IOException
*/
public void response(String info) throws IOException {
System.out.println("客户端地址 : " + packet.getAddress().getHostAddress()
+ ",端口:" + packet.getPort());
DatagramPacket dp = new DatagramPacket(buffer, buffer.length, packet
.getAddress(), packet.getPort());
dp.setData(info.getBytes());
ds.send(dp);
}
/**
* 设置报文的缓冲长度.
* @param bufsize 缓冲长度
*/
public void setLength(int bufsize) {
packet.setLength(bufsize);
}
/**
* 获得发送回应的IP地址.
* @return 返回回应的IP地址
*/
public InetAddress getResponseAddress() {
return packet.getAddress();
}
/**
* 获得回应的主机的端口.
* @return 返回回应的主机的端口.
*/
public int getResponsePort() {
return packet.getPort();
}
/**
* 关闭udp监听口.
*/
public final void close() {
try {
ds.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
发送字节数组:
package com.wm.box.service.udp;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wm.box.model.Point;
import com.wm.box.model.StreamOperate;
import com.wm.box.service.ChannelHandler;
import com.wm.box.service.PredictHandler;
import com.wm.box.util.ByteUtil;
import com.wm.box.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.concurrent.GenericFutureListener;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.net.InetSocketAddress;
import java.util.*;
@Component
public class BoxUdpOperateServer {
private static final Logger logger = Logger.getLogger(BoxUdpOperateServer.class);
@Value("${udp.server.host}")
private String host;
@Value("${udp.listen.port}")
private int port;
@Autowired
private PredictHandler predictHandler;
@Autowired
private ChannelHandler channelHandler;
/**
* 启动预测流
* @param streamOperate 所需数据
*/
public void enableStream(final StreamOperate streamOperate){
if(streamOperate == null){
throw new NullPointerException("msg is null");
}
InetSocketAddress inetSocketAddress = new InetSocketAddress(host,port);
byte[] headerBytes = {(byte) 0xcc};
byte[] codeBytes = {(byte) 0x01};
byte[] streamIdBytes = ByteUtil.getFixBytes(streamOperate.getStreamId(),16);
byte[] modelBytes = ByteUtil.getFixBytes(streamOperate.getModel(),20);
byte[] thresholds = ByteUtil.getBytes(streamOperate.getThresholds());
byte[] resultBytes = ArrayUtil.addAll(streamIdBytes,modelBytes,thresholds);
String areas = streamOperate.getAreas();
if(!StringUtils.isEmpty(areas)){
List<List<Point>> coordinates = JSONObject.parseObject(areas,new TypeReference<List<List<Point>>>(){});
byte[] areaNumBytes = ByteUtil.getBytes(coordinates.size());
resultBytes = ArrayUtil.addAll(resultBytes,areaNumBytes);
for (List<Point> coordinate : coordinates) {
for (Point point : coordinate) {
byte[] pointX = ByteUtil.getBytes(point.getX());
byte[] pointY = ByteUtil.getBytes(point.getY());
resultBytes = ArrayUtil.addAll(resultBytes,pointX,pointY);
}
}
}
byte[] videoUrl = ByteUtil.getFixBytes(streamOperate.getVideoUrl(),100);
resultBytes = ArrayUtil.addAll(resultBytes,videoUrl);
byte[] packetLen = ByteUtil.getBytes(resultBytes.length+6);
resultBytes = ArrayUtil.addAll(headerBytes,codeBytes,packetLen,resultBytes);
ByteBuf dataBuf = Unpooled.copiedBuffer(resultBytes);
DatagramPacket datagramPacket = new DatagramPacket(dataBuf, inetSocketAddress);
int count = 0;
for (byte resultByte : resultBytes) {
System.out.print(resultByte+" ");
count++;
}
System.out.println("字节个数为:"+count);
senderInternal(datagramPacket);
}
/**
* 关闭预测流
* @param streamOperate 所需数据
*/
public void disableStream(final StreamOperate streamOperate) {
if (streamOperate == null) {
throw new NullPointerException("msg is null");
}
InetSocketAddress inetSocketAddress = new InetSocketAddress(host,port);
byte[] headerBytes = {(byte) 0xcc};
byte[] codeBytes = {(byte) 0x02};
byte[] streamIdBytes = ByteUtil.getFixBytes(streamOperate.getStreamId(),16);
byte[] resultBytes = ArrayUtil.addAll(streamIdBytes);
resultBytes = ArrayUtil.addAll(headerBytes,codeBytes,resultBytes);
int count = 0;
for (byte resultByte : resultBytes) {
System.out.print(resultByte+" ");
count++;
}
System.out.println("字节个数为:"+count);
ByteBuf dataBuf = Unpooled.copiedBuffer(resultBytes);
DatagramPacket datagramPacket = new DatagramPacket(dataBuf, inetSocketAddress);
senderInternal(datagramPacket);
}
/**
* 解析server发过来的数据包
* @param content 字节数据
* @return
* */
public void parseServerUdpData(byte[] content){
byte[] headerBytes = Arrays.copyOfRange(content,0,1);
String header = HexUtil.byte2HexStr(headerBytes);
if(!header.equals(BoxUdpCode.HEADER)){
return;
}
byte[] codeBytes = Arrays.copyOfRange(content,1,2);
String code = HexUtil.byte2HexStr(codeBytes);
if(code.equals(BoxUdpCode.ALARM_MESSAGE)){
predictHandler.handler(content);
}
if(code.equals(BoxUdpCode.OPERATE)){
channelHandler.updateStreamUrl(content);
}
}
/**
* 发送数据包服务器无返回结果
* @param datagramPacket
*/
private void senderInternal(final DatagramPacket datagramPacket) {
logger.info("BoxUdpClient.channel"+ BoxUdpNettyClient.channel);
if(BoxUdpNettyClient.channel == null){
throw new NullPointerException("channel is null");
}
BoxUdpNettyClient.channel.writeAndFlush(datagramPacket).addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
boolean success = future.isSuccess();
if(logger.isInfoEnabled()){
logger.info("Sender datagramPacket result : "+success);
}
}
});
}
}
客户端:
package com.wm.box.service.udp;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
public class BoxUdpNettyClient {
private final Bootstrap bootstrap;
public final NioEventLoopGroup workerGroup;
public static Channel channel;
public void start(String host,int listenPort) throws Exception{
try {
channel = bootstrap.bind(host,listenPort).sync().channel();
channel.closeFuture().await(1000);
} finally {
// workerGroup.shutdownGracefully();
}
}
public Channel getChannel(){
return channel;
}
public static BoxUdpNettyClient getInstance(){
return logPushUdpClient.INSTANCE;
}
private static final class logPushUdpClient{
static final BoxUdpNettyClient INSTANCE = new BoxUdpNettyClient();
}
private BoxUdpNettyClient(){
bootstrap = new Bootstrap();
workerGroup = new NioEventLoopGroup();
bootstrap.group(workerGroup)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel ch)throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new BoxUdpNettyClientHandler());
}
});
}
}
客户端处理类:
package com.wm.box.service.udp;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wm.box.model.Point;
import com.wm.box.model.StreamOperate;
import com.wm.box.util.ByteUtil;
import com.wm.box.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.concurrent.GenericFutureListener;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
public class BoxUdpNettyClientHandler extends SimpleChannelInboundHandler<DatagramPacket>{
private static final Logger logger = Logger.getLogger(BoxUdpNettyClientHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("client channel is ready!");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
logger.info("client channel is ready! ip:"+packet.sender().getAddress()+":"+packet.sender().getPort());
}
}
字节工具类:
package com.wm.box.util;
import java.nio.charset.Charset;
public class ByteUtil {
public static byte[] getBytes(short data) {
byte[] bytes = new byte[2];
bytes[0] = (byte) (data & 0xff);
bytes[1] = (byte) ((data & 0xff00) >> 8);
return bytes;
}
public static byte[] getBytes(char data) {
byte[] bytes = new byte[2];
bytes[0] = (byte) (data);
bytes[1] = (byte) (data >> 8);
return bytes;
}
public static byte[] getBytes(int data) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (data & 0xff);
bytes[1] = (byte) ((data & 0xff00) >> 8);
bytes[2] = (byte) ((data & 0xff0000) >> 16);
bytes[3] = (byte) ((data & 0xff000000) >> 24);
return bytes;
}
public static byte[] getBytes(long data) {
byte[] bytes = new byte[8];
bytes[0] = (byte) (data & 0xff);
bytes[1] = (byte) ((data >> 8) & 0xff);
bytes[2] = (byte) ((data >> 16) & 0xff);
bytes[3] = (byte) ((data >> 24) & 0xff);
bytes[4] = (byte) ((data >> 32) & 0xff);
bytes[5] = (byte) ((data >> 40) & 0xff);
bytes[6] = (byte) ((data >> 48) & 0xff);
bytes[7] = (byte) ((data >> 56) & 0xff);
return bytes;
}
public static byte[] getBytes(float data) {
int intBits = Float.floatToIntBits(data);
return getBytes(intBits);
}
public static byte[] getBytes(double data) {
long intBits = Double.doubleToLongBits(data);
return getBytes(intBits);
}
public static byte[] getBytes(String data, String charsetName) {
Charset charset = Charset.forName(charsetName);
return data.getBytes(charset);
}
public static byte[] getFixBytes(String str,int length){
byte[] bytes = new byte[length];
byte[] aBytes = str.getBytes();
if(aBytes.length > length){
return bytes;
}
for (int i = 0; i < aBytes.length; i++) {
bytes[i] = aBytes[i];
}
return bytes;
}
public static byte[] getBytes(String data) {
return getBytes(data, "GBK");
}
public static short getShort(byte[] bytes) {
return (short) ((0xff & bytes[0]) | (0xff00 & (bytes[1] << 8)));
}
public static char getChar(byte[] bytes) {
return (char) ((0xff & bytes[0]) | (0xff00 & (bytes[1] << 8)));
}
public static int getInt(byte[] bytes) {
return (0xff & bytes[0]) | (0xff00 & (bytes[1] << 8)) | (0xff0000 & (bytes[2] << 16)) | (0xff000000 & (bytes[3] << 24));
}
public static long getLong(byte[] bytes) {
return (0xffL & (long) bytes[0]) | (0xff00L & ((long) bytes[1] << 8)) | (0xff0000L & ((long) bytes[2] << 16)) | (0xff000000L & ((long) bytes[3] << 24))
| (0xff00000000L & ((long) bytes[4] << 32)) | (0xff0000000000L & ((long) bytes[5] << 40)) | (0xff000000000000L & ((long) bytes[6] << 48)) | (0xff00000000000000L & ((long) bytes[7] << 56));
}
public static float getFloat(byte[] bytes) {
return Float.intBitsToFloat(getInt(bytes));
}
public static double getDouble(byte[] bytes) {
long l = getLong(bytes);
System.out.println(l);
return Double.longBitsToDouble(l);
}
public static String getString(byte[] bytes, String charsetName) {
return new String(bytes, Charset.forName(charsetName));
}
public static String getString(byte[] bytes) {
return getString(bytes, "GBK");
}
/**
* 多个数组合并一个
*
* @return
*/
public static byte[] byteMergerAll(byte[]... bytes) {
int allLength = 0;
for (byte[] b : bytes) {
allLength += b.length;
}
byte[] allByte = new byte[allLength];
int countLength = 0;
for (byte[] b : bytes) {
System.arraycopy(b, 0, allByte, countLength, b.length);
countLength += b.length;
}
return allByte;
}
}
Hex工具类:
package com.wm.box.util;
public class HexUtil {
/**
* 用于建立十六进制字符的输出的小写字符数组
*/
private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* 用于建立十六进制字符的输出的大写字符数组
*/
private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* 字符串转换成十六进制字符串
*
* @param str 待转换的ASCII字符串
* @return String 每个Byte之间空格分隔,如: [61 6C 6B]
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
sb.append(' ');
}
return sb.toString().trim();
}
/**
* 十六进制转换字符串
*
* @param hexStr Byte字符串(Byte之间无分隔符 如:[616C6B])
* @return String 对应的字符串
*/
public static String hexStr2Str(String hexStr) {
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++) {
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
return new String(bytes);
}
/**
* bytes转换成十六进制字符串
*
* @param b byte数组
* @return String 每个Byte值之间空格分隔
*/
public static String byte2HexStr(byte[] b) {
String stmp = "";
StringBuilder sb = new StringBuilder("");
for (int n = 0; n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0xFF);
sb.append((stmp.length() == 1) ? "0" + stmp : stmp);
// sb.append(" ");
}
return sb.toString().toUpperCase().trim();
}
/**
* bytes字符串转换为Byte值
*
* @param src Byte字符串,每个Byte之间没有分隔符
* @return byte[]
*/
public static byte[] hexStr2Bytes(String src) {
if (src.length()>1){
int m = 0, n = 0;
int l = src.length() / 2;
byte[] ret = new byte[l];
for (int i = 0; i < l; i++) {
m = i * 2 + 1;
n = m + 1;
ret[i] = Byte.decode("0x" + src.substring(i * 2, m) + src.substring(m, n));
}
return ret;
}else{
byte[] ret = new byte[1];
ret[0] = Byte.decode("0x0"+src);
return ret;
}
}
/**
* String的字符串转换成unicode的String
*
* @param strText 全角字符串
* @return String 每个unicode之间无分隔符
* @throws Exception
*/
public static String strToUnicode(String strText) throws Exception {
char c;
StringBuilder str = new StringBuilder();
int intAsc;
String strHex;
for (int i = 0; i < strText.length(); i++) {
c = strText.charAt(i);
intAsc = (int) c;
strHex = Integer.toHexString(intAsc);
if (intAsc > 128)
str.append("\\u" + strHex);
else
// 低位在前面补00
str.append("\\u00" + strHex);
}
return str.toString();
}
/**
* unicode的String转换成String的字符串
*
* @param hex 16进制值字符串 (一个unicode为2byte)
* @return String 全角字符串
*/
public static String unicodeToString(String hex) {
int t = hex.length() / 6;
StringBuilder str = new StringBuilder();
for (int i = 0; i < t; i++) {
String s = hex.substring(i * 6, (i + 1) * 6);
// 高位需要补上00再转
String s1 = s.substring(2, 4) + "00";
// 低位直接转
String s2 = s.substring(4);
// 将16进制的string转为int
int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16);
// 将int转换为字符
char[] chars = Character.toChars(n);
str.append(new String(chars));
}
return str.toString();
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data byte[]
* @return 十六进制char[]
*/
public static char[] encodeHex(byte[] data) {
return encodeHex(data, true);
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data byte[]
* @param toLowerCase <code>true</code> 传换成小写格式 , <code>false</code> 传换成大写格式
* @return 十六进制char[]
*/
public static char[] encodeHex(byte[] data, boolean toLowerCase) {
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data byte[]
* @param toDigits 用于控制输出的char[]
* @return 十六进制char[]
*/
protected static char[] encodeHex(byte[] data, char[] toDigits) {
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data byte[]
* @return 十六进制String
*/
public static String encodeHexStr(byte[] data) {
return encodeHexStr(data, true);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data byte[]
* @param toLowerCase <code>true</code> 传换成小写格式 , <code>false</code> 传换成大写格式
* @return 十六进制String
*/
public static String encodeHexStr(byte[] data, boolean toLowerCase) {
return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data byte[]
* @param toDigits 用于控制输出的char[]
* @return 十六进制String
*/
protected static String encodeHexStr(byte[] data, char[] toDigits) {
return new String(encodeHex(data, toDigits));
}
/**
* 将十六进制字符数组转换为字节数组
*
* @param data 十六进制char[]
* @return byte[]
* @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常
*/
public static byte[] decodeHex(char[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new RuntimeException("Odd number of characters.");
}
byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
/**
* 将十六进制字符转换成一个整数
*
* @param ch 十六进制char
* @param index 十六进制字符在字符数组中的位置
* @return 一个整数
* @throws RuntimeException 当ch不是一个合法的十六进制字符时,抛出运行时异常
*/
protected static int toDigit(char ch, int index) {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new RuntimeException("Illegal hexadecimal character " + ch
+ " at index " + index);
}
return digit;
}
}
内容化解析,并处理逻辑示例:
package com.wm.box.service;
import com.wm.box.entity.Channel;
import com.wm.box.service.udp.BoxUdpCode;
import com.wm.box.util.ByteUtil;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class ChannelHandler {
private static final Logger logger = Logger.getLogger(ChannelHandler.class);
@Autowired
private ChannelService channelService;
public boolean updateStreamUrl(byte[] content) {
try {
String str = new String(content);
System.out.println("响应内容:"+str);
byte[] responseCodeBytes = Arrays.copyOfRange(content,2,6);
byte[] streamIdBytes = Arrays.copyOfRange(content,6,21);
byte[] pushStreamUrlBytes = Arrays.copyOfRange(content,22,121);
String streamId = new String(streamIdBytes);
System.out.println("streamId:"+streamId);
int code = ByteUtil.getInt(responseCodeBytes);
System.out.println("code:"+code);
if(code == BoxUdpCode.SUCCESS){
Channel channel = channelService.findById(Long.parseLong(streamId));
if(channel !=null){
channel.setVideoAddress(new String(pushStreamUrlBytes));
channelService.updateById(channel);
return true;
}
}
} catch (Exception e) {
logger.error("通道更新推流地址异常",e);
}
return false;
}
}