来源:http://www.fenbi360.net/Content.aspx?id=1021&t=jc

UDP"打洞"原理

1.       NAT分类

根据Stun协议(RFC3489),NAT大致分为下面四类

1)      Full Cone

这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口.然后外网的任何发到这个打开的端口的UDP数据报都可以到达A.不管是不是C发过来的.

例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)
任何发送到 NAT(202.100.100.100:8000)的数据都可以到达A(192.168.8.100:5000)

2)      Restricted Cone

这种NAT内部的机器A连接过外网的机器C后,NAT打开一个端口.然后C可以用任何端口和A通信.其他的外网机器不行.

例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)
任何从C发送到 NAT(202.100.100.100:8000)的数据都可以到达A(192.168.8.100:5000)

3)      Port Restricted Cone

这种NAT内部的机器A连接过外网的机器C后,NAT打开一个端口.然后C可以用原来的端口和A通信.其他的外网机器不行.

例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)
C(202.88.88.88:2000)发送到 NAT(202.100.100.100:8000)的数据都可以到达A(192.168.8.100:5000)

以上三种NAT通称Cone NAT.我们只能用这种NAT进行UDP打洞.

4)      Symmetic

对于这种NAT.连接不同的外部目标.原来NAT打开的端口会变化.而Cone NAT不会.虽然可以用端口猜测.但是成功的概率很小.因此放弃这种NAT的UDP打洞.

2.       UDP hole punching

对于Cone NAT.要采用UDP打洞.需要一个公网机器C来充当”介绍人”.内网的A,B先分别和C通信.打开各自的NAT端口.C这个时候知道A,B的公网IP: Port. 现在A和B想直接连接.比如A给B发.除非B是Full Cone.否则不能通信.反之亦然.但是我们可以这样.

A要连接B.A给B发一个UDP包.同时.A让那个介绍人给B发一个命令,让B同时给A发一个UDP包.这样双方的NAT都会记录对方的IP,然后就会允许互相通信.

3.       同一个NAT后面的情况

如果A,B在同一个NAT后面.如果用上面的技术来进行互连.那么如果NAT支持loopback(就是本地到本地的转换),A,B可以连接,但是比较浪费带宽和NAT.有一种办法是,A,B和介绍人通信的时候,同时把自己的local IP也告诉服务器.A,B通信的时候,同时发local ip和公网IP.谁先到就用哪个IP.但是local ip就有可能不知道发到什么地方去了.比如A,B在不同的NAT后面但是他们各自的local ip段一样.A给B的local IP发的UDP就可能发给自己内部网里面的某某某了.

还有一个办法是服务器来判断A,B是否在一个NAT后面.(网络拓朴不同会不会有问题?)

WellKnown.cs

//WellKnown公用库
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.Net.Sockets;
using System.Collections; namespace P2PWellKnown
{
/// <summary>
/// UDP用户登录事件委托
/// </summary>
/// <param name="sender">事件源对象</param>
/// <param name="e">事件实体</param>
public delegate void UdpUserLogInDelegate(object sender, UDPSockEventArgs e); /// <summary>
/// 一般UDP消息事件委托
/// </summary>
/// <param name="sender">事件源对象</param>
/// <param name="e">事件实体</param>
public delegate void UdpMessageDelegate(object sender, UDPSockEventArgs e); /// <summary>
/// 初始化一个新连接的事件委托
/// </summary>
/// <param name="sender">事件源对象</param>
/// <param name="e">事件实体</param>
public delegate void UdpNewConnectDelegate(object sender, UDPSockEventArgs e); /// <summary>
/// P2P共享数据类
/// </summary>
public class P2PConsts
{
/// <summary>
/// UDP服务器监听端口
/// </summary>
public const int UDP_SRV_PORT = ; /// <summary>
///TCP服务器监听端口
/// </summary>
public const int TCP_SRV_PORT = ;
} /// <summary>
/// FormatterHelper 序列化,反序列化消息的帮助类
/// </summary>
public class FormatterHelper
{ public static byte[] Serialize(object obj)
{
BinaryFormatter binaryF = new BinaryFormatter();
MemoryStream ms = new MemoryStream( * );
binaryF.Serialize(ms, obj);
ms.Seek(, SeekOrigin.Begin);
byte[] buffer = new byte[(int)ms.Length];
ms.Read(buffer, , buffer.Length);
ms.Close();
return buffer;
} public static object Deserialize(byte[] buffer)
{
BinaryFormatter binaryF = new BinaryFormatter();
MemoryStream ms = new MemoryStream(buffer, , buffer.Length, false);
object obj = binaryF.Deserialize(ms);
ms.Close();
return obj;
} } /// <summary>
/// 用于承载UDPSock信息的事件类
/// </summary>
public class UDPSockEventArgs : EventArgs
{
/// <summary>
/// 要承载的消息
/// </summary>
private string m_strMsg; /// <summary>
/// 用户信息
/// </summary>
private string m_strUserName; /// <summary>
/// 触发该事件的公共终端
/// </summary>
private IPEndPoint m_EndPoint; /// <summary>
/// 初始化UDPSock事件
/// </summary>
/// <param name="sMsg">用户发送的信息</param>
public UDPSockEventArgs(string sMsg)
: base()
{
this.m_strMsg = sMsg;
} /// <summary>
/// 远端用户名
/// </summary>
public string RemoteUserName
{
get
{
return m_strUserName;
}
set
{
m_strUserName = value;
}
} /// <summary>
/// 一般套接字消息
/// </summary>
public string SockMessage
{
get
{
return m_strMsg;
}
set
{
m_strMsg = value;
}
} /// <summary>
/// 公共远端节点
/// </summary>
public IPEndPoint RemoteEndPoint
{
get
{
return m_EndPoint;
}
set
{
m_EndPoint = value;
}
}
}
}

