JavaNetty心跳监听(三)
使用定时发送消息的方式,实现硬件检测,达到心态检测的目的。
心跳监测是用于检测电脑硬件和软件信息的一种技术。如:CPU 使用率,磁盘使用率, 内存使用率,进程情况,线程情况等。
4.5.1 sigar
需要下载一个 zip 压缩包。内部包含若干 sigar 需要的操作系统文件。sigar 插件是通过 JVM 访问操作系统,读取计算机硬件的一个插件库。读取计算机硬件过程中,必须由操作系统提供硬件信息。硬件信息是通过操作系统提供的。zip 压缩包中是 sigar 编写的操作系统文 件,如:windows 中的动态链接库文件。
解压需要的操作系统文件,将操作系统文件赋值到${Java_home}/bin 目录中
import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; import java.util.Properties; import org.hyperic.sigar.CpuInfo; import org.hyperic.sigar.CpuPerc; import org.hyperic.sigar.FileSystem; import org.hyperic.sigar.FileSystemUsage; import org.hyperic.sigar.Mem; import org.hyperic.sigar.NetFlags; import org.hyperic.sigar.NetInterfaceConfig; import org.hyperic.sigar.NetInterfaceStat; import org.hyperic.sigar.OperatingSystem; import org.hyperic.sigar.Sigar; import org.hyperic.sigar.SigarException; import org.hyperic.sigar.Swap; import org.hyperic.sigar.Who; public class OSUtils { public static void main(String[] args) { try { // System信息,从jvm获取 property(); System.out.println("----------------------------------"); // cpu信息 cpu(); System.out.println("----------------------------------"); // 内存信息 memory(); System.out.println("----------------------------------"); // 操作系统信息 os(); System.out.println("----------------------------------"); // 用户信息 who(); System.out.println("----------------------------------"); // 文件系统信息 file(); System.out.println("----------------------------------"); // 网络信息 net(); System.out.println("----------------------------------"); // 以太网信息 ethernet(); System.out.println("----------------------------------"); } catch (Exception e1) { e1.printStackTrace(); } } private static void property() throws UnknownHostException { Runtime r = Runtime.getRuntime(); Properties props = System.getProperties(); InetAddress addr; addr = InetAddress.getLocalHost(); String ip = addr.getHostAddress(); Map<String, String> map = System.getenv(); String userName = map.get("USERNAME");// 获取用户名 String computerName = map.get("COMPUTERNAME");// 获取计算机名 String userDomain = map.get("USERDOMAIN");// 获取计算机域名 System.out.println("用户名: " + userName); System.out.println("计算机名: " + computerName); System.out.println("计算机域名: " + userDomain); System.out.println("本地ip地址: " + ip); System.out.println("本地主机名: " + addr.getHostName()); System.out.println("JVM可以使用的总内存: " + r.totalMemory()); System.out.println("JVM可以使用的剩余内存: " + r.freeMemory()); System.out.println("JVM可以使用的处理器个数: " + r.availableProcessors()); System.out.println("Java的运行环境版本: " + props.getProperty("java.version")); System.out.println("Java的运行环境供应商: " + props.getProperty("java.vendor")); System.out.println("Java供应商的URL: " + props.getProperty("java.vendor.url")); System.out.println("Java的安装路径: " + props.getProperty("java.home")); System.out.println("Java的虚拟机规范版本: " + props.getProperty("java.vm.specification.version")); System.out.println("Java的虚拟机规范供应商: " + props.getProperty("java.vm.specification.vendor")); System.out.println("Java的虚拟机规范名称: " + props.getProperty("java.vm.specification.name")); System.out.println("Java的虚拟机实现版本: " + props.getProperty("java.vm.version")); System.out.println("Java的虚拟机实现供应商: " + props.getProperty("java.vm.vendor")); System.out.println("Java的虚拟机实现名称: " + props.getProperty("java.vm.name")); System.out.println("Java运行时环境规范版本: " + props.getProperty("java.specification.version")); System.out.println("Java运行时环境规范供应商: " + props.getProperty("java.specification.vender")); System.out.println("Java运行时环境规范名称: " + props.getProperty("java.specification.name")); System.out.println("Java的类格式版本号: " + props.getProperty("java.class.version")); System.out.println("Java的类路径: " + props.getProperty("java.class.path")); System.out.println("加载库时搜索的路径列表: " + props.getProperty("java.library.path")); System.out.println("默认的临时文件路径: " + props.getProperty("java.io.tmpdir")); System.out.println("一个或多个扩展目录的路径: " + props.getProperty("java.ext.dirs")); System.out.println("操作系统的名称: " + props.getProperty("os.name")); System.out.println("操作系统的构架: " + props.getProperty("os.arch")); System.out.println("操作系统的版本: " + props.getProperty("os.version")); System.out.println("文件分隔符: " + props.getProperty("file.separator")); System.out.println("路径分隔符: " + props.getProperty("path.separator")); System.out.println("行分隔符: " + props.getProperty("line.separator")); System.out.println("用户的账户名称: " + props.getProperty("user.name")); System.out.println("用户的主目录: " + props.getProperty("user.home")); System.out.println("用户的当前工作目录: " + props.getProperty("user.dir")); } private static void memory() throws SigarException { Sigar sigar = new Sigar(); Mem mem = sigar.getMem(); // 内存总量 System.out.println("内存总量: " + mem.getTotal() / 1024L + "K av"); // 当前内存使用量 System.out.println("当前内存使用量: " + mem.getUsed() / 1024L + "K used"); // 当前内存剩余量 System.out.println("当前内存剩余量: " + mem.getFree() / 1024L + "K free"); Swap swap = sigar.getSwap(); // 交换区总量 System.out.println("交换区总量: " + swap.getTotal() / 1024L + "K av"); // 当前交换区使用量 System.out.println("当前交换区使用量: " + swap.getUsed() / 1024L + "K used"); // 当前交换区剩余量 System.out.println("当前交换区剩余量: " + swap.getFree() / 1024L + "K free"); } private static void cpu() throws SigarException { Sigar sigar = new Sigar(); CpuInfo infos[] = sigar.getCpuInfoList(); CpuPerc cpuList[] = null; cpuList = sigar.getCpuPercList(); for (int i = 0; i < infos.length; i++) {// 不管是单块CPU还是多CPU都适用 CpuInfo info = infos[i]; System.out.println("第" + (i + 1) + "块CPU信息"); System.out.println("CPU的总量MHz: " + info.getMhz());// CPU的总量MHz System.out.println("CPU生产商: " + info.getVendor());// 获得CPU的卖主,如:Intel System.out.println("CPU类别: " + info.getModel());// 获得CPU的类别,如:Celeron System.out.println("CPU缓存数量: " + info.getCacheSize());// 缓冲存储器数量 printCpuPerc(cpuList[i]); } } private static void printCpuPerc(CpuPerc cpu) { System.out.println("CPU用户使用率: " + CpuPerc.format(cpu.getUser()));// 用户使用率 System.out.println("CPU系统使用率: " + CpuPerc.format(cpu.getSys()));// 系统使用率 System.out.println("CPU当前等待率: " + CpuPerc.format(cpu.getWait()));// 当前等待率 System.out.println("CPU当前错误率: " + CpuPerc.format(cpu.getNice()));// System.out.println("CPU当前空闲率: " + CpuPerc.format(cpu.getIdle()));// 当前空闲率 System.out.println("CPU总的使用率: " + CpuPerc.format(cpu.getCombined()));// 总的使用率 } private static void os() { OperatingSystem OS = OperatingSystem.getInstance(); // 操作系统内核类型如: 386、486、586等x86 System.out.println("操作系统: " + OS.getArch()); System.out.println("操作系统CpuEndian(): " + OS.getCpuEndian());// System.out.println("操作系统DataModel(): " + OS.getDataModel());// // 系统描述 System.out.println("操作系统的描述: " + OS.getDescription()); // 操作系统类型 // System.out.println("OS.getName(): " + OS.getName()); // System.out.println("OS.getPatchLevel(): " + OS.getPatchLevel());// // 操作系统的卖主 System.out.println("操作系统的卖主: " + OS.getVendor()); // 卖主名称 System.out.println("操作系统的卖主名: " + OS.getVendorCodeName()); // 操作系统名称 System.out.println("操作系统名称: " + OS.getVendorName()); // 操作系统卖主类型 System.out.println("操作系统卖主类型: " + OS.getVendorVersion()); // 操作系统的版本号 System.out.println("操作系统的版本号: " + OS.getVersion()); } private static void who() throws SigarException { Sigar sigar = new Sigar(); Who who[] = sigar.getWhoList(); if (who != null && who.length > 0) { for (int i = 0; i < who.length; i++) { // System.out.println("当前系统进程表中的用户名" + String.valueOf(i)); Who _who = who[i]; System.out.println("用户控制台: " + _who.getDevice()); System.out.println("用户host: " + _who.getHost()); // System.out.println("getTime(): " + _who.getTime()); // 当前系统进程表中的用户名 System.out.println("当前系统进程表中的用户名: " + _who.getUser()); } } } private static void file() throws Exception { Sigar sigar = new Sigar(); FileSystem fslist[] = sigar.getFileSystemList(); try { for (int i = 0; i < fslist.length; i++) { System.out.println("分区的盘符名称" + i); FileSystem fs = fslist[i]; // 分区的盘符名称 System.out.println("盘符名称: " + fs.getDevName()); // 分区的盘符名称 System.out.println("盘符路径: " + fs.getDirName()); System.out.println("盘符标志: " + fs.getFlags());// // 文件系统类型,比如 FAT32、NTFS System.out.println("盘符类型: " + fs.getSysTypeName()); // 文件系统类型名,比如本地硬盘、光驱、网络文件系统等 System.out.println("盘符类型名: " + fs.getTypeName()); // 文件系统类型 System.out.println("盘符文件系统类型: " + fs.getType()); FileSystemUsage usage = null; usage = sigar.getFileSystemUsage(fs.getDirName()); switch (fs.getType()) { case 0: // TYPE_UNKNOWN :未知 break; case 1: // TYPE_NONE break; case 2: // TYPE_LOCAL_DISK : 本地硬盘 // 文件系统总大小 System.out.println(fs.getDevName() + "总大小: " + usage.getTotal() + "KB"); // 文件系统剩余大小 System.out.println(fs.getDevName() + "剩余大小: " + usage.getFree() + "KB"); // 文件系统可用大小 System.out.println(fs.getDevName() + "可用大小: " + usage.getAvail() + "KB"); // 文件系统已经使用量 System.out.println(fs.getDevName() + "已经使用量: " + usage.getUsed() + "KB"); double usePercent = usage.getUsePercent() * 100D; // 文件系统资源的利用率 System.out.println(fs.getDevName() + "资源的利用率: " + usePercent + "%"); break; case 3:// TYPE_NETWORK :网络 break; case 4:// TYPE_RAM_DISK :闪存 break; case 5:// TYPE_CDROM :光驱 break; case 6:// TYPE_SWAP :页面交换 break; } System.out.println(fs.getDevName() + "读出: " + usage.getDiskReads()); System.out.println(fs.getDevName() + "写入: " + usage.getDiskWrites()); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return; } private static void net() throws Exception { Sigar sigar = new Sigar(); String ifNames[] = sigar.getNetInterfaceList(); for (int i = 0; i < ifNames.length; i++) { String name = ifNames[i]; NetInterfaceConfig ifconfig = sigar.getNetInterfaceConfig(name); System.out.println("网络设备名: " + name);// 网络设备名 System.out.println("IP地址: " + ifconfig.getAddress());// IP地址 System.out.println("子网掩码: " + ifconfig.getNetmask());// 子网掩码 if ((ifconfig.getFlags() & 1L) <= 0L) { System.out.println("!IFF_UP...skipping getNetInterfaceStat"); continue; } NetInterfaceStat ifstat = sigar.getNetInterfaceStat(name); System.out.println(name + "接收的总包裹数:" + ifstat.getRxPackets());// 接收的总包裹数 System.out.println(name + "发送的总包裹数:" + ifstat.getTxPackets());// 发送的总包裹数 System.out.println(name + "接收到的总字节数:" + ifstat.getRxBytes());// 接收到的总字节数 System.out.println(name + "发送的总字节数:" + ifstat.getTxBytes());// 发送的总字节数 System.out.println(name + "接收到的错误包数:" + ifstat.getRxErrors());// 接收到的错误包数 System.out.println(name + "发送数据包时的错误数:" + ifstat.getTxErrors());// 发送数据包时的错误数 System.out.println(name + "接收时丢弃的包数:" + ifstat.getRxDropped());// 接收时丢弃的包数 System.out.println(name + "发送时丢弃的包数:" + ifstat.getTxDropped());// 发送时丢弃的包数 } } private static void ethernet() throws SigarException { Sigar sigar = null; sigar = new Sigar(); String[] ifaces = sigar.getNetInterfaceList(); for (int i = 0; i < ifaces.length; i++) { NetInterfaceConfig cfg = sigar.getNetInterfaceConfig(ifaces[i]); if (NetFlags.LOOPBACK_ADDRESS.equals(cfg.getAddress()) || (cfg.getFlags() & NetFlags.IFF_LOOPBACK) != 0 || NetFlags.NULL_HWADDR.equals(cfg.getHwaddr())) { continue; } System.out.println(cfg.getName() + "IP地址:" + cfg.getAddress());// IP地址 System.out.println(cfg.getName() + "网关广播地址:" + cfg.getBroadcast());// 网关广播地址 System.out.println(cfg.getName() + "网卡MAC地址:" + cfg.getHwaddr());// 网卡MAC地址 System.out.println(cfg.getName() + "子网掩码:" + cfg.getNetmask());// 子网掩码 System.out.println(cfg.getName() + "网卡描述信息:" + cfg.getDescription());// 网卡描述信息 System.out.println(cfg.getName() + "网卡类型" + cfg.getType());// } } }
/** * 1. 双线程组 * 2. Bootstrap配置启动信息 * 3. 注册业务处理Handler * 4. 绑定服务监听端口并启动服务 */ import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import utils.SerializableFactory4Marshalling; public class Server4Heatbeat { // 监听线程组,监听客户端请求 private EventLoopGroup acceptorGroup = null; // 处理客户端相关操作线程组,负责处理与客户端的数据通讯 private EventLoopGroup clientGroup = null; // 服务启动相关配置信息 private ServerBootstrap bootstrap = null; public Server4Heatbeat(){ init(); } private void init(){ acceptorGroup = new NioEventLoopGroup(); clientGroup = new NioEventLoopGroup(); bootstrap = new ServerBootstrap(); // 绑定线程组 bootstrap.group(acceptorGroup, clientGroup); // 设定通讯模式为NIO bootstrap.channel(NioServerSocketChannel.class); // 设定缓冲区大小 bootstrap.option(ChannelOption.SO_BACKLOG, 1024); // SO_SNDBUF发送缓冲区,SO_RCVBUF接收缓冲区,SO_KEEPALIVE开启心跳监测(保证连接有效) bootstrap.option(ChannelOption.SO_SNDBUF, 16*1024) .option(ChannelOption.SO_RCVBUF, 16*1024) .option(ChannelOption.SO_KEEPALIVE, true); } public ChannelFuture doAccept(int port) throws InterruptedException{ bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(SerializableFactory4Marshalling.buildMarshallingDecoder()); ch.pipeline().addLast(SerializableFactory4Marshalling.buildMarshallingEncoder()); ch.pipeline().addLast(new Server4HeatbeatHandler()); } }); ChannelFuture future = bootstrap.bind(port).sync(); return future; } public void release(){ this.acceptorGroup.shutdownGracefully(); this.clientGroup.shutdownGracefully(); } public static void main(String[] args){ ChannelFuture future = null; Server4Heatbeat server = null; try{ server = new Server4Heatbeat(); future = server.doAccept(9999); System.out.println("server started."); future.channel().closeFuture().sync(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ if(null != future){ try { future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } if(null != server){ server.release(); } } } }
import java.util.ArrayList; import java.util.List; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import utils.HeatbeatMessage; public class Server4HeatbeatHandler extends ChannelHandlerAdapter { private static List<String> credentials =new ArrayList<String>(); private static final String HEATBEAT_SUCCESS = "SERVER_RETURN_HEATBEAT_SUCCESS"; public Server4HeatbeatHandler(){ // 初始化客户端列表信息。一般通过配置文件读取或数据库读取。 credentials.add("192.168.199.222_WIN-QIUB2JF5TDP"); } // 业务处理逻辑 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof String){ this.checkCredential(ctx, msg.toString()); } else if (msg instanceof HeatbeatMessage){ this.readHeatbeatMessage(ctx, msg); } else { ctx.writeAndFlush("wrong message").addListener(ChannelFutureListener.CLOSE); } } private void readHeatbeatMessage(ChannelHandlerContext ctx, Object msg){ HeatbeatMessage message = (HeatbeatMessage) msg; System.out.println(message); System.out.println("======================================="); ctx.writeAndFlush("receive heatbeat message"); } /** * 身份检查。检查客户端身份是否有效。 * 客户端身份信息应该是通过数据库或数据文件定制的。 * 身份通过 - 返回确认消息。 * 身份无效 - 断开连接 * @param ctx * @param credential */ private void checkCredential(ChannelHandlerContext ctx, String credential){ System.out.println(credential); System.out.println(credentials); if(credentials.contains(credential)){ ctx.writeAndFlush(HEATBEAT_SUCCESS); }else{ ctx.writeAndFlush("no credential contains").addListener(ChannelFutureListener.CLOSE); } } // 异常处理逻辑 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("server exceptionCaught method run..."); // cause.printStackTrace(); ctx.close(); } }
/** * 1. 单线程组 * 2. Bootstrap配置启动信息 * 3. 注册业务处理Handler * 4. connect连接服务,并发起请求 */ import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import utils.SerializableFactory4Marshalling; public class Client4Heatbeat { // 处理请求和处理服务端响应的线程组 private EventLoopGroup group = null; // 服务启动相关配置信息 private Bootstrap bootstrap = null; public Client4Heatbeat(){ init(); } private void init(){ group = new NioEventLoopGroup(); bootstrap = new Bootstrap(); // 绑定线程组 bootstrap.group(group); // 设定通讯模式为NIO bootstrap.channel(NioSocketChannel.class); } public ChannelFuture doRequest(String host,int port) throws InterruptedException{ this.bootstrap.handler(new ChannelInitializer<SocketChannel>(){ @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(SerializableFactory4Marshalling.buildMarshallingDecoder()); ch.pipeline().addLast(SerializableFactory4Marshalling.buildMarshallingEncoder()); ch.pipeline().addLast(new Client4HeatbeatHandler()); } }); ChannelFuture future = this.bootstrap.connect(host, port).sync(); return future; } public void release(){ this.group.shutdownGracefully(); } public static void main(String[] args) { Client4Heatbeat client = null; ChannelFuture future = null; try{ client = new Client4Heatbeat(); future = client.doRequest("localhost", 9999); }catch(Exception e){ e.printStackTrace(); }finally{ if(null != future){ try { future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } if(null != client){ client.release(); } } } }
import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.hyperic.sigar.CpuPerc; import org.hyperic.sigar.FileSystem; import org.hyperic.sigar.Mem; import org.hyperic.sigar.Sigar; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; import utils.HeatbeatMessage; public class Client4HeatbeatHandler extends ChannelHandlerAdapter { //计划任务的线程池 private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private ScheduledFuture heatbeat; private InetAddress remoteAddr; private static final String HEATBEAT_SUCCESS = "SERVER_RETURN_HEATBEAT_SUCCESS"; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception{ // 获取本地INET信息 this.remoteAddr = InetAddress.getLocalHost(); // 获取本地计算机名 String computerName = System.getenv().get("COMPUTERNAME"); String credentials = this.remoteAddr.getHostAddress() + "_" + computerName; System.out.println(credentials); // 发送到服务器,作为信息比对证书 ctx.writeAndFlush(credentials); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try{ if(msg instanceof String){ if(HEATBEAT_SUCCESS.equals(msg)){ this.heatbeat = this.executorService.scheduleWithFixedDelay(new HeatbeatTask(ctx), 0L, 2L, TimeUnit.SECONDS); System.out.println("client receive - " + msg); }else{ System.out.println("client receive - " + msg); } } }finally{ ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("client exceptionCaught method run..."); // cause.printStackTrace(); // 回收资源 if(this.heatbeat != null){ this.heatbeat.cancel(true); this.heatbeat = null; } ctx.close(); } class HeatbeatTask implements Runnable{ private ChannelHandlerContext ctx; public HeatbeatTask(){ } public HeatbeatTask(ChannelHandlerContext ctx){ this.ctx = ctx; } public void run(){ try { HeatbeatMessage msg = new HeatbeatMessage(); msg.setIp(remoteAddr.getHostAddress()); Sigar sigar = new Sigar(); // CPU信息 CpuPerc cpuPerc = sigar.getCpuPerc(); Map<String, Object> cpuMsgMap = new HashMap<String,Object>(); cpuMsgMap.put("Combined", cpuPerc.getCombined()); cpuMsgMap.put("User", cpuPerc.getUser()); cpuMsgMap.put("Sys", cpuPerc.getSys()); cpuMsgMap.put("Wait", cpuPerc.getWait()); cpuMsgMap.put("Idle", cpuPerc.getIdle()); // 内存信息 Map<String, Object> memMsgMap = new HashMap<String,Object>(); Mem mem = sigar.getMem(); memMsgMap.put("Total", mem.getTotal()); memMsgMap.put("Used", mem.getUsed()); memMsgMap.put("Free", mem.getFree()); // 文件系统 Map<String, Object> fileSysMsgMap = new HashMap<String,Object>(); FileSystem[] list = sigar.getFileSystemList(); fileSysMsgMap.put("FileSysCount", list.length); List<String> msgList = null; for(FileSystem fs : list){ msgList = new ArrayList<String>(); msgList.add(fs.getDevName() + "总大小: " + sigar.getFileSystemUsage(fs.getDirName()).getTotal() + "KB"); msgList.add(fs.getDevName() + "剩余大小: " + sigar.getFileSystemUsage(fs.getDirName()).getFree() + "KB"); fileSysMsgMap.put(fs.getDevName(), msgList); } msg.setCpuMsgMap(cpuMsgMap); msg.setMemMsgMap(memMsgMap); msg.setFileSysMsgMap(fileSysMsgMap); ctx.writeAndFlush(msg); } catch (Exception e) { e.printStackTrace(); } } } }
4.6 HTTP 协议处理
使用 Netty 服务开发。实现 HTTP 协议处理逻辑
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; //http协议文件传输 public class HttpStaticFileServer { private final int port;//端口 public HttpStaticFileServer(int port) { this.port = port; } public void run() throws Exception{ EventLoopGroup bossGroup =new NioEventLoopGroup();//线程一 //这个是用于serversocketchannel的event EventLoopGroup workerGroup =new NioEventLoopGroup();//线程二//这个是用于处理accept到的channel try{ ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new HttpStaticFileServerInitializer()); b.bind(port).sync().channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8089; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8089; } new HttpStaticFileServer(port).run();//启动服务 } }
import static io.netty.handler.codec.http.HttpHeaderNames.CACHE_CONTROL; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaderNames.DATE; import static io.netty.handler.codec.http.HttpHeaderNames.EXPIRES; import static io.netty.handler.codec.http.HttpHeaderNames.IF_MODIFIED_SINCE; import static io.netty.handler.codec.http.HttpHeaderNames.LAST_MODIFIED; import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; import static io.netty.handler.codec.http.HttpMethod.GET; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; import static io.netty.handler.codec.http.HttpResponseStatus.FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED; import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.NOT_MODIFIED; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import java.io.File; import java.io.FileNotFoundException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; import java.util.regex.Pattern; import javax.activation.MimetypesFileTypeMap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelProgressiveFuture; import io.netty.channel.ChannelProgressiveFutureListener; import io.netty.channel.DefaultFileRegion; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderUtil; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.stream.ChunkedFile; import io.netty.util.CharsetUtil; /** * A simple handler that serves incoming HTTP requests to send their respective * HTTP responses. It also implements {@code 'If-Modified-Since'} header to * take advantage of browser cache, as described in * <a href="http://tools.ietf.org/html/rfc2616#section-14.25">RFC 2616</a>. * * <h3>How Browser Caching Works</h3> * * Web browser caching works with HTTP headers as illustrated by the following * sample: * <ol> * <li>Request #1 returns the content of {@code /file1.txt}.</li> * <li>Contents of {@code /file1.txt} is cached by the browser.</li> * <li>Request #2 for {@code /file1.txt} does return the contents of the * file again. Rather, a 304 Not Modified is returned. This tells the * browser to use the contents stored in its cache.</li> * <li>The server knows the file has not been modified because the * {@code If-Modified-Since} date is the same as the file's last * modified date.</li> * </ol> * * <pre> * Request #1 Headers * =================== * GET /file1.txt HTTP/1.1 * * Response #1 Headers * =================== * HTTP/1.1 200 OK * Date: Tue, 01 Mar 2011 22:44:26 GMT * Last-Modified: Wed, 30 Jun 2010 21:36:48 GMT * Expires: Tue, 01 Mar 2012 22:44:26 GMT * Cache-Control: private, max-age=31536000 * * Request #2 Headers * =================== * GET /file1.txt HTTP/1.1 * If-Modified-Since: Wed, 30 Jun 2010 21:36:48 GMT * * Response #2 Headers * =================== * HTTP/1.1 304 Not Modified * Date: Tue, 01 Mar 2011 22:44:28 GMT * * </pre> */ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; public static final String HTTP_DATE_GMT_TIMEZONE = "GMT"; public static final int HTTP_CACHE_SECONDS = 60; private final boolean useSendFile; public HttpStaticFileServerHandler(boolean useSendFile) { this.useSendFile = useSendFile; } /** * 类似channelRead方法。 */ @Override public void messageReceived( ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { if (!request.decoderResult().isSuccess()) { sendError(ctx, BAD_REQUEST); return; } if (request.method() != GET) { sendError(ctx, METHOD_NOT_ALLOWED); return; } final String uri = request.uri(); System.out.println("-----uri----"+uri); final String path = sanitizeUri(uri); System.out.println("-----path----"+path); if (path == null) { sendError(ctx, FORBIDDEN); return; } File file = new File(path); if (file.isHidden() || !file.exists()) { sendError(ctx, NOT_FOUND); return; } if (file.isDirectory()) { if (uri.endsWith("/")) { sendListing(ctx, file); } else { sendRedirect(ctx, uri + '/'); } return; } if (!file.isFile()) { sendError(ctx, FORBIDDEN); return; } // Cache Validation String ifModifiedSince = (String) request.headers().get(IF_MODIFIED_SINCE); if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince); // Only compare up to the second because the datetime format we send to the client // does not have milliseconds long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000; long fileLastModifiedSeconds = file.lastModified() / 1000; if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { sendNotModified(ctx); return; } } RandomAccessFile raf; try { raf = new RandomAccessFile(file, "r"); } catch (FileNotFoundException fnfe) { sendError(ctx, NOT_FOUND); return; } long fileLength = raf.length(); HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); //setContentLength(response, fileLength); HttpHeaderUtil.setContentLength(response, fileLength); setContentTypeHeader(response, file); setDateAndCacheHeaders(response, file); if (HttpHeaderUtil.isKeepAlive(request)) { response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE); } // Write the initial line and the header. ctx.write(response); // Write the content. ChannelFuture sendFileFuture; if (useSendFile) { sendFileFuture = ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise()); } else { sendFileFuture = ctx.write(new ChunkedFile(raf, 0, fileLength, 8192), ctx.newProgressivePromise()); } sendFileFuture.addListener(new ChannelProgressiveFutureListener() { @Override public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) { if (total < 0) { // total unknown System.err.println("Transfer progress: " + progress); } else { System.err.println("Transfer progress: " + progress + " / " + total); } } @Override public void operationComplete(ChannelProgressiveFuture future) throws Exception { System.err.println("Transfer complete."); } }); // Write the end marker ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); // Decide whether to close the connection or not. if (!HttpHeaderUtil.isKeepAlive(request)) { // Close the connection when the whole content is written out. lastContentFuture.addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); if (ctx.channel().isActive()) { sendError(ctx, INTERNAL_SERVER_ERROR); } } private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); /** * 路径解码 * @param uri * @return */ private static String sanitizeUri(String uri) { // Decode the path. try { uri = URLDecoder.decode(uri, "UTF-8"); } catch (UnsupportedEncodingException e) { try { uri = URLDecoder.decode(uri, "ISO-8859-1"); } catch (UnsupportedEncodingException e1) { throw new Error(); } } if (!uri.startsWith("/")) { return null; } // Convert file separators. uri = uri.replace('/', File.separatorChar); // Simplistic dumb security check. // You will have to do something serious in the production environment. if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) { return null; } // Convert to absolute path. return System.getProperty("user.dir") + File.separator + uri; } private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*"); private static void sendListing(ChannelHandlerContext ctx, File dir) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK); response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); StringBuilder buf = new StringBuilder(); String dirPath = dir.getPath(); buf.append("<!DOCTYPE html>\r\n"); buf.append("<html><head><title>"); buf.append("Listing of: "); buf.append(dirPath); buf.append("</title></head><body>\r\n"); buf.append("<h3>Listing of: "); buf.append(dirPath); buf.append("</h3>\r\n"); buf.append("<ul>"); buf.append("<li><a href=\"../\">..</a></li>\r\n"); for (File f: dir.listFiles()) { if (f.isHidden() || !f.canRead()) { continue; } String name = f.getName(); if (!ALLOWED_FILE_NAME.matcher(name).matches()) { continue; } buf.append("<li><a href=\""); buf.append(name); buf.append("\">"); buf.append(name); buf.append("</a></li>\r\n"); } buf.append("</ul></body></html>\r\n"); ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8); response.content().writeBytes(buffer); buffer.release(); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static void sendRedirect(ChannelHandlerContext ctx, String newUri) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND); response.headers().set(LOCATION, newUri); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse( HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8)); response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * When file timestamp is the same as what the browser is sending up, send a "304 Not Modified" * * @param ctx * Context */ private static void sendNotModified(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED); setDateHeader(response); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * Sets the Date header for the HTTP response * * @param response * HTTP response */ private static void setDateHeader(FullHttpResponse response) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); Calendar time = new GregorianCalendar(); response.headers().set(DATE, dateFormatter.format(time.getTime())); } /** * Sets the Date and Cache headers for the HTTP Response * * @param response * HTTP response * @param fileToCache * file to extract content type */ private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); // Date header Calendar time = new GregorianCalendar(); response.headers().set(DATE, dateFormatter.format(time.getTime())); // Add cache headers time.add(Calendar.SECOND, HTTP_CACHE_SECONDS); response.headers().set(EXPIRES, dateFormatter.format(time.getTime())); response.headers().set(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS); response.headers().set( LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified()))); } /** * Sets the content type header for the HTTP Response * * @param response * HTTP response * @param file * file to extract content type */ private static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap(); response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath())); } }
流数据的传输处理
在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里。
不 幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列。即使你发送了 2 个独立 的数据包,操作系统也不会作为 2 个消息处理而仅仅是作为一连串的字节而言。因此这是不 能保证你远程写入的数据就会准确地读取。所以一个接收方不管他是客户端还是服务端,都 应该把接收到的数据整理成一个或者多个更有意思并且能够让程序的业务逻辑更好理解的 数据。 在处理流数据粘包拆包时,可以使用下述处理方式: 使用定长数据处理,如:每个完整请求数据长度为 8 字节等。 (FixedLengthFrameDecoder)
使用特殊分隔符的方式处理,如:每个完整请求数据末尾使用’\0’作为数据结束标记。 (DelimiterBasedFrameDecoder) 使用自定义协议方式处理,如:http 协议格式等。