C#的UDP打洞试验

该博客给出了C#实现客户端与服务器通信的代码。定义了用户类,包含用户名和网络地址属性。客户端和服务器端代码中设置了多种通信命令,如登录、下线、请求用户列表等,还涉及UDP打洞相关操作,但作者表示试验多次未成功,需检查代码。

本人技术有限,可能代码很烂~~令各位看官忍不住想骂。。。我希望个为看官们可以原谅我这位菜鸟,有指教可以留言发表,但是请不要恶语伤害我幼小的心灵。

公共代码:

 /// <summary>
 /// 用户类
 /// </summary>
 public class User
 {
  /// <summary>
  /// 用户名
  /// </summary>
  private string name = "";

  /// <summary>
  /// 用户的所在网络地址
  /// </summary>
  private EndPoint location = null;

  /// <summary>
  /// 用户名属性
  /// </summary>
  public string Name
  {
   get{ return name;}
  }

  /// <summary>
  /// 用户地址属性
  /// </summary>
  public EndPoint Location
  {
   get{ return location;}
  }

  /// <summary>
  /// 返回一个用户实例
  /// </summary>
  /// <param name="_name">用户名</param>
  /// <param name="_location">用户地址</param>
  public User(string _name, EndPoint _location)
  {
   name = _name;
   location = _location;
  }
 }
}

客户端程序代码:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections;

namespace Client
{
 /// <summary>
 /// Class1 的摘要说明。
 /// </summary>
 class ClientApp
 {
  #region 参数...

  /*客户端向服务器*/
  const string LOGIN  = "11";//客户端登录
  const string LOGOUT  = "12";//客户端下线
  const string GETLIST = "13";//请求用户列表
  const string P2PCON  = "14";//请求P2P连接

  /*服务器向客户端*/
  const string NONUSER  = "21";//请求连接的用户不存在
  const string RENAME   = "22";//登录用户名已经存在
  const string USERLIST = "23";//在线用户列表
  const string MAKHOLD  = "24";//UDP打洞命令
  const string LOGINOK  = "25";//登陆成功
  const string SERVCLS  = "26";//服务器关闭
  const string MSGEND   = "27";//消息结束

  /*客户端向客户端*/
  const string P2PMSG = "31";//客户端消息
  const string HOLDMG = "32";//UDP打洞消息
  const string RECMSG = "33";//消息应答
  const string PINGMG = "34";//ping客户端

  /*网络参数*/
  const int MaxTry = 5;//消息发送的重试次数

  /*客户端命令行*/
  const string GETUSERS = "getu";//获取在线用户列表
  const string SENDMSG = "send";//发送消息给某个客户端
  const string EXIT = "exit";//退出程序
  const string PING = "ping";
  const string USERS = "user";//打印在线用户
  const string HELP = "help";//帮助

  /*程序变量*/
  static bool more = true;
  static bool ok = true;
  static string Username = "";//用户名
  static ArrayList users = new ArrayList();
  static Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  static IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse("222.76.56.239"), 8888);
  static Thread listenThr = new Thread(new ThreadStart(Listen));

  #endregion

  #region 方法...

  static private void Listen()
  {
   while(true)
   {
    int recv = 0;//接收到的消息字节数

    byte[] data = new byte[1024];//消息缓冲区

    IPEndPoint sender = new IPEndPoint(IPAddress.Any,0);
    EndPoint remote = (EndPoint)sender;//消息来源的远程地址

    recv = sock.ReceiveFrom(data, ref remote);
   
    string head = Encoding.Unicode.GetString(data,0,4);//获取消息头两个字节的标记
   
    switch(head)
    {
     case RECMSG:
      ok = true;
      break;

     case USERLIST:
      GetUserList(data, recv);
      break;

     case MSGEND:
      more = false;
      break;

     case SERVCLS:
      Console.WriteLine("/n服务器已关闭!/n");
      Console.Write("#Command:");
      break;

     case MAKHOLD:
      string recever = Encoding.Unicode.GetString(data, 4,20);

      int index = FindUser(recever);

      if(index != -1)
      {
       sock.SendTo(Encoding.Unicode.GetBytes(HOLDMG),((User)users[index]).Location);
      }

      break;

     case P2PMSG:
      Console.WriteLine("/n收到来自" +  Encoding.Unicode.GetString(data,4, 20) +
           "的消息:" + Encoding.Unicode.GetString(data,24, recv - 24));

      sock.SendTo(Encoding.Unicode.GetBytes(RECMSG), remote);

      Console.Write("/n#Command:");
      break;

     case HOLDMG:
      break;

     case PINGMG:
      break;
    }
   }
  }

