来源: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. arm64的适配问题,这次真醉了

    写过tableView的童鞋都知道,有必须的两个代理方法要实现,还有几个选择实现的. 必须实现的代理方法: ~设置行数 - (NSInteger)tableView:(UITableView *)ta ...

  2. Python学习进程(11)日期和时间

        本节介绍Python应用程序处理时间和日期的方式.其中转换日期格式是最常用的功能.     (1)获取时间戳: Python 提供了一个 time 和 calendar 模块可以用于格式化日期 ...

  3. Docker容器技术-创建一个简单的Web应用

    一.创建一个简单的Web应用 1.identicon 基于某个值而自动产生的图像,这个值是IP地址或用户名的散列值. 用途: 通过计算用户名或IP地址的散列值,在网站上提供用于识别用户的图像,以及自动 ...

  4. Tomcat8内置jdk8运行环境发布web项目

    简单说明:之前部署项目都是没有改变之前的环境变量,最近由于公司的数据源换了,jdk由1.7改成了1.8,tomcat7也改为了1.8,现在需要部署采用新数据源的这个项目, 为了不改变之前的环境变量,使 ...

  5. INSPIRED启示录 读书笔记 - 第13章 产品原则

    确定什么最重要 产品原则是对团队信仰和价值观的总结,用来指导产品团队作出正确的决策和取舍.它体现了产品团队的目标和愿景,是产品战略的重要组成部分.从形式上看,它是一系列明确的.体现团队特色的产品价值准 ...

  6. 斯坦福机器学习视频笔记 Week4 & Week5 神经网络 Neural Networks

    神经网络是一种受大脑工作原理启发的模式. 它在许多应用中广泛使用:当您的手机解释并理解您的语音命令时,很可能是神经网络正在帮助理解您的语音; 当您兑现支票时,自动读取数字的机器也使用神经网络. Non ...

  7. Centos6.5安装glusterfs3.6.2

    硬件环境Centos6.5  glusterfs3.6.2 先安装必要的包 yum install flex bison 2. 下载glusterfs3.6.2 wget http://downloa ...

  8. 快速的熟悉一个angular的项目从run看起

    config之类的都会注入到controller或者run里边

  9. 【疯了C#】神奇的换肤(二)

    昨天参照了网上的资料练习了换肤,今天进一步的实现选择换肤 其实很简单,需要实现的功能如下点击combobox中的不同项目然后面板会自动的切换到相应的界面主题. 界面如下: 下述代码参照 “张隽永” 博 ...

  10. 五一清北学堂培训之Day 3之DP

    今天又是长者给我们讲小学题目的一天 长者的讲台上又是布满了冰红茶的一天 ---------------------------------------------------------------- ...