UDPP2PSock.cs

//UDPP2PSock.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading; using P2PWellKnown; namespace UDPP
{
/// <summary>
/// UDPP2P套接字管理类
/// </summary>
public class UDPP2PSock
{
/// <summary>
/// 用户登录事件
/// </summary>
public event UdpUserLogInDelegate OnUserLogInU; /// <summary>
/// 一般UDP消息事件
/// </summary>
public event UdpMessageDelegate OnSockMessageU; /// <summary>
/// 初始化一个新连接事件
/// </summary>
public event UdpNewConnectDelegate OnNewConnectU; /// <summary>
/// UDP服务器
/// </summary>
private UdpClient m_udpServer; /// <summary>
/// UDP客户端
/// </summary>
private UdpClient m_udpClient; /// <summary>
/// 服务器实际上在本地机器上监听的
/// 端口,用于当一台计算机上同时启
/// 动两个可两以上服务器进程时,标
/// 识不同的服务器进程
/// </summary>
private int m_iMyServerPort; /// <summary>
/// 客户端在本地机器上实际使用的端口,
/// 用于当一台计算机上同时有两个或两
/// 个以上客户端进程在运行时,标识不
/// 同的客户端进程
/// </summary>
private int m_iMyClientPort; /// <summary>
/// 标识是否已成功创服务器
/// </summary>
private bool m_bServerCreated; /// <summary>
/// 标识是否已成功创建客户端
/// </summary>
private bool m_bClientCreated; /// <summary>
/// 服务器使用的线程
/// </summary>
private Thread m_serverThread; /// <summary>
/// 客户端使用的线程
/// </summary>
private Thread m_clientThread; /// <summary>
/// 打洞线程
/// </summary>
//private Thread m_burrowThread; /// <summary>
/// 远端节点
/// </summary>
private IPEndPoint m_remotePoint; /// <summary>
/// 当前进程作为客户端的公共终端
/// </summary>
private string m_strMyPublicEndPoint; /// <summary>
/// 当前进程作为客户端的私有终端
/// </summary>
private string m_strMyPrivateEndPoint; /// <summary>
/// 用于接受信息的 StringBuilder实例
/// </summary>
private StringBuilder m_sbResponse = new StringBuilder(); /// <summary>
/// P2P打洞时标识是否收到回应消息
/// </summary>
private bool m_bRecvAck = false; /// <summary>
/// 请求向其方向打洞的私有终端
/// </summary>
private IPEndPoint m_requestPrivateEndPoint; /// <summary>
/// 请求向其方向打洞的公共终端
/// </summary>
private IPEndPoint m_requestPublicEndPoint; /// <summary>
/// 打洞消息要发向的节点
/// </summary>
private ToEndPoint m_toEndPoint; /// <summary>
/// 用于标识是否已经和请求客户端建立点对连接
/// </summary>
//private bool m_bHasConnected=false ; /// <summary>
/// 创建服务器或客户端的最大尝试
/// 次数,为(65536-60000),防止
/// 因不能创建而限入死循环或使用
/// 无效端口
/// </summary>
private const int MAX_CREATE_TRY = ; /// <summary>
/// 打洞时尝试连接的最大尝试次数
/// </summary>
private const int MAX_CONNECT_TRY = ; /// <summary>
/// 构造函数,初始化UDPP2P实例
/// </summary>
public UDPP2PSock()
{
m_iMyServerPort = P2PConsts.UDP_SRV_PORT;
m_iMyClientPort = ;
m_bClientCreated = false;
m_bServerCreated = false;
m_toEndPoint = new ToEndPoint();
m_serverThread = new Thread(new ThreadStart(RunUDPServer));
m_clientThread = new Thread(new ThreadStart(RunUDPClient));
//m_burrowThread = new Thread(new ThreadStart(BurrowProc));
} /// <summary>
/// 创建UDP 服务器
/// </summary>
public void CreateUDPSever()
{
int iTryNum = ; //开始尝试创建服务器
while (!m_bServerCreated && iTryNum < MAX_CREATE_TRY)
{
try
{
m_udpServer = new UdpClient(m_iMyServerPort);
m_bServerCreated = true;
}
catch
{
m_iMyServerPort++;
iTryNum++;
}
} //创建失败,抛出异常
if (!m_bServerCreated && iTryNum == MAX_CREATE_TRY)
{
throw new Exception("创建服务器尝试失败!");
}
m_serverThread.Start(); } /// <summary>
/// 创建UDP客户端
/// </summary>
/// <param name="strServerIP"& gt;服务器IP</param>
/// <param name="iServerPort"& gt;服务器端口</param>
public void CreateUDPClient(string strServerIP, int iServerPort)
{
int iTryNum = ; //开始尝试创建服务器
while (!m_bClientCreated && iTryNum < MAX_CREATE_TRY)
{
try
{
m_udpClient = new UdpClient(m_iMyClientPort);
m_bClientCreated = true;
string strIPAddress = (System.Net.Dns.GetHostAddresses("localhost")[]).ToString();
m_strMyPrivateEndPoint = strIPAddress + ":" + m_iMyClientPort.ToString();
}
catch
{
m_iMyClientPort++;
iTryNum++;
}
} //创建失败,抛出异常
if (!m_bClientCreated && iTryNum == MAX_CREATE_TRY)
{
throw new Exception("创建客户端尝试失败!");
} IPEndPoint hostPoint = new IPEndPoint(IPAddress.Parse(strServerIP), iServerPort);
string strLocalIP = (System.Net.Dns.GetHostAddresses("localhost"))[].ToString();
SendLocalPoint(strLocalIP, m_iMyClientPort, hostPoint);
m_clientThread.Start();
} /// <summary>
/// 运行UDP 服务器
/// </summary>
private void RunUDPServer()
{
while (true)
{
byte[] msgBuffer = m_udpServer.Receive(ref m_remotePoint);
m_sbResponse.Append(System.Text.Encoding.Default.GetString(msgBuffer));
CheckCommand();
Thread.Sleep();
}
} /// <summary>
/// 运行UDP客户端
/// </summary>
private void RunUDPClient()
{
while (true)
{
byte[] msgBuffer = m_udpClient.Receive(ref m_remotePoint);
m_sbResponse.Append(System.Text.Encoding.Default.GetString(msgBuffer));
CheckCommand();
Thread.Sleep();
}
} /// <summary>
/// 销毁UDP 服务器
/// </summary>
public void DisposeUDPServer()
{
m_serverThread.Abort();
m_udpServer.Close();
} /// <summary>
/// 销毁UDP客房端
/// </summary>
public void DisposeUDPClient()
{
m_clientThread.Abort();
m_udpClient.Close();
} /// <summary>
/// 发送消息
/// </summary>
/// <param name="strMsg"& gt;消息内容</param>
/// <param name="REP"& gt;接收节点</param>
public void SendData(string strMsg, IPEndPoint REP)
{
byte[] byMsg = System.Text.Encoding.Default.GetBytes(strMsg.ToCharArray());
m_udpClient.Send(byMsg, byMsg.Length, REP);
} /// <summary>
/// 发送消息,服务器专用
/// </summary>
/// <param name="strMsg"& gt;消息内容</param>
/// <param name="REP"& gt;接收节点</param>
private void ServerSendData(string strMsg, IPEndPoint REP)
{
byte[] byMsg = System.Text.Encoding.Default.GetBytes(strMsg.ToCharArray());
m_udpServer.Send(byMsg, byMsg.Length, REP);
} /// <summary>
/// 发送本地节点信息
/// </summary>
/// <param name="strLocalIP"& gt;本地IP</param>
/// <param name="iLocalPort"& gt;本地端口</param>
public void SendLocalPoint(string strLocalIP, int iLocalPort, IPEndPoint REP)
{
string strLocalPoint = "\x01\x02" + strLocalIP + ":" + iLocalPort.ToString() + "\x02\x01";
SendData(strLocalPoint, REP);
} /// <summary>
/// 同时向指定的终端(包括公共终端和私有终端)打洞
/// </summary>
/// <param name="pubEndPoint"& gt;公共终端</param>
/// <param name="prEndPoint"& gt;私有终端</param>
/// <returns>打洞成功返回true,否则返回false</returns>
public void StartBurrowTo(IPEndPoint pubEndPoint, IPEndPoint prEndPoint)
{
Thread burrowThread = new Thread(new ThreadStart(BurrowProc));
m_toEndPoint.m_privateEndPoint = prEndPoint;
m_toEndPoint.m_publicEndPoint = pubEndPoint;
burrowThread.Start();
} /// <summary>
/// 打洞线程
/// </summary>
private void BurrowProc()
{
IPEndPoint prEndPoint = m_toEndPoint.m_privateEndPoint;
IPEndPoint pubEndPoint = m_toEndPoint.m_publicEndPoint;
int j = ;
for (int i = ; i < MAX_CONNECT_TRY; i++)
{
SendData("\x01\x07\x07\x01", prEndPoint);
SendData("\x01\x07\x07\x01", pubEndPoint); // 等待接收线程标记修改
for (j = ; j < MAX_CONNECT_TRY; j++)
{
if (m_bRecvAck)
{
m_bRecvAck = false;
SendData("\x01\x07\x07\x01", prEndPoint);
Thread.Sleep();
SendData("\x01\x07\x07\x01", pubEndPoint); UDPSockEventArgs args = new UDPSockEventArgs("");
args.RemoteEndPoint = pubEndPoint;
if (OnNewConnectU != null)
{
OnNewConnectU(this, args);
}
//Thread .Sleep (System .Threading.Timeout .Infinite );
return;
}
else
{
Thread.Sleep();
}
} //如果没有收到目标主机的回应,表明本次打
// 洞尝试失败,等待100毫秒后尝试下一次打洞
Thread.Sleep();
} //MAX_CONNECT_TRY 尝试都失败,表明打洞失败,抛出异常
//throw new Exception(" 打洞失败!");
System.Windows.Forms.MessageBox.Show("打洞失败!");////////////
} /// <summary>
/// 转发打洞请求消息,在服务器端使用
/// </summary>
/// <param name="strSrcPrEndpoint"& gt;请求转发的源私有终端</param>
/// <param name="strSrcPubEndPoint"& gt;请求转发的源公共终端</param>
/// <param name="REP"& gt;转发消息到达的目的终端</param>
public void SendBurrowRequest(string strSrcPrEndpoint, string strSrcPubEndPoint, IPEndPoint REP)
{
string strBurrowMsg = "\x04\x07" + strSrcPrEndpoint + " " + strSrcPubEndPoint + "\x07\x04";
ServerSendData(strBurrowMsg, REP);
} /// <summary>
/// 检查字符串中的命令
/// </summary>
private void CheckCommand()
{
int nPos;
string strCmd = m_sbResponse.ToString(); //如果接收远端用户名
if ((nPos = strCmd.IndexOf("\x01\x02")) > -)
{
ReceiveName(strCmd, nPos); // 反馈公共终给端远端主机
string strPubEPMsg = "\x03\x07" + m_remotePoint.ToString() + "\x07\x03";
SendData(strPubEPMsg, m_remotePoint); return;
} //如果接收我的公共终端
if ((nPos = strCmd.IndexOf("\x03\x07")) > -)
{
ReceiveMyPublicEndPoint(strCmd, nPos);
return;
} //如果是打洞请求消息
if ((nPos = strCmd.IndexOf("\x04\x07")) > -)
{
ReceiveAndSendAck(strCmd, nPos);
return;
} //如果是打洞回应消息
if ((nPos = strCmd.IndexOf("\x01\x07")) > -)
{
m_bRecvAck = true;
int nPos2 = strCmd.IndexOf("\x07\x01");
if (nPos2 > -)
{
m_sbResponse.Remove(nPos, nPos2 - nPos + );
} return;
} //一般聊天消息
m_sbResponse.Remove(, strCmd.Length);
RaiseMessageEvent(strCmd);
} /// <summary>
/// 接收远端用户名
/// </summary>
/// <param name="strCmd"& gt;包含用户名的控制信息</param>
/// <param name="nPos"></param>
private void ReceiveName(string strCmd, int nPos)
{
int nPos2 = strCmd.IndexOf("\x02\x01");
if (nPos2 == -)
{
return;
}
m_sbResponse.Remove(nPos, nPos2 - nPos + ); string strUserName = strCmd.Substring(nPos + , nPos2 - nPos - );
UDPSockEventArgs e = new UDPSockEventArgs("");
e.RemoteUserName = strUserName;
e.RemoteEndPoint = m_remotePoint; //触发用户登录事件
if (OnUserLogInU != null)
{
OnUserLogInU(this, e);
}
} /// <summary>
/// 接收打洞请求的消息并发送回应
/// </summary>
/// <param name="strCmd"></param>
/// <param name="nPos"></param>
private void ReceiveAndSendAck(string strCmd, int nPos)
{
int nPos2 = strCmd.IndexOf("\x07\x04");
if (nPos2 == -)
{
return;
}
m_sbResponse.Remove(nPos, nPos2 - nPos + ); string strBurrowMsg = strCmd.Substring(nPos + , nPos2 - nPos - ); string[] strSrcPoint = strBurrowMsg.Split(' '); //分析控制字符串包含的节点信息
string[] strPrEndPoint = strSrcPoint[].Split(':');
string[] strPubEndPoint = strSrcPoint[].Split(':');
m_requestPrivateEndPoint = new IPEndPoint(IPAddress.Parse(strPrEndPoint[]), int.Parse(strPrEndPoint[]));
m_requestPublicEndPoint = new IPEndPoint(IPAddress.Parse(strPubEndPoint[]), int.Parse(strPubEndPoint[])); //向请求打洞终端的方向打洞
StartBurrowTo(m_requestPublicEndPoint, m_requestPrivateEndPoint);
} /// <summary>
/// 接收我的公共终端
/// </summary>
/// <param name="strCmd"& gt;包含公共终端的控制信息</param>
/// <param name="nPos"& gt;控制字符串的起始位置</param>
private void ReceiveMyPublicEndPoint(string strCmd, int nPos)
{
int nPos2 = strCmd.IndexOf("\x07\x03");
if (nPos2 == -)
{
return;
}
m_sbResponse.Remove(nPos, nPos2 - nPos + ); m_strMyPublicEndPoint = strCmd.Substring(nPos + , nPos2 - nPos - );
} /// <summary>
/// 触发一般UDP消息事件
/// </summary>
/// <param name="strMsg"& gt;消息内容</param>
private void RaiseMessageEvent(string strMsg)
{
UDPSockEventArgs args = new UDPSockEventArgs("");
args.SockMessage = strMsg;
args.RemoteEndPoint = m_remotePoint;
if (OnSockMessageU != null)
{
OnSockMessageU(this, args);
}
} /// <summary>
/// 获取当前进程作为客户端的公共终端
/// </summary>
public string MyPublicEndPoint
{
get
{
return m_strMyPublicEndPoint;
}
} /// <summary>
/// 获取当前进程作为客户端的私有终端
/// </summary>
public string MyPrivateEndPoint
{
get
{
return m_strMyPrivateEndPoint;
}
}
} /// <summary>
/// 保存打洞消息要发向的节点信息
/// </summary>
class ToEndPoint
{
/// <summary>
/// 私有节点
/// </summary>
public IPEndPoint m_privateEndPoint; /// <summary>
/// 公共节点
/// </summary>
public IPEndPoint m_publicEndPoint;
}
}