  /*用户登录*/
  static string UserLogIn()
  {
   IPEndPoint sender = new IPEndPoint(IPAddress.Any,0);
   EndPoint remote = (EndPoint)sender;//消息来源的远程地址

   Console.Write("请输入一个用户名:");

   string username = Console.ReadLine();

   byte[] temp = Encoding.Unicode.GetBytes(username);

   if(temp.Length > 20)
   {
    Console.WriteLine("用户名不能超过20个字节!/n");
    return "";
   }
  
   username = username.PadRight(10,' ');
   
   byte[] data = Encoding.Unicode.GetBytes(LOGIN + username.ToUpper());

   sock.SendTo(data,serverEP);

   //TODO:记得在此添加超时处理
   int recv = sock.Receive(data);

   if(Encoding.Unicode.GetString(data,0, recv) == LOGINOK)
   {
    Console.WriteLine("登陆成功!/n");
    ShowHelp();
    Console.WriteLine();

    return username;
   }
   else
   {
    Console.WriteLine("登陆失败,用户名已存在!/n");
    return "";
   }
  }

  /*发送消息*/
  static private bool SendMsg(string name, string msg)
  {
   bool result = false;

   name = name.PadRight(10,' ');

   int index = FindUser(name);

   if(index != -1)
   {
    byte[] toServ = Encoding.Unicode.GetBytes(P2PCON + name + Username);
    byte[] toClient = Encoding.Unicode.GetBytes(P2PMSG + Username + msg);

    ok = false;

    for(int i=0; i<MaxTry; i++)
    {
     if(ok)
      break;

     sock.SendTo(toClient,((User)users[index]).Location);

     Thread.Sleep(1000);
    }

    for(int i=0; i<MaxTry; i++)
    {
     if(ok)
      break;

     sock.SendTo(toServ, serverEP);

     Thread.Sleep(1000);

     sock.SendTo(toClient,((User)users[index]).Location);
    }

    if(!ok)
    {
     Console.WriteLine("消息发送失败。/n");
    }
    else
    {
     Console.WriteLine("消息发送成功。/n");
    }
   }
   else
   {
    Console.WriteLine("用户不存在。/n");
   }

   return result;
  }

  /*获取用户列表*/
  static private void GetUserList(byte[] data, int recv)
  {
   string userdata = Encoding.Unicode.GetString(data, 4, recv-4);
   
   string username = userdata.Substring(0,10);

   if(FindUser(username) == -1)
   {
    int portIndex = userdata.IndexOf(':');

    string address = userdata.Substring(10, userdata.Length - (userdata.Length - portIndex) - 10);

    IPEndPoint userEP = new IPEndPoint(IPAddress.Parse(address), int.Parse(userdata.Substring(portIndex+1)));

    EndPoint location = (EndPoint)userEP;

    users.Add(new User(username, location));
   }
  }

  /*打印在线用户名单*/
  static private void PrintfUsers()
  {
   if(users.Count != 0)
   {
    Console.WriteLine("在线用户:");

    for(int i=0; i<users.Count; i++)
    {
     Console.WriteLine(((User)users[i]).Name + "/t/t" +((User)users[i]).Location.ToString());
    }
       
    Console.WriteLine("/n");
   }
   else
   {
    Console.WriteLine("目前没有在线用户/n");
   }
  }

  /*查找用户*/
  static private int FindUser(string _name)
  {
   int result = -1;

   for(int i=0; i<users.Count; i++)
   {
    if(((User)users[i]).Name == _name)
    {
     result = i;

     break;
    }
   }

   return result;
  }

  static void WaitUserList()
  {
   byte[] data = Encoding.Unicode.GetBytes(GETLIST);

   sock.SendTo(data, serverEP);

   more = true;

   for(int i=0; i<MaxTry; i++)
   {
    if(!more)
     break;

    Thread.Sleep(1000);
   }

   if(more)
   {
    Console.WriteLine("获取用户列表失败!服务器超时!/n");
   }
   else
   {
    PrintfUsers();
   }
  }

  static void ShowHelp()
  {
   Console.WriteLine("/t/t---------------------------------");
   Console.WriteLine("/t/t|   GETU 获取在线用户列表       |");
   Console.WriteLine("/t/t|   SEND 发送消息给某个客户端   |");
   Console.WriteLine("/t/t|   USER 打印在线用户           |");
   Console.WriteLine("/t/t|   EXIT 退出程序               |");
   Console.WriteLine("/t/t---------------------------------");
  }

