简单的UDP穿透示例

实验环境,台式机win7+用VirtualBox搭建的两个虚拟机Linux环境,两个虚拟机的网络连接方式都选择为网络地址转换(NAT)。很明显两个虚拟操作系统之间不能直接通信,需要在主机win7上搭建服务器以帮助两个虚拟机互相穿透通讯。下面是实验的全部代码。

首先是服务器端代码,出于简洁的考虑,用C#语言实现个UDPServer

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

namespace TestServer
{
    class Program
    {
        //服务器监听端口
        public static int port = 1987;

        //服务器套接口
        private static Socket s;

        //存储客户端IP以及端口信息
        private static List<string> clientList;

        //返回客户端可用的其他客户端外网端口信息
        private static void RetInfo(object sock) {
            EndPoint remote = (EndPoint)sock;
            Console.WriteLine("新客户端接入 "+remote.ToString());

            int i;
            while(true)
            {
                bool other = false;
                for (i = 0; i < clientList.Count; i++) {
                    //找一个不同于当前remote客户端的客户端IP和端口返回给remote
                    if (clientList[i] != remote.ToString())
                    {
                        string str = clientList[i];
                        byte[] data = new byte[1024];
                        data = Encoding.ASCII.GetBytes(str);
                        s.SendTo(data, remote);
                        other = true;
                        break;
                    }
                }
                //找到,结束当前客户端连接
                if (other == true) {
                    break;
                }
            }
        }

        static void Main(string[] args)
        {
            try
            {
                //绑定本地UDP端口
                IPEndPoint ipe = new IPEndPoint(IPAddress.Any, port);
                s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                s.Bind(ipe);

                clientList = new List<string>();
                clientList.Clear();
                Console.WriteLine("start server");

                while (true)
                {
                    IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
                    EndPoint remote = (EndPoint)sender;

                    byte[] data = new byte[1024];
                    int len = s.ReceiveFrom(data, ref remote);
                    //出于测试考虑,只限于两个客户端穿透
                    if (clientList.Count == 2) {
                        clientList.Clear();
                        Console.WriteLine("服务器重置");
                    }
                    clientList.Add(remote.ToString());

                    //创建一个线程向客户端返回其他客户端的信息,如果没有则直到新客户端加入
                    Thread newThread = new Thread(new ParameterizedThreadStart(RetInfo));
                    newThread.Start(remote);

                }
                //这两行代码不会运行到
                s.Close();
                Console.WriteLine("stop server");
            }
            catch (Exception err)
            {
                Console.WriteLine(err.ToString());
            }
        }
    }
}
服务器端主要的逻辑是接受两个客户端a,b的连接,然后分别将a和b的NAT转换后的IP以及端口信息告诉客户端b,a。这样就完成了服务器的工作了。

B 然后客户端代码如下,两个客户端代码基本一致,只是为了区分,两者发送的消息和打印的内容稍有区别,客户端用php代码模拟。首先和服务器连接,获取到别的客户端NAT转换地址后直接向别的客户端通信。

<?php
$server_ip = "192.168.197.118";
$server_port = 1987;

//连接win7服务器端
$sock = socket_create(AF_INET,SOCK_DGRAM,0);
if(!$sock){
    exit(-1);
}

//向UDPServer服务器发送消息(内容随便)
$buf = "login in to server";
if(!socket_sendto($sock,$buf,strlen($buf),0,$server_ip,$server_port))
{
    socket_close($sock);
    exit(-1);
}
while(true){
        //阻塞等待服务器响应消息
        $msg = "";
        if(!socket_recvfrom($sock,$msg,256,0,$server_ip,$server_port))
        {
                socket_close($sock);
                exit(-1);
        }
        //如果从服务器获取到客户端B或A的IP以及端口,则跳出循环去发送消息
        if(($isip = strpos($msg,":"))==true)
        {
                $otherinfo = split(":",$msg);
                $server_ip = $otherinfo[0];
                $server_port = $otherinfo[1];
                //$buf = "nice to meet you A"; // 客户端B代码
                $buf = "nice to meet you B"; // 客户端A代码
                //用于穿透的包,其他客户端不会收到
                if(!socket_sendto($sock,$buf,strlen($buf),0,"$server_ip",$server_port))
                {
                        socket_close($sock);
                        exit(-1);
                }
        }
        else
        {
                //循环向客户端B或A发送消息
                while(true){
                        //$buf = "hello client A"; // 客户端B代码
                        $buf = "hello client B"; // 客户端A代码
                        if(!socket_sendto($sock,$buf,strlen($buf),0,$server_ip,$server_port))
                        {
                                socket_close($sock);
                                exit(-1);
                        }
                        $msg = "";
                        if(!socket_recvfrom($sock,$msg,256,0,$server_ip,$server_port))
                        {
                                socket_close($sock);
                                exit(-1);
                        }
                        //echo "Recv $msg from Client A at ".time(0)."\n";  //客户端B代码
                        echo "Recv $msg from Client B at ".time(0)."\n";    //客户端A代码
                        sleep(1);
                }
        }
}
?>

C 实验效果

首先运行服务器端,然后分别运行两个虚拟机中的代码 php clientA.php 以及 php clientB.php,可以看到互相之间已经能通过UDP协议发送报文了。

服务器端内容

start server
新客户端接入 192.168.197.118:54170
新客户端接入 192.168.197.118:54171
客户端A的内容
ciaos@ciaos-VirtualBox:~/Public$ php clientA.php 
Recv hello client A from Client B at 1344308281
Recv hello client A from Client B at 1344308282
Recv hello client A from Client B at 1344308283
Recv hello client A from Client B at 1344308284
Recv hello client A from Client B at 1344308285
Recv hello client A from Client B at 1344308286
Recv hello client A from Client B at 1344308287

客户端B的内容

ciaos@ciaos-VirtualBox:~/Public$ php clientB.php 
Recv hello client B from Client A at 1344308281
Recv hello client B from Client A at 1344308282
Recv hello client B from Client A at 1344308283
Recv hello client B from Client A at 1344308284
Recv hello client B from Client A at 1344308285
Recv hello client B from Client A at 1344308286
Recv hello client B from Client A at 1344308287

你可能感兴趣的:(简单的UDP穿透示例)