好久没有来了,公司项目差不多结束了,这个简单应用也应该结束。
本篇会把我写的这个工程中核心代码讲解一下,并附有程序下载:p2p.rar
首先从服务器说起。P2P也需要服务器,而且服务器不能至于nat中,或是在局域网中。当然也是有办法的,如果要在局域网中测试,可以在路由中设置一个DMZ主机,也就是设置一个IP的机器为类外网机器,可以直接被外网访问。这样再运行服务器就可以了。其他内网中的客户端就可以用服务器的外网IP来登录。
下面是服务器界面:
服务器代码较简单,主要实现以下几个功能:
1. 保存客户端列表信息,当有新客户端登录时或登出时将列表发送给所有客户端。
2. 如果需要,帮助通知客户端进行UDP打洞,特别是发送文件时。
以下是主要代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
1
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
13
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
15
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
16
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
17
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
18
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
19
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
20
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
21
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
22
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
23
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
24
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
25
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
26
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
27
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
28
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
29
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
30
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
31
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
32
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
33
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
34
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
35
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
36
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
37
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
38
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
39
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
40
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
41
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
42
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
43
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
44
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
45
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
46
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
47
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
48
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
49
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
50
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
51
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
52
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
53
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
54
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
55
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
56
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
57
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
58
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
59
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
60
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
61
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
62
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
63
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
64
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
65
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
66
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
67
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
68
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
69
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
70
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
71
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
72
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
73
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
74
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
75
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
76
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
77
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
78
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
79
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
80
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
81
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
82
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
83
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
84
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
85
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
86
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
87
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
88
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
89
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
90
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
这样一个简单的服务器就完成了。下面介绍客户端的代码,比较复杂。
客户端界面:
客户端主要实现以下几个功能:
1. 登录或登出服务器,与服务器进行通信。
2. 与客户端通信,包括发送消息和发送文件。
以下是主要代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public class UDPClient
{
private UdpClient client;//保持监听的UDP客户端
public IPEndPoint hostPoint;//保存服务器终端信息
private IPEndPoint remotePoint;//保存某一终端信息
public UserCollection userList;//客户端列表
private string Name;//用户名
private string Password;//密码
private Thread listenThread;//监听线程
private const int MAXTRY = 3;//最大要求打洞次数
public UDPClient()
{
this.remotePoint = new IPEndPoint(IPAddress.Any, 0);
this.client = new UdpClient();
this.userList = new UserCollection();
}
public void Start()
{
this.listenThread = new Thread(new ThreadStart(Run));
this.listenThread.Start();
}
public bool ConnectToServer()
{
try
{
LoginMessage lginMsg = new LoginMessage(this.Name, this.Password);
byte[] buffer = FormatterHelper.Serialize(lginMsg);//序列化登录信息,便于发送
client.Send(buffer, buffer.Length, hostPoint);
buffer = client.Receive(ref remotePoint);//等待接收服务器发送来的客户端列表
GetUsersResponseMessage srvResMsg = (P2PHelper.GetUsersResponseMessage)P2PHelper.FormatterHelper.Deserialize(buffer);
userList.Clear();
foreach (P2PHelper.User user in srvResMsg.UserList)
{
userList.Add(user);//添加客户端
}
return true;
}
catch
{
return false;
}
}
public bool CheckServer()//检查是否能与服务器通信,这是很必要的,我发现,vista系统或是一些nat下的机器,每过差不多两分钟便不能再与服务器通信,也就是这个端口在路由上已经被关闭,所以,我设定,每分钟与与服务器通信一次,以保持端口正常开启, 代码与CheckClient前半部分相似,不再详述
{
}
public bool CheckClient(string toUserName)//检查是否能与客户端通信,如果不能,则要求服务器通知其打洞到这里,这是保证通信必须要经过的步骤
{
P2PHelper.User toUser = userList.Find(toUserName);
if (toUser == null)
{
return false;
}
for (int i = 0; i < MAXRETRY; i++)
{
CheckMessage checkMsg = new CheckMessage (this.Name);
byte[] buffer = FormatterHelper.Serialize(checkMsg );
client.Send(buffer, buffer.Length, toUser.NetPoint);
for (int j = 0; j < 5; j++)
{
if (this.ReceivedACK)
{
this.ReceivedACK = false;
return true;
}
else
{
Thread.Sleep(100);
}
}
AskPunchHoleMessage askPunchHoleMsg = new AskPunchHoleMessage (Name, toUserName);
buffer = P2PHelper.FormatterHelper.Serialize(askPunchHoleMsg );
client.Send(buffer, buffer.Length, hostPoint);
}
return false;
}
public bool SendMessageToRemote(IPEndPoint remote, PPMessage ppm)//发送消息到客户端,主要是聊天消息,其它消息不再详述
{
byte[] buffer = new byte[1024];
bool shouldSend = true;
switch (index)
{
case 1: ChatMessage chatMsg = (ChatMessage)ppm;
buffer = FormatterHelper.Serialize(chatMsg);
break;
}
if (!shouldSend)
{
return false;
}
client.Send(buffer, buffer.Length, remote);
return true;
}
public void Dispose()//注销,通知服务器
{
try
{
P2PHelper.LogoutMessage lgoutMsg = new P2PHelper.LogoutMessage(myName);
byte[] buffer = FormatterHelper.Serialize(lgoutMsg);
client.Send(buffer, buffer.Length, hostPoint);
if (this.listenThread != null)
{
this.listenThread.Abort();
this.listenThread = null;
}
}
catch
{ }
}
private void Run()//监听线程函数,与服务器差不多,只是需要多监听几种消息,不再详述
{
}
}
客户端的重点是发送文件,所以,如何发送完整,并接收完整是关键,下面是发送和接收的过程,主要是实现如何不丢失:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public void SendFile()//发送文件函数
{
FileStream fs = new FileStream(this.filename, FileMode.Open, FileAccess.Read);//文件流
try
{
byte[] fileBuffer = new byte[1024]; // 每次传1KB
FileMessage fileMsg;//发送文件消息
AnswerFileMessage answerFileMsg;//回应文件消息
byte[] buffer = new byte[1024];
int bytesRead = 1;
do
{
buffer = fileClient.Receive(ref remotePoint);//同样需要接收消息
object msgObj = FormatterHelper.Deserialize(buffer);
Type msgType = msgObj.GetType();
if (msgType == typeof(AnswerFileMessage))//判断是AnswerFileMessage就继续发送,否则等待
{
answerFileMsg = (AnswerFileMessage)msgObj;
if (this.sendCount == answerFileMsg.Length)//发送前判断接收方接收的数据是否完整,如果完整就继续发送
{
bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);//获得要发送的字节数
if (bytesRead > 0)//判断字节数
{
fileMsg = new P2PHelper.FileMessage("", fileBuffer, bytesRead);
buffer = P2PHelper.FormatterHelper.Serialize(fileMsg);
this.fileClient.Send(buffer, buffer.Length, this.remotePoint);
this.totalBytes += bytesRead;
this.sendCount++;
}
else//说明已经发完,则发送发完消息
{
P2PHelper.EndSendFileMessage endFileMsg = new P2PHelper.EndSendFileMessage();
buffer = P2PHelper.FormatterHelper.Serialize(endFileMsg);
fileClient.Send(buffer, buffer.Length, this.remotePoint);
break;
}
}
else//如果不完整,则将之前发送的再发送一遍
{
fileMsg = new FileMessage("", fileBuffer, bytesRead);
buffer = FormatterHelper.Serialize(fileMsg);
this.fileClient.Send(buffer, buffer.Length, this.remotePoint);
}
Console.WriteLine("send "+this.totalBytes);
}
else if (msgType == typeof(P2PHelper.EndSendFileMessage))
{
break;
}
} while (true);
}
catch (Exception ex)
{
Console.WriteLine("Send file has lost
![](https://www.cnblogs.com/Images/dot.gif)
}
finally
{
}
}
public void ReceiveFile()
{
FileStream fs = new FileStream(this.filename, FileMode.CreateNew, FileAccess.Write);//文件流
try
{
P2PHelper.FileMessage fileMsg;
byte[] buffer;
byte[] fileBuffer = new byte[1024];
int bytesRead = 1;
buffer = FormatterHelper.Serialize(new P2PHelper.AnswerFileMessage(0));//先发送一个AnswerFileMessage通知发送方开始发送
fileClient.Send(buffer, buffer.Length, this.remotePoint);
do
{
buffer = fileClient.Receive(ref remotePoint);//接收消息
object msgObj = FormatterHelper.Deserialize(buffer);
Type msgType = msgObj.GetType();
if (msgType == typeof(FileMessage))//判断FileMessage
{
fileMsg = (FileMessage)msgObj;
bytesRead = fileMsg.Length;
fs.Write(fileMsg.Message, 0, fileMsg.Length);//将接收到信息写入文件
this.totalBytes += bytesRead;
this.sendCount++;//统计接收次数是否完整,发送给发送方验证
buffer = FormatterHelper.Serialize(new P2PHelper.AnswerFileMessage(this.sendCount));
this.fileClient.Send(buffer, buffer.Length, this.remotePoint);//发送AnswerFileMessage通知发送方继续发送
}
else if (msgType == typeof(EndSendFileMessage))//判断结束发送消息,则结束接收
{
break;
}
} while (true);
}
catch (Exception ex)
{
Console.WriteLine("Receive file has lost
![](https://www.cnblogs.com/Images/dot.gif)
}
finally
{
}
}
这些就是客户端的主要代码,再加上用于界面操作等代码就可以运行了,快试试吧。