流媒体技术之RTSP
版权声明:本文为博主原创文章,未经博主允许不得转载。
最近对于流媒体技术比较感兴趣,虽然读书的时候学过相关方面的基础知识,但是大学上课,你懂得,一方面理论与实际脱节很严重,另一方面考试完全就是突击。学了和没学一样。好了,吐槽结束,书归正文。
研究流媒体技术的前提是先明白三个协议,RTSP,RTCP和RTP。关于这三种协议具体的定义百度上可以说是一抓一大把。总的来说, RTSP控制负责控制,包括创建,播放和暂停等操作,RTCP和RTP可以认为是一种协议,最大的区别 是RTCP中没有负载(payload,也就是媒体数据流),RTP则包含了负载。RTCP主要负责传输server和client的状态,如已经接收了多少数据,时间戳是什么,而RTP主要作用就是传输流媒体数据。
大部分对于RTSP都提到了这一个词:“RTSP是文本协议”,这句话是什么意思?通俗点说,如果你想告诉服务器你的名字,你首先构建一个类似于name="xxxxx"的字符串,然后把这个字符串转成byte[],经过SOCKET传给服务器,服务器就能够知道你的名字了。与之形成对比的是RTCP,RTCP规定了每个比特的每一位都代表什么,例如一个RTCP包的第一个比特的前两位代表版本,第三位用来填充,而第二个比特代表这次会话的序列号。坦率的说,实现RTCP协议可比RTSP烧脑多了。
回到RTSP这个话题,RTSP协议包含以下几种操作,option,describe,setup,play,pause和teardown。option是询问服务器你能提供什么方法,describe则是获取服务器的详细信息,setup是与服务器建立连接,服务器返回一个sessionid用来之后进行鉴权,play就是通知服务器可以发数据了,pause则是通知服务器暂停发数据,teardown,挥泪告别,さようなら。
如果你在百度上搜索过如下的关键字:RTSP Java。你会发现有人已经实现了RTSP协议,如果你真的使用了那份代码,恭喜你,你踩到坑啦。大部分转载的人并没有对转载的内容进行验证。我被网上的这份代码坑了号就,今天刚刚出坑,特此记录。
RTSPProtocal:RTSP协议类,主要负责创建RTSP文本
- public class RTSPProtocal {
- public static byte[] encodeOption(String address, String VERSION, int seq) {
- StringBuilder sb = new StringBuilder();
- sb.append("OPTIONS ");
- sb.append(address.substring(0, address.lastIndexOf("/")));
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq);
- sb.append("\r\n");
- sb.append("\r\n");
- System.out.println(sb.toString());
- //send(sb.toString().getBytes());
- return sb.toString().getBytes();
- }
- public static byte[] encodeDescribe(String address, String VERSION, int seq) {
- StringBuilder sb = new StringBuilder();
- sb.append("DESCRIBE ");
- sb.append(address);
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq);
- sb.append("\r\n");
- sb.append("\r\n");
- System.out.println(sb.toString());
- //send(sb.toString().getBytes());
- return sb.toString().getBytes();
- }
- public static byte[] encodeSetup(String address, String VERSION, String sessionid,
- int portOdd, int portEven, int seq, String trackInfo) {
- StringBuilder sb = new StringBuilder();
- sb.append("SETUP ");
- sb.append(address);
- sb.append("/");
- sb.append(trackInfo);
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq++);
- sb.append("\r\n");
- //"50002-50003"
- sb.append("Transport: RTP/AVP;UNICAST;client_port="+portEven+"-"+portOdd+";mode=play\r\n");
- sb.append("\r\n");
- System.out.println(sb.toString());
- System.out.println(sb.toString());
- //send(sb.toString().getBytes());
- return sb.toString().getBytes();
- }
- public static byte[] encodePlay(String address, String VERSION, String sessionid, int seq) {
- StringBuilder sb = new StringBuilder();
- sb.append("PLAY ");
- sb.append(address);
- sb.append(VERSION);
- sb.append("Session: ");
- sb.append(sessionid);
- sb.append("Cseq: ");
- sb.append(seq);
- sb.append("\r\n");
- sb.append("Range: npt=0.000-");
- sb.append("\r\n");
- sb.append("\r\n");
- System.out.println(sb.toString());
- //send(sb.toString().getBytes());
- return sb.toString().getBytes();
- }
- public static byte[] encodePause(String address, String VERSION, String sessionid, int seq) {
- StringBuilder sb = new StringBuilder();
- sb.append("PAUSE ");
- sb.append(address);
- sb.append("/");
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq);
- sb.append("\r\n");
- sb.append("Session: ");
- sb.append(sessionid);
- sb.append("\r\n");
- System.out.println(sb.toString());
- //send(sb.toString().getBytes());
- return sb.toString().getBytes();
- }
- public static byte[] encodeTeardown(String address, String VERSION, String sessionid, int seq) {
- StringBuilder sb = new StringBuilder();
- sb.append("TEARDOWN ");
- sb.append(address);
- sb.append("/");
- sb.append(VERSION);
- sb.append("Cseq: ");
- sb.append(seq);
- sb.append("\r\n");
- sb.append("User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)\r\n");
- sb.append("Session: ");
- sb.append(sessionid);
- sb.append("\r\n");
- System.out.println(sb.toString());
- return sb.toString().getBytes();
- //send(sb.toString().getBytes());
- //
- }
- }
RTSPClient:使用RTSPProtocal中的静态方法获取字符创,拥有发送和接收数据的功能
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.net.Socket;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.SocketChannel;
- import java.util.HashMap;
- import java.util.Map;
- public class RTSPClient {
- private static final int BUFFER_SIZE = 8192;
- private String localIpAddress;
- private String remoteIpAddress;
- private int localPort;
- private int localPortOdd;
- private int localPortEven;
- private int remoteIPort;
- private int remotePortOdd;
- private int remotePortEven;
- private Map<Integer, ReceiveSocket> map = new HashMap<>();
- public int getRemotePortOdd() {
- return remotePortOdd;
- }
- public void setRemotePortOdd(int remotePortOdd) {
- this.remotePortOdd = remotePortOdd;
- }
- public int getRemotePortEven() {
- return remotePortEven;
- }
- public void setRemotePortEven(int remotePortEven) {
- this.remotePortEven = remotePortEven;
- }
- public void addSocket(Integer port, ReceiveSocket socket){
- map.put(port, socket);
- }
- private String rtspAddress;
- private Socket tcpSocket;
- private SocketChannel socketChannel;
- private Selector selector;
- public String getLocalIpAddress() {
- return localIpAddress;
- }
- public void setLocalIpAddress(String localIpAddress) {
- this.localIpAddress = localIpAddress;
- }
- public int getLocalPort() {
- return localPort;
- }
- public void setLocalPort(int localPort) {
- this.localPort = localPort;
- }
- public int getLocalPortOdd() {
- return localPortOdd;
- }
- public void setLocalPortOdd(int localPortOdd) {
- this.localPortOdd = localPortOdd;
- }
- public int getLocalPortEven() {
- return localPortEven;
- }
- public void setLocalPortEven(int localPortEven) {
- this.localPortEven = localPortEven;
- }
- public String getRtspAddress() {
- return rtspAddress;
- }
- public void setRtspAddress(String rtspAddress) {
- this.rtspAddress = rtspAddress;
- }
- public Socket getTcpSocket() {
- return tcpSocket;
- }
- public void setTcpSocket(Socket tcpSocket) {
- this.tcpSocket = tcpSocket;
- }
- public String getRemoteIpAddress() {
- return remoteIpAddress;
- }
- public void setRemoteIpAddress(String remoteIpAddress) {
- this.remoteIpAddress = remoteIpAddress;
- }
- public int getRemoteIPort() {
- return remoteIPort;
- }
- public void setRemoteIPort(int remoteIPort) {
- this.remoteIPort = remoteIPort;
- }
- public Selector getSelector() {
- return selector;
- }
- public void setSelector(Selector selector) {
- this.selector = selector;
- }
- //new InetSocketAddress(
- //remoteIp, 554),
- //new InetSocketAddress("192.168.31.106", 0),
- //"rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp"
- public void inital() throws IOException{
- socketChannel = SocketChannel.open();
- socketChannel.socket().setSoTimeout(30000);
- socketChannel.configureBlocking(false);
- InetSocketAddress localAddress = new InetSocketAddress(this.localIpAddress, localPort);
- InetSocketAddress remoteAddress=new InetSocketAddress(this.remoteIpAddress, 554);
- socketChannel.socket().bind(localAddress);
- if (socketChannel.connect(remoteAddress)) {
- System.out.println("开始建立连接:" + remoteAddress);
- }
- if (selector == null) {
- // 创建新的Selector
- try {
- selector = Selector.open();
- } catch (final IOException e) {
- e.printStackTrace();
- }
- }
- socketChannel.register(selector, SelectionKey.OP_CONNECT
- | SelectionKey.OP_READ | SelectionKey.OP_WRITE, this);
- System.out.println("端口打开成功");
- }
- public void write(byte[] out) throws IOException {
- if (out == null || out.length < 1) {
- return;
- }
- System.out.println(out.toString());
- ByteBuffer sendBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
- sendBuf.clear();
- sendBuf.put(out);
- sendBuf.flip();
- if (isConnected()) {
- try {
- socketChannel.write(sendBuf);
- } catch (final IOException e) {
- }
- } else {
- System.out.println("通道为空或者没有连接上");
- }
- }
- public boolean isConnected() {
- return socketChannel != null && socketChannel.isConnected();
- }
- public byte[] receive() {
- if (isConnected()) {
- try {
- int len = 0;
- int readBytes = 0;
- ByteBuffer receiveBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
- synchronized (receiveBuf) {
- receiveBuf.clear();
- try {
- while ((len = socketChannel.read(receiveBuf)) > 0) {
- readBytes += len;
- }
- } finally {
- receiveBuf.flip();
- }
- if (readBytes > 0) {
- final byte[] tmp = new byte[readBytes];
- receiveBuf.get(tmp);
- return tmp;
- } else {
- System.out.println("接收到数据为空,重新启动连接");
- return null;
- }
- }
- } catch (final IOException e) {
- System.out.println("接收消息错误:");
- }
- } else {
- System.out.println("端口没有连接");
- }
- return null;
- }
- /*
- * 非常重要
- * */
- public void sendBeforePlay(){
- ReceiveSocket socketEven = map.get(this.localPortEven);
- ReceiveSocket socketOdd = map.get(this.localPortOdd);
- if(socketEven == null){
- socketEven = new ReceiveSocket(this.localIpAddress,this.localPortEven);
- map.put(this.localPortEven, socketEven);
- }
- if(socketOdd == null){
- socketEven = new ReceiveSocket(this.localIpAddress, this.localPortOdd);
- map.put(this.localPortOdd, socketOdd);
- }
- byte[] bytes = new byte[1];
- bytes[0]=0;
- try {
- socketEven.send(bytes, this.remoteIpAddress, this.remotePortEven);
- socketOdd.send(bytes, this.remoteIpAddress, this.remotePortOdd);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return;
- }
- public void reConnect(SelectionKey key) throws IOException {
- if (isConnected()) {
- return;
- }
- // 完成SocketChannel的连接
- socketChannel.finishConnect();
- while (!socketChannel.isConnected()) {
- try {
- Thread.sleep(300);
- } catch (final InterruptedException e) {
- e.printStackTrace();
- }
- socketChannel.finishConnect();
- }
- }
- }
ReceiveSocket:用来接收服务器发来的RTP和RTCP协议数据,只是简单地对UDP进行了包装而已
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- import java.net.InetSocketAddress;
- import java.net.SocketException;
- import java.net.UnknownHostException;
- public class ReceiveSocket implements Runnable{
- private DatagramSocket ds;
- public ReceiveSocket(String localAddress, int port){
- try {
- InetSocketAddress addr = new InetSocketAddress("192.168.31.106", port);
- ds = new DatagramSocket(addr);//监听16264端口
- } catch (SocketException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void run() {
- // TODO Auto-generated method stub
- while(true){
- byte[] buf = new byte[20];
- DatagramPacket dp = new DatagramPacket(buf,buf.length);
- try
- {
- ds.receive(dp);
- String ip = dp.getAddress().getHostAddress(); //数据提取
- String data = new String(dp.getData(),0,dp.getLength());
- int port = dp.getPort();
- System.out.println(data+"."+port+".."+ip);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- public void send(byte[] buf, String ip, int rec_port) throws IOException {
- // TODO Auto-generated method stub
- DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName(ip),rec_port);//10000为定义的端口
- ds.send(dp);
- //ds.close();
- }
- }
PlayerClient:播放类,通过不同状态之间的相互转化完成RTSP协议的交互工作。这里有一点非常关键:请注意setup这个状态,在和服务器建立连接之后,如果直接发送PLAY请求,服务器不会向指定的端口发送RTCP数据(这个问题困扰了我一晚上)。因此在发送PLAY请求之前,client接收RTCP和RTP的两个端口必须先向服务器的RTCP和RTP端口发送任意的数据,发送方式为UDP,服务器在setup操作时已经返回RTCP和RTP的端口信息。具体的实现参考sendBeforePlay()。我在网上没有找到这么操作的原因,这还是通过wireshark对VLC进行抓包才发现这个隐藏逻辑。
- import java.io.IOException;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.util.Iterator;
- public class PlayerClient {
- private RTSPClient rtspClient = new RTSPClient();
- private static final String VERSION = " RTSP/1.0\r\n";
- private static final String RTSP_OK = "RTSP/1.0 200 OK";
- private Selector selector;
- private enum Status {
- init, options, describe, setup, play, pause, teardown
- }
- private Status sysStatus = Status.init;
- private String rtspAddress = "rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp";
- private String localAddress = "192.168.31.106";
- private int localPort=0;
- private String remoteAddress = "218.204.223.237";
- private int count=0;
- private String sessionid;
- private String trackInfo;
- private boolean isSended=true;
- private int localPortOdd=50002;
- private int localPortEven=50003;
- private ReceiveSocket socket1 = new ReceiveSocket(localAddress,localPortOdd);
- private ReceiveSocket socket2 = new ReceiveSocket(localAddress,localPortEven);
- public void init(){
- rtspClient.setLocalIpAddress(localAddress);
- rtspClient.setLocalPort(localPort);
- rtspClient.setRemoteIpAddress(remoteAddress);
- rtspClient.setRemoteIPort(554);
- rtspClient.setRtspAddress(rtspAddress);
- rtspClient.setLocalPortEven(this.localPortEven);
- rtspClient.setLocalPortOdd(this.localPortOdd);
- rtspClient.addSocket(this.localPortOdd, socket1);
- rtspClient.addSocket(this.localPortEven, socket2);
- try
- {
- rtspClient.inital();
- } catch (IOException e) {
- e.printStackTrace();
- }
- this.selector = rtspClient.getSelector();
- new Thread(socket1).start();
- new Thread(socket2).start();
- }
- public void run() throws IOException{
- int seq=2;
- while(true){
- if(rtspClient.isConnected() && isSended){
- switch (sysStatus) {
- case init:
- byte[] message = RTSPProtocal.encodeOption(this.rtspAddress, this.VERSION, seq);
- this.rtspClient.write(message);
- break;
- case options:
- seq++;
- message = RTSPProtocal.encodeDescribe(this.rtspAddress, this.VERSION, seq);
- this.rtspClient.write(message);
- break;
- case describe:
- seq++;
- message = RTSPProtocal.encodeSetup(this.rtspAddress, VERSION, sessionid,
- localPortEven, localPortOdd,seq, trackInfo);
- this.rtspClient.write(message);
- break;
- case setup:
- if(sessionid==null&&sessionid.length()>0){
- System.out.println("setup还没有正常返回");
- }else{
- seq++;
- message = RTSPProtocal.encodePlay(this.rtspAddress, VERSION, sessionid, seq);
- this.rtspClient.write(message);
- }
- break;
- case play:
- count++;
- System.out.println("count: "+count);
- break;
- case pause:
- break;
- default:
- break;
- }
- isSended=false;
- }
- else{
- }
- select();
- }
- }
- private void handle(byte[] msg) {
- String tmp = new String(msg);
- System.out.println("返回内容:"+tmp);
- if (tmp.startsWith(RTSP_OK)) {
- switch (sysStatus) {
- case init:
- sysStatus = Status.options;
- System.out.println("option ok");
- isSended=true;
- break;
- case options:
- sysStatus = Status.describe;
- trackInfo=tmp.substring(tmp.indexOf("trackID"));
- System.out.println("describe ok");
- isSended=true;
- break;
- case describe:
- sessionid = tmp.substring(tmp.indexOf("Session: ") + 9, tmp
- .indexOf("Date:"));
- int index = tmp.indexOf("server_port=");
- String serverPort1 = tmp.substring(tmp.indexOf("server_port=") + 12, tmp
- .indexOf("-", index));
- String serverPort2 = tmp.substring(tmp.indexOf("-", index) + 1, tmp
- .indexOf("\r\n", index));
- this.rtspClient.setRemotePortEven(Integer.valueOf(serverPort1));
- this.rtspClient.setRemotePortOdd(Integer.valueOf(serverPort2));
- if(sessionid!=null&&sessionid.length()>0){
- sysStatus = Status.setup;
- System.out.println("setup ok");
- }
- isSended=true;
- break;
- case setup:
- sysStatus = Status.play;
- System.out.println("play ok");
- this.rtspClient.sendBeforePlay();
- this.rtspClient.sendBeforePlay();
- isSended=true;
- break;
- case play:
- //sysStatus = Status.pause;
- System.out.println("pause ok");
- isSended=true;
- break;
- case pause:
- sysStatus = Status.teardown;
- System.out.println("teardown ok");
- isSended=true;
- //shutdown.set(true);
- break;
- case teardown:
- sysStatus = Status.init;
- System.out.println("exit start");
- isSended=true;
- break;
- default:
- break;
- }
- } else {
- System.out.println("返回错误:" + tmp);
- }
- }
- private void select() {
- int n = 0;
- try
- {
- if (selector == null) {
- return;
- }
- n = selector.select(1000);
- } catch (final Exception e) {
- e.printStackTrace();
- }
- // 如果select返回大于0,处理事件
- if (n > 0) {
- for (final Iterator<SelectionKey> i = selector.selectedKeys()
- .iterator(); i.hasNext();) {
- // 得到下一个Key
- final SelectionKey sk = i.next();
- i.remove();
- // 检查其是否还有效
- if (!sk.isValid()) {
- continue;
- }
- if (sk.isReadable()) {
- byte[] message = rtspClient.receive();
- handle(message);
- }
- if (sk.isConnectable()) {
- try {
- rtspClient.reConnect(sk);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
Test:测试类
- public class Test {
- public static void main(String[] args){
- PlayerClient player = new PlayerClient();
- player.init();
- try
- {
- player.run();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
只要在ReceiveSocket的run方法中打断点,你就会发现源源不断的数据向你发来,是不是感觉很爽,哈哈哈。
流媒体技术之RTSP的更多相关文章
- 流媒体技术 rtp/rtcp/rtsp资料精华!
流媒体技术 rtp/rtcp/rtsp资料精华! 流媒体技术 流媒体是指在网络中使用流式(Sreaming)传输技术进行传输的连续时基媒体.如音频数据流或视频数据流,而不是一种新的媒体.流媒体技 ...
- 基于EasyNVR摄像机流媒体服务器实现RTSP或Onvif监控摄像头Web无插件化直播监控
前言介绍 随着互联网的发展,尤其是移动互联网基于H5.微信的应用越来越多,企业也更多地想基于H5.微信公众号来快速开发和运营自己的产品,而传统的安防IPC所输出的各种RTSP.GB28181.SDK视 ...
- [总结]RTMP流媒体技术零基础学习方法
本文主要总结一些我在学习RTMP流媒体技术过程中积累的经验.也为后来学习RTMP流媒体技术的人们一个参考.本文力图从简到难,循序渐进的介绍RTMP流媒体技术的方方面面,先从应用说起,逐步深化剖析相关工 ...
- javaCV开发详解之3:收流器实现,录制流媒体服务器的rtsp/rtmp视频文件(基于javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- 流媒体技术学习笔记之(八)海康、大华IpCamera RTSP地址和格式
海康: rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream 说明: username: 用户名 ...
- 流媒体技术学习笔记之(一)nginx+nginx-rtmp-module+ffmpeg搭建流媒体服务器
参照网址: [1]http://blog.csdn.net/redstarofsleep/article/details/45092147 [2]HLS介绍:http://www.cnblogs.co ...
- 流媒体协议RTMP,RTSP与HLS有什么不同
转载自:http://www.cuplayer.com/player/PlayerCode/Wowza/2015/0204/1774.html HLS (HTTP Live Streaming) Ap ...
- EasyDarwin开源流媒体服务器实现RTSP直播同步输出MP4、RTMP、HLS的方案思路
背景 近期跟开源团队商量,想在EasyDarwin上继续做一些功能扩展,目前EasyDarwin开源流媒体服务器只能够实现高效的RTSP推流直播转发/分发功能,输入与输出都是RTSP/RTP流,不能够 ...
- 流媒体技术的应用,如何搭建一个SimpleNVR流媒体服务系统
Onvif/RTSP流媒体服务 SimpleNVR Onvif/RTSP流媒体服务是一款软硬一体音视频流媒体服务软件.它是在5G.AI.云计算.大数据.物联网等网络技术大规模商用后,用户要求视频随时随 ...
随机推荐
- Windows7下Blend for Visual Studio 2012使用问题
目前开发的系统里很多控件样式和动画比较复杂,应该是之前同事用Blend做的,这种神器不用太浪费了,自己也准备试试. 系统环境Windows7+Visual Studio 2012 1.Windows7 ...
- iOS 公司开发者账号申请
苹果开发者账号分三种. 个人账号:个人申请用于开发苹果app所使用的账号,仅限于个人使用,申请比较容易,$99. 公司账号:以公司的名义申请的开发者账号,用于公司内部的开发者共用,$99. 企业账号: ...
- android 短信助手demo
关于意图Intent: 显式意图:必须指定要激活的组件的完整包名和类名(应用程序之间耦合在一起) 一般激活自己应用的组件的时候采用显式意图 隐式意图:只需要指定动作和数据就可以(好处是应用程序之间没有 ...
- linux+jre+apache+mysql+tomcat调优
一.不再为Apache进程淤积.耗尽内存而困扰 0. /etc/my.cnf,在mysqld那一段加上如下一行: log-slow-queries=queries-slow.log 重启MySQL 酌 ...
- redis高可用之REDIS SENTINEL
1. Redis主从配置 1.1. 设置主从复制 Master <= Salve 10.24.6.5:6379 <= 10.24.6.7:6379 1.2. 取消主从复制 1.3. ...
- linux下修改系统时间
一.查看时间: [root@localhost ~]# date2016年 11月 19日 星期六 12:46:37 CST 二.修改时间,修改系统时间 [root@localhost ~]# dat ...
- linux路由表命令
转自此大神http://www.cnblogs.com/gunl/archive/2010/09/14/1826234.html 留在好查阅 linux 路由表维护 查看 Linux 内核路由表 使用 ...
- c# 其他技术学习
1.注册表编辑 为了方便对注册表进行操作,.NET提供了Registry类和RegistryKey类 2.API函数的应用 (1)自定义特性的代码:在类.属性.方法的上方加上“[]”的代码 (2)有个 ...
- Java异常信息处理
import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import org.jun ...
- 续Gulp使用入门三步压缩CSS
gulp 压缩css 一.安装 gulp-minify-css 模块 提示:你需要使用命令行的 cd 切换到对应目录后进行安装操作. 在命令行输入 npm install gulp-minify-cs ...