  #endregion

  /// <summary>
  /// 应用程序的主入口点。
  /// </summary>
  [STAThread]
  static void Main(string[] args)
  {
   bool loop = true;

   //byte[] data = new byte[1024];

   while(loop)
   {
    Console.Write("请输入服务器IP:");
    string ip = Console.ReadLine();

    try
    {
     serverEP = new IPEndPoint(IPAddress.Parse(ip), 8888);
     loop = false;
    }
    catch
    {
     Console.Write("请输入IP不正确。/n");
     loop = true;
    }
   }

   loop = true;

   /*用户登录*/
   while(Username == "")
   {
    Username = UserLogIn();
   }

   listenThr.Start();

   while(loop)
   {
    Console.Write("#Command:");

    string input = Console.ReadLine();

    if(input.Length < 4)
    {
     Console.WriteLine("'" + input + "'" + "不是有效命令!/n");
     continue;
    }

    switch(input.Substring(0,4).ToLower())
    {
     case GETUSERS:
      if(input.Length == 4)
      {
       WaitUserList();
      }
      else
      {
       Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      }

      break;

     case USERS:
      if(input.Length == 4)
      {
       PrintfUsers();
      }
      else
      {
       Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      }
      break;

     case SENDMSG:
      if(input.Length > 5  && input[4] == ' ')
      {
       int Nindex = input.IndexOf(' ',5);
       
       if(Nindex < 0 || Nindex == input.Length-1)
       {
        Console.WriteLine("Send命令格式应当如下:");
        Console.WriteLine("SEND [用户名] [消息内容]/n");
        break;
       }

       string name = input.Substring(5,Nindex-5).ToUpper();
       string msg = input.Substring(Nindex+1);

       SendMsg(name, msg);       
      }
      else
      {
       Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      }

      break;

     case EXIT:
      if(input.Length==4)
      {
       loop = false;

       byte[] temp = Encoding.Unicode.GetBytes(Username);
       byte[] data = Encoding.Unicode.GetBytes(LOGOUT + temp.Length.ToString("0#") + Username.ToUpper());

       listenThr.Abort();

       sock.SendTo(data, serverEP);
       sock.Close();
      }
      else
      {
       Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      }

      break;

     case PING:
      if(input.Length > 5 && input[4] == ' ')
      {
       Console.WriteLine("命令完成。/n");
      }
      else
      {
       Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      }

      break;

     case HELP:
      ShowHelp();
      break;
     default:
      Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      break;
    }
   }
  }
 }

 服务器端程序代码:

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Server
{
 /// <summary>
 /// Class1 的摘要说明。
 /// </summary>
 class ServerApp
 {
  #region 参数...

  /*客户端向服务器*/
  const string LOGIN  = "11";//客户端登录
  const string LOGOUT  = "12";//客户端下线
  const string GETLIST = "13";//请求用户列表
  const string P2PCON  = "14";//请求P2P连接

  /*服务器向客户端*/
  const string NONUSER  = "21";//请求连接的用户不存在
  const string RENAME   = "22";//登录用户名已经存在
  const string USERLIST = "23";//在线用户列表
  const string MAKHOLD  = "24";//UDP打洞命令
  const string LOGINOK  = "25";//登陆成功
  const string SERVCLS  = "26";//服务器关闭
  const string MSGEND   = "27";//消息结束

  /*服务器命令行*/
  const string EXIT  = "exit";//退出程序
  const string USERS = "user";//打印在线用户

  /*程序变量*/
  static ArrayList users = new ArrayList();//用户列表
  static Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  static IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8888);
  static Thread listenThr = new Thread(new ThreadStart(Listen));

  #endregion

  #region 方法...
  static private void Listen()
  {
   while(true)
   {
    int recv = 0;//接收到的消息字节数

    byte[] data = new byte[1024];//消息缓冲区

    IPEndPoint sender = new IPEndPoint(IPAddress.Any,0);
    EndPoint remote = (EndPoint)sender;//消息来源的远程地址

    recv = sock.ReceiveFrom(data, ref remote);

    string head = Encoding.Unicode.GetString(data,0,4);//获取消息头两个字节的标记

    switch(head)
    {
     case LOGIN:
      UserLogIn(data, remote);
      break;

     case LOGOUT:
      UserLogOut(data);
      break;

     case GETLIST:
      GetUserList(remote);
      break;

     case P2PCON:
      PeerConnect(data, remote);
      break;
    }
   }
  }

  /*查找用户*/
  static private int FindUser(string _name)
  {
   int result = -1;

   for(int i=0; i<users.Count; i++)
   {
    if(((User)users[i]).Name == _name)
    {
     result = i;

     break;
    }
   }

   return result;
  }

  /*用户登录*/
  static private void UserLogIn(byte[] data, EndPoint _remote)
  {
   bool error = false;

   /*新登记的用户*/
   User newuser = new User(Encoding.Unicode.GetString(data,4,20), _remote);

   /*判断用户是否重名*/
   error = (FindUser(newuser.Name) != -1);

   if(!error)
   {
    users.Add(newuser);

    Console.WriteLine("/n用户 " + newuser.Name + " 登陆, 地址:"+ newuser.Location.ToString() + "/n");
    Console.Write("#Command:");

    sock.SendTo(Encoding.Unicode.GetBytes(LOGINOK), _remote);
   
    for(int i=0; i< users.Count; i++)
    {
     GetUserList(((User)users[i]).Location);
    }
   }
   else
   {
    sock.SendTo(Encoding.Unicode.GetBytes(RENAME), _remote);
   }
  }

  /*用户下线*/
  static private void UserLogOut(byte[] data)
  {
   /*获取户名并删除其记录*/
   int lenght = int.Parse(Encoding.Unicode.GetString(data,4,4));//用户名长度
   string username = Encoding.Unicode.GetString(data,8,lenght);//用户名
   int index = FindUser(username);//用户在列表中的索引

   if(index != -1)
   {
    users.RemoveAt(index);
    Console.WriteLine("/n用户 " + username + " 退出。/n");
    Console.Write("#Command:");
   }
  }

  /*获取在线用户列表*/
  static private void GetUserList(EndPoint remote)
  {
   for(int i=0; i<users.Count; i++)
   {
    byte[] userdata = Encoding.Unicode.GetBytes(USERLIST +((User)users[i]).Name + ((User)users[i]).Location.ToString());

    sock.SendTo(userdata, remote);
   }

   sock.SendTo(Encoding.Unicode.GetBytes(MSGEND),remote);
  }

  /*命令进行端到端连接*/
  static private void PeerConnect(byte[] data, EndPoint remote)
  {
   string recever = Encoding.Unicode.GetString(data, 4,20);
   string sender = Encoding.Unicode.GetString(data, 20,20);

   byte[] msg = Encoding.Unicode.GetBytes(MAKHOLD + sender);

   int index = FindUser(recever);

   if(index != -1)
   {
    sock.SendTo(msg, ((User)users[index]).Location);
   }
   else
   {
    sock.SendTo(Encoding.Unicode.GetBytes(NONUSER), remote);
   }
  }

  /*打印在线用户名单*/
  static private void PrintfUsers()
  {
   if(users.Count != 0)
   {
    Console.WriteLine("在线用户:");

    for(int i=0; i<users.Count; i++)
    {
     Console.WriteLine(((User)users[i]).Name + "/t/t" +((User)users[i]).Location.ToString());
    }
       
    Console.WriteLine("/n");
   }
   else
   {
    Console.WriteLine("目前没有在线用户/n");
   }
  }

  #endregion

  /// <summary>
  /// 应用程序的主入口点。
  /// </summary>
  [STAThread]
  static void Main(string[] args)
  {
   bool loop = true;

   sock.Bind(ipep);

   listenThr.Start();

   Console.WriteLine("服务器已启动...../n");

   while(loop)
   {
    Console.Write("#Command:");

    string input = Console.ReadLine();

    if(input.Length < 4)
    {
     Console.WriteLine("'" + input + "'" +"不是有效命令!/n");
     continue;
    }

    switch(input.Substring(0,4).ToLower())
    {
     case USERS:
      if(input.Length == 4)
      {
       PrintfUsers();
      }
      else
      {
       Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      }

      break;

     case EXIT:
      listenThr.Abort();
      
      byte[] data = Encoding.Unicode.GetBytes(SERVCLS);

      for(int i=0; i<users.Count; i++)
      {
       sock.SendTo(data, ((User)users[i]).Location);
      }

      sock.Close();
      
      loop = false;

      break;

     default:
      Console.WriteLine("'" +input+"'" +"不是有效命令!/n");
      break;
    }
   }
  }
 }

用空再解释UDP打洞是怎么回事,代码写的很乱,请大家见谅!

这个试验我做了很多次都没成功,不知道是不是代码哪里错了~~~有空得检查下。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值