关于如何使用上述程序包的一些说明:
主要程序的初始化,参考代码如下:

using UDPP;
using P2PWellKnown; //创建UDP服务器和客户端
try
{
string strServerIP = "127.0.0.1";
UDPP2PSock udpSock = new UDPP2PSock();
udpSock.OnUserLogInU += new UdpUserLogInDelegate(OnUserLogInU);
udpSock.OnNewConnectU += new UdpNewConnectDelegate(OnNewConnectU);
udpSock.CreateUDPSever();
udpSock.CreateUDPClient(strServerIP, P2PConsts.UDP_SRV_PORT);
}
catch (Exception ex)
{ }

经上面的初始化后,就可以使用类UDPP2PSock中的方法了。

注:

udpSock.OnUserLogInU +=new UdpUserLogInDelegate(OnUserLogInU);
udpSock.OnNewConnectU +=new UdpNewConnectDelegate(OnNewConnectU);
中的OnUserLogInU和OnNewConnectU是事件名称,如
private void test(object sender, UDPSockEventArgs e)
{
MessageBox.Show("ok");
}

出处:http://www.cnblogs.com/hcbin/archive/2010/04/10/1709019.html

UDP打洞原理及代码的更多相关文章

  1. Udp打洞原理和源代码。

    所谓udp打洞就是指客户端A通过udp协议向服务器发送数据包,服务器收到后,获取数据包,并且 可获取客户端A地址和端口号.同样在客户端B发送给服务器udp数据包后,服务器同样在收到B发送过来 的数据包 ...

  2. UDP 打洞 原理解释

    终于找到了一份满意的UDP打洞原理解释,附上正文,自己整理了一下源码 3.3. UDP hole punching UDP打洞技术 The third technique, and the one o ...

  3. [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)

    NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...

  4. UDP"打洞"原理

    1. NAT分类 根据Stun协议(RFC3489),NAT大致分为下面四类 1) Full Cone 这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口.然后外网的任何发到这个打开的端口 ...

  5. p2p的UDP打洞原理

    >>>>>>>>>>>>>>>>>>>>>>>>> ...

  6. UDP打洞原理介绍

     NAT穿越模块的设计与实现 Internet的快速发展以及IPv4地址数量的不足使得NAT设备得到了大规模的应用,然而这也给越来越多的端到端通信也带来了不少的麻烦.一般来说,NAT设备允许内网内主机 ...

  7. udp打洞( NAT traversal )的方法介绍

    http://www.cnblogs.com/whyandinside/archive/2010/12/08/1900492.html http://www.gzsec.com/oldversion/ ...

  8. UDP ------ UDP打洞

    为什么需要UDP打洞 处于两个不同局域网的主机不能直接进行UDP通信 UDP"打洞"原理 1.       NAT分类 根据Stun协议(RFC3489),NAT大致分为下面四类 ...

  9. P2P UPD打洞原理

    转自:http://blog.pfan.cn/fengfei/18828.html 首先先介绍一些基本概念:            NAT(Network Address             Tr ...

