C#实现UDP打洞

转自:http://hi.baidu.com/sdfiyon/blog/item/63a6e039155e02f23a87ceb1.html

下面是UDP打洞程序包的源码:
//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     = 2280;

/// <summary>
     ///TCP服务器监听端口
     /// </summary>
     public const int TCP_SRV_PORT =2000;
}

/// <summary>
/// FormatterHelper 序列化,反序列化消息的帮助类
/// </summary>
public class FormatterHelper

{

public static byte[] Serialize(object obj)

{

BinaryFormatter binaryF = new BinaryFormatter();

MemoryStream ms = new MemoryStream(1024*10);

binaryF.Serialize(ms, obj);

ms.Seek(0, SeekOrigin.Begin);

byte[] buffer = new byte[(int)ms.Length];

ms.Read(buffer, 0, buffer.Length);

ms.Close();

return buffer;

}

public static object Deserialize(byte[] buffer)

{

BinaryFormatter binaryF = new BinaryFormatter();

MemoryStream ms = new MemoryStream(buffer, 0, 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
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using P2PWellKnown;

namespace UDPP2P
{
       /// <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 = 5536;

/// <summary>
           /// 打洞时尝试连接的最大尝试次数
           /// </summary>
           private const int MAX_CONNECT_TRY = 10;

/// <summary>
           /// 构造函数,初始化UDPP2P实例
           /// </summary>
           public UDPP2PSock()
           {
               m_iMyServerPort = P2PConsts.UDP_SRV_PORT;
               m_iMyClientPort = 60000;
               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=0;

//开始尝试创建服务器
               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">服务器IP</param>
           /// <param name="iServerPort">服务器端口</param>
           public void CreateUDPClient(string strServerIP,int iServerPort)
           {
               int iTryNum = 0;

//开始尝试创建服务器
               while (!m_bClientCreated     && iTryNum < MAX_CREATE_TRY)
               {
                   try
                   {
                       m_udpClient     = new UdpClient(m_iMyClientPort );
                       m_bClientCreated     = true;
                       string strIPAddress = (System.Net.Dns.GetHostAddresses("localhost")[0]).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"))[0].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(10);
               }
           }

/// <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(10);
               }
           }

/// <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">消息内容</param>
           /// <param name="REP">接收节点</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">消息内容</param>
           /// <param name="REP">接收节点</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">本地IP</param>
           /// <param name="iLocalPort">本地端口</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">公共终端</param>
          /// <param name="prEndPoint">私有终端</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 = 0;
              for (int i = 0; i < MAX_CONNECT_TRY; i++)
              {
                  SendData("/x01/x07/x07/x01", prEndPoint);
                  SendData("/x01/x07/x07/x01", pubEndPoint);

//等待接收线程标记修改
                  for (j = 0; j < MAX_CONNECT_TRY; j++)
                  {
                      if (m_bRecvAck)
                      {
                          m_bRecvAck = false;
                          SendData("/x01/x07/x07/x01", prEndPoint);
                          Thread.Sleep(50);
                          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);
                      }
                  }

//如果没有收到目标主机的回应,表明本次打
                  //洞尝试失败,等待100毫秒后尝试下一次打洞
                  Thread.Sleep(100);
              }

//MAX_CONNECT_TRY尝试都失败,表明打洞失败,抛出异常
              //throw new Exception("打洞失败!");
              System.Windows.Forms.MessageBox.Show("打洞失败!");
          }

/// <summary>
          /// 转发打洞请求消息,在服务器端使用
          /// </summary>
          /// <param name="strSrcPrEndpoint">请求转发的源私有终端</param>
          /// <param name="strSrcPubEndPoint">请求转发的源公共终端</param>
          /// <param name="REP">转发消息到达的目的终端</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")) > -1)
              {
                  ReceiveName(strCmd, nPos);
                
                  //反馈公共终给端远端主机
                  string strPubEPMsg = "/x03/x07" + m_remotePoint.ToString() + "/x07/x03";
                  SendData(strPubEPMsg, m_remotePoint);

return;
              }

//如果接收我的公共终端
              if ((nPos = strCmd.IndexOf("/x03/x07")) > -1)
              {
                  ReceiveMyPublicEndPoint(strCmd, nPos);
                  return;
              }

//如果是打洞请求消息
              if ((nPos = strCmd.IndexOf("/x04/x07")) > -1)
              {
                  ReceiveAndSendAck(strCmd, nPos);
                  return;
              }

//如果是打洞回应消息
              if ((nPos =strCmd .IndexOf ("/x01/x07"))>-1)
              {
                  m_bRecvAck = true;
                  int nPos2 = strCmd.IndexOf("/x07/x01");
                  if (nPos2 > -1)
                  {
                      m_sbResponse.Remove(nPos, nPos2 - nPos + 2);
                  }

/*
                  if (m_requestPublicEndPoint != null)
                  {
                      if (!m_bHasConnected)
                      {
                          m_bHasConnected = true;
                          UDPSockEventArgs args = new UDPSockEventArgs("");
                          args.RemoteEndPoint = m_requestPublicEndPoint;
                          if (OnNewConnectU != null)
                          {
                              OnNewConnectU(this, args);
                          }
                          m_requestPublicEndPoint = null;
                      }
                  }*/

return;
              }

//一般聊天消息
              m_sbResponse.Remove(0, strCmd.Length);
              RaiseMessageEvent(strCmd);
          }

/// <summary>
          /// 接收远端用户名
          /// </summary>
          /// <param name="strCmd">包含用户名的控制信息</param>
          /// <param name="nPos"></param>
          private void ReceiveName(string strCmd, int nPos)
          {
              int nPos2 = strCmd.IndexOf("/x02/x01");
              if (nPos2 == -1)
              {
                  return;
              }
              m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

string strUserName = strCmd.Substring(nPos + 2, nPos2 - nPos - 2);
              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 == -1)
              {
                  return;
              }
              m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

string strBurrowMsg = strCmd.Substring(nPos + 2, nPos2 - nPos - 2);

string[] strSrcPoint = strBurrowMsg.Split(' ');

//分析控制字符串包含的节点信息
              string[] strPrEndPoint = strSrcPoint[0].Split(':');
              string[] strPubEndPoint = strSrcPoint[1].Split(':');
              m_requestPrivateEndPoint    = new IPEndPoint(IPAddress.Parse(strPrEndPoint[0]), int.Parse(strPrEndPoint[1]));
              m_requestPublicEndPoint    = new IPEndPoint(IPAddress.Parse(strPubEndPoint[0]), int.Parse(strPubEndPoint[1]));

//向请求打洞终端的方向打洞
              StartBurrowTo(m_requestPublicEndPoint, m_requestPrivateEndPoint);
          }

