1:基于Socket连接;
2:在四个局域网内测试通过;
3:简单模型,需要进一步优化效率和处理;
基本思路
两个分别在不同的局域网内的用户无法直接建立连接并通讯。因为处在不同局域网的用户(没有公共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处理转发消息的代码
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
1
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
2
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
3
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
4
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
5
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
6
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
7
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
8
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
9
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
10
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
11
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
12
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
13
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
14
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
15
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
16
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
17
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
18
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
19
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
20
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
21
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
22
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
23
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
24
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
25
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
26
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
27
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
28
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
29
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
30
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
31
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
32
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
33
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
34
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
35
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
36
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
37
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
38
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
39
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
40
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
41
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
42
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
43
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
44
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
45
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
46
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
47
![](https://i-blog.csdnimg.cn/blog_migrate/eec4c0236afc26744c9c4e910bc34958.gif)
48
![](https://i-blog.csdnimg.cn/blog_migrate/4fd96b3cf02f4c7b5c8964ac8167f7af.gif)
3:)客户端的简单实现
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
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 try
22 {
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 msg
30 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;MSG
49
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 here
65 int bytesRec = this._socket.Receive(bytes);
66
67 data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
68 #region hander data
69 Logger.Log("DataReceive", data);
70
71 string[] allData = data.Split(';');
72 string requestFrom = allData[1]; // this is the basic format; to_IP;from_IP;MSG
73 string msg=allData[2];
74
75 if (OnMsgReceived != null)
76 {
77 OnMsgReceived(requestFrom, msg);
78 }
79 #endregion
80 }
81 }
82
83
84 }
4:)UI等其他处理 (略)
问题
1:)一个Socket的实例可以在服务器/客户端存活多久呢?我测试发现,至少几个小时没有问题,但是最长时间却不知道。
2:)服务器S用单独的线程来处理链接,并不是最好的方式
3:)服务器负载平衡,在多个服务器的情况下,要让客户端可以选择效率最高的服务器进行中转
4:)有一台机器已经在公网上,或者两台都在公网上,需要另外的模型。他们不需要中转。
小结