随机推荐

  1. BlockingQueue阻塞队列

    java.util.concurrent包: 1.Excutors类:通过这个类可获得多种线程池的实例 Excutors.newSingleThreadExecutor():获得单线程的Executo ...

  2. webpack基础配置

    webpack运行规则: Webpack 会给每个模块分配一个唯一的id并通过这个id索引和访问模块.在页面启动时,会先执行入口文件中的代码,其它模块会在运行 require 的时候再执行. 运行时主 ...

  3. Windows虚拟机安装Linux系统

    windows系统安装linux centos虚拟系统 1.下载 VMware Workstation Pro并安装,效果如图 2.下载linux系统 https://www.centos.org/d ...

  4. php数组函数-array_map()

    array_map()函数返回用户自定义函数作用后的数组.回调函数接受的参数 数目应该和传递给array_map()函数的数组数目一直. array_map(function,array1,array ...

  5. Android编译系统环境过程初始化分析【转】

    本文转载自:http://blog.csdn.net/luoshengyang/article/details/18928789 Android源代码在编译之前,要先对编译环境进行初始化,其中最主要就 ...

  6. springmvc 学习笔记1

    1.新建JAVAWEB程序,并导入jar包到工程.(IOC,SPRING MVC两部分) a.复制JAR包到lib文件夹下. b.选上所有jar包,右键后选"Bulid Path" ...

  7. juniper ssg 常用命令

    netscreen juniper ssg操作命令   2013年4月10日   命令行下取得配置信息 get config   命令行下取得相应时间设置 get clock    set vrout ...

  8. dfs枚举

    深度优先搜索(DFS,Depth-First Search)是搜索手段之一.它从某个状态开始,不断的转移状态知道无法转移,然后退回到前一步的状态,继续转移到其他状态,如此不断重复,直到找到最终的解. ...

  9. 基于netty的异步http请求

    package com.pt.utils; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; im ...

  10. http 常见的错误码

    翻译自:https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 常见错误码 一.信息 1XX (Information 1xx) ——这一类的状 ...