/// <summary>
          /// 接收我的公共终端
          /// </summary>
          /// <param name="strCmd">包含公共终端的控制信息</param>
          /// <param name="nPos">控制字符串的起始位置</param>
          private void ReceiveMyPublicEndPoint(string strCmd, int nPos)
          {
              int nPos2 = strCmd.IndexOf("/x07/x03");
              if (nPos2 == -1)
              {
                  return;
              }
              m_sbResponse.Remove(nPos, nPos2 - nPos + 2);

m_strMyPublicEndPoint=strCmd.Substring(nPos + 2, nPos2 - nPos - 2);
          }

/// <summary>
          /// 触发一般UDP消息事件
          /// </summary>
          /// <param name="strMsg">消息内容</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;
      }

}

关于如何使用上述程序包的一些说明:
           主要程序的初始化,参考代码如下:
              //创建UDP服务器和客户端
              try
              {
                  string strServerIP="127.0.0.1"
                  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)
              {

}

UDP打洞程序包的源码相关推荐

  1. 二开微信表情包小程序魔改版源码

    简介: 二开微信表情包小程序魔改版源码内附图文安装教程 源码包括俩个版本,一个之前发过黄色版本,一个是二开魔改版. 安装搭建就不说了,源码内打包好了 网盘下载地址: http://kekewl.cc/ ...

  2. 微信小程序:云开发表情包制作源码

    该款小程序是一个表情包制作 内容毕竟丰富,另外自定义制作方面也是特别的自由 支持自主上传图片,自定义文章,另外拥有多种素材模板以供选择 这是一款云开发的小程序,但是安装还是挺简单的 搭建教程: 首先使 ...

  3. 最新版南风表情包小程序独立版源码 独家最火表情包小程序源码+完整后台API+小程序前端

    南风表情包小程序独立版源码 独家最火表情包小程序源码+完整后台API+小程序前端 南风表情包小程序源码,独家分享网传最火表情包小程序源码,带有独立版完整后台 API.小程序前端,并附带安装搭建说明.最 ...

  4. Linux-什么是二进制包,源码包,RPM包,软件仓库

    博文说明[前言]: 本文将通过个人口吻介绍什么是二进制包,RPM包,源码RPM包(SRPM包),源码包,以及RPM常用命令,源码rpm的安装(*.src.rpm),源码包的安装步骤知识(./confi ...

  5. MobileIMSDK怎样修改服务端核心jar包的源码并替换掉Java服务端的jar包

    场景 MobileIMSDK怎样将Java服务端运行起来以及打成jar包运行: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/11 ...

  6. Eclipse中怎样修改jar包的源码并将原jar包替换

    场景 在某Java Application中,所引用的lib下的某jar包,知道其源码,怎样修改其源码,然后将项目中的jar包替换为修改后的jar包. 注: 博客主页: https://blog.cs ...

  7. php表白树洞怎么写,树洞表白墙免费源码版

    树洞表白墙免费源码版是一款实用工具,这款软件的主要功能就是制作属于你的表白情书,发送给你爱慕已久的对象,如果你因为羞涩不敢口头表白,使用这款软件不能说百分百成功,但至少是一个表白的途径. 软件介绍 树 ...

  8. 开发 web 程序服务 之 源码分析

    文章目录 开发 web 程序服务 之 源码分析 前言 http 包源码 路由部分 监听和服务部分 mux 库源码 源码分析 创建路由 路由匹配 总结 开发 web 程序服务 之 源码分析 前言 本文的 ...

  9. 怎样加入� android private libraries 中的包的源码

    先上图: 这里以加入� afinal_0.5.1_bin.jar 为例. 第一步:加入�jar包到libs里面,系统自己主动把jar载入到android private libraries中: 第二步 ...

最新文章

  1. IDEA 解决 Java8 的数据流问题,用过的都说好!!!
  2. 基于 Java 2 运行时安全模型的线程协作--转
  3. Winform中实现中文验证码(附代码下载)
  4. 程序员面试题准备(1)单链表逆置
  5. ExtJS中给Tree节点加click事件
  6. Activiti绩效对决
  7. java android aes加密解密_AES加密解密在JAVA和ANDROID下互通
  8. Redis之跳跃表实现
  9. 项目报告制作-中型无盘网吧
  10. [Java] POJ 2387 最短路问题
  11. 概念学习(学习笔记)
  12. 计算机组成原理中CPI、MIPS、CPU执行时间、主频等计算
  13. U盘量产后USB鼠标和键盘都无法使用,如何解决?
  14. 一位过来人:也谈谈体制内 • 体制外
  15. 苹果系统下载了python软件怎么卸载_mac怎么卸载pycharm
  16. 2018大数据就业前景怎么样
  17. EMOTET,URSNIF,DRIDEXBitPaymer之间的关联性分析
  18. 罗斯公司理财第11版笔记和课后习题答案
  19. 用智能人脸识别门禁管理工地更严谨考勤更方便
  20. Label立体字体效果

热门文章

  1. C++控制向文件中写入浮点数的格式
  2. ruby 生成随机字符串_Ruby程序生成随机数
  3. getdate函数_PHP getdate()函数与示例
  4. Java——集合的遍历之迭代遍历
  5. a letter and a number(一封信和一个数字)
  6. 【汇编语言】除法(DIV/IDIV)
  7. python合并两个文本文件内容_用Python 将两个文件的内容合并成一个新的文件.
  8. app调html页面,app界面管理(风格色调).html
  9. uva 10692——Huge Mods
  10. linux 常用命令04 查找和检索