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