原文URL: http://www.cnblogs.com/dlwang2002/archive/2008/09/16/1291793.html
1:基于Socket连接;
2:在四个局域网内测试通过;
3:简单模型,需要进一步优化效率和处理;
========================= 备注: 经过修改和重构,抽象出来的P2PLib已经可以工作了,并且提取出一个Envelope对象,当作通讯协议和数据载体。 在这个Lib之上,很容易实现不同局域网之间的具体应用,比如现在已经建立了三个应用: 1:IM,跨局域网即时通讯(UI像MSN) 2:FT,文件传输,断点续传 3:DP,数据库代理(客户需要在任何地方访问它局域网内的数据库,这一个其实是作为某ORM的数据层出现的。)
========================== 基本思路
两个分别在不同的局域网内的用户无法直接建立连接并通讯。因为处在不同局域网的用户(没有公共IP)无法被外部机器主动连接,所以凡是所谓的P2P一般都是通过中间服务器中转通讯的。比如在几年前俺曾经介绍过一个P2P的软件,http://www.cnblogs.com/dlwang2002/archive/2005/04/14/207988.html,基本原理那里面有介绍。
这次所建立的模型,是双方都在不同的局域网内部,都没有公用IP。
基本原理是这样的。局域网A内用户PA想要和局域网B内的用户PB通讯,那么需要通过中间服务器S进行转接通讯。Socket链接虽然只是由一方发起(局域网内的),但是socket确实一个可以在两端都能通讯的,也就是说,PA链接S后,S实际上可以使用这个通道直接发消息给PA。同理,如果PB连接之后,S将有两个Socket实例,然后S可以把SA的消息直接转发给SB,这样SB就转载了SA的请求到了PB。虽然还是要通过中转,但是S只负责把两端Socket互联,速度延时可近似认为是0,也就是可以认为PA和PB是建立了直接的链接,P2P。
过程如下:
1:)PA向S发出连接请求;S接受请求,并且保留住PA的socket实例SA,存进一个在线用户列表LiveConnections中
2:)PB请求S并建立连接(和A无先后关系),S中保存其socket实例SB
3:)PA向s发出通讯请求,指明通讯对象是PB
4:)S接收到A的请求,再当前的LiveConnections中找到PB的socket示例SB,转发消息;
5:)PB接受到来自PA的消息。
主要程序代码
1:)首先的问题是如何建立Socket连接。这个问题在以前的一篇Blog中有提到(http://www.cnblogs.com/dlwang2002/archive/2008/07/21/924803.html)。这里使用的代码基本上都是和那一个一样的,只有中间处理通讯数据的部分稍有不同。这些代码不再赘述。
2:)服务器S处理转发消息的代码
1 #region hander data 2 try 3 { 4 Logger.Log("DataReceive", data);//test, catch the data transaction 5 6 string[] allData = data.Split(';'); 7 string requestTo = allData[0]; // this is the basic format: to_IP;from_IP;MSG 8 9 //find stored socket connection here10 ClientConnection requectToCC = null; 11 foreach (DictionaryEntry de in SocketListener.ClientConnections) 12 { 13 ClientConnection cc = de.Key as ClientConnection; 14 // request form will be like this "xx.xx.xxx.xxx:xxxxxx",the last number is running number 15 // so if 2 client both in the same network, this will be confued to dispatch the socket 16 //here just find the last one that in the same network17 if (cc.RequestFrom.IndexOf(requestTo) > -1) 18 { 19 requectToCC = cc; 20 }21 22 }23 if (requectToCC != null && requectToCC.ConSocket.Connected) 24 { 25 //can get the connection here, then transfer this request to it26 try27 { 28 ReplayMsg(requectToCC.ConSocket, data); 29 }30 catch (Exception ce) 31 { 32 ReplayMsg(this.ConSocket, "process error: " + ce.Message); 33 Logger.Log("SendMsgError", ce.Message); 34 }35 }36 else37 { 38 //if can not find, means the reqeust to is not login/register there39 ReplayMsg(this.ConSocket, "the client you request to is disconnected"); 40 Logger.Log("SendMsgError", "the client you request to is disconnected"); 41 }42 // 43 }44 catch (Exception ex) 45 { 46 Logger.Log("DataError " + _requestFrom, ex.Message); 47 }48 #endregion
3:)客户端的简单实现
1 namespace P2PClient 2 { 3 public class ConnectionManager 4 { 5 private Socket _socket; 6 private IPEndPoint _hostEP; 7 private string _localIP; 8 9 public delegate void MessageReceiveEvent(string fromIP,string msg); 10 11 public event MessageReceiveEvent OnMsgReceived; 12 13 public ConnectionManager(string ip, int port) 14 { 15 IPAddress address = IPAddress.Parse(ip); 16 _hostEP = new IPEndPoint(address, port); 17 } 18 19 public bool Connect2Server() 20 { 21 try22 { 23 _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 24 _socket.Connect(_hostEP); 25 26 IPHostEntry oIPHost = Dns.Resolve(Environment.MachineName); 27 _localIP = oIPHost.AddressList[0].ToString(); 28 29 //there use the same socket, to wait for reply msg30 Thread thread = new Thread(new ThreadStart(WaitForReceiveData)); 31 thread.Name = "connection_"; 32 thread.Start(); 33 34 return true; 35 } 36 catch (Exception ex) 37 { 38 return false; 39 } 40 } 41 public void Close() 42 { 43 _socket.Shutdown(SocketShutdown.Both); 44 _socket.Close(); 45 } 46 public void SendMessage(string clientIP, string msg) 47 { 48 string sendStr = clientIP + ";"+_localIP+";" + msg;//simple format; to_IP;from_IP;MSG49 50 byte[] bytesSendStr = new byte[1024]; 51 bytesSendStr = Encoding.ASCII.GetBytes(sendStr); 52 _socket.Send(bytesSendStr, bytesSendStr.Length, 0); 53 } 54 55 public void WaitForReceiveData() 56 { 57 byte[] bytes = new Byte[1024]; 58 59 while (true) 60 { 61 bytes = new byte[1024]; 62 string data = ""; 63 64 //the system while be wait here until there is msg received here65 int bytesRec = this._socket.Receive(bytes); 66 67 data += Encoding.ASCII.GetString(bytes, 0, bytesRec); 68 #region hander data69 Logger.Log("DataReceive", data); 70 71 string[] allData = data.Split(';'); 72 string requestFrom = allData[1]; // this is the basic format; to_IP;from_IP;MSG73 string msg=allData[2]; 74 75 if (OnMsgReceived != null) 76 { 77 OnMsgReceived(requestFrom, msg); 78 } 79 #endregion80 } 81 } 82 83 84 }
4
:)UI
等其他处理 (略)
问题
1:)一个Socket的实例可以在服务器/客户端存活多久呢?我测试发现,至少几个小时没有问题,但是最长时间却不知道。
2:)服务器S用单独的线程来处理链接,并不是最好的方式
3:)服务器负载平衡,在多个服务器的情况下,要让客户端可以选择效率最高的服务器进行中转
4:)有一台机器已经在公网上,或者两台都在公网上,需要另外的模型。他们不需要中转。
小结
简单,效率未知。