飞秋(FeiQ)是一款局域网聊天传送文件的绿色软件,它参考了飞鸽传书(IPMSG)和QQ,
完全兼容飞鸽传书(IPMSG)协议,具有局域网传送方便,速度快,操作简单的优点,同
时具有QQ中的一些功能。
功能说明:
- 飞秋(FeiQ)是一款局域网内即时通信软件, 基于 TCP/IP(UDP).
- 完全兼容网上广为流传的飞鸽传书并比原来飞鸽功能更加强大.
- 不需要服务器支持.
- 支持文件/文件夹的传送 (支持大文件传送[4G以上]), 发送方和接收方
先给飞秋做下广告。以上的说明简介转自华军软件园:http://www.newhua.com/soft/66046.htm
下面转入正题,我们公司同时之间习惯使用飞秋来进行通讯,同时也用QQ,他们各有各的好处的特点,飞秋的好处就是,不需要注册帐号,简单方便。但是飞秋在windows2003系统下经常会卡死,不知道是公司网络问题,还是其他什么问题。后来也考虑使用其他的即时im来取代它,尝试过,后来放弃了,毕竟习惯难以改变。既然如此,我们就继续用吧,不过我们想为飞秋增加一些功能,或者想使用飞秋做提醒,那么原来的飞秋就不那么方便了。于是我考虑写个客户端和它来通讯。
飞秋支持ipmsg协议,那就好办了。我们只需要遵守协议和消息机制,就能和其他飞秋客户端互发消息了。
ipmsg协议的内容,参考文档http://www.cnblogs.com/hnrainll/archive/2011/05/07/2039567.html,同时对译者感谢。飞秋的消息发送使用的是udp,消息的格式为:
1:100:shirouzu:Jupiter:32:Hello,以冒号作为分割,分为6短。各段的意思为:
版本号:消息编号:发送人姓名:发送人机器名:命令字:附加内容。
那么我依靠这些就能互发消息了。然后还要知道“命令字”的内容。这些命令字的常量定义,我没有找到官方文档。不过协议里提到的指令(命令字)对应的数字(int)是多少?却没有找到,而且到处找不到。后来在一篇文章里见到作者写的代码里有包含一些代码中提到了这些常量的定义,就拿来用了。该文章的地址找不到了,对作者表示歉意。
* @名称 :
* @描述 :
* @创建人 : 张云飞
* @创建日期: 2011/7/11 13:27:24
* @修改记录:
* ---------------------------------------------------------- */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FeiQAPI
{
public enum IPMSG_Header
{
/* header */
IPMSG_VERSION = 0x0001 ,
IPMSG_DEFAULT_PORT = 0x0979 ,
}
public enum IPMSG_FileattachCommand
{
/* file types for fileattach command */
IPMSG_FILE_REGULAR = 0x00000001 ,
IPMSG_FILE_DIR = 0x00000002 ,
IPMSG_LISTGET_TIMER = 0x0104 ,
IPMSG_LISTGETRETRY_TIMER = 0x0105
}
public enum IPMSG_COMMAND : int
{
/* command */
IPMSG_NOOPERATION = 0x00000000 ,
IPMSG_BR_ENTRY = 0x00000001 ,
IPMSG_BR_EXIT = 0x00000002 ,
IPMSG_ANSENTRY = 0x00000003 ,
IPMSG_BR_ABSENCE = 0x00000004 ,
IPMSG_BR_ISGETLIST = 0x00000010 ,
IPMSG_OKGETLIST = 0x00000011 ,
IPMSG_GETLIST = 0x00000012 ,
IPMSG_ANSLIST = 0x00000013 ,
IPMSG_FILE_MTIME = 0x00000014 ,
IPMSG_FILE_CREATETIME = 0x00000016 ,
IPMSG_BR_ISGETLIST2 = 0x00000018 ,
IPMSG_SENDMSG = 0x00000020 ,
IPMSG_RECVMSG = 0x00000021 ,
IPMSG_READMSG = 0x00000030 ,
IPMSG_DELMSG = 0x00000031 ,
/* option for all command */
IPMSG_ABSENCEOPT = 0x00000100 ,
IPMSG_SERVEROPT = 0x00000200 ,
IPMSG_DIALUPOPT = 0x00010000 ,
IPMSG_FILEATTACHOPT = 0x00200000 ,
}
}
有了命令字,然后就是消息对象的MODEL了。我们构建一个消息类的实体,以便在使用中传递消息对象。
* @名称 :
* @描述 :
* @创建人 : 张云飞
* @创建日期: 2011/7/11 13:35:37
* @修改记录:
* ---------------------------------------------------------- */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FeiQAPI
{
/// <summary>
/// 版本号(1):包编号:发送者姓名:发送者主机名:命令字:附加信息
/// </summary>
public class IPMSG
{
string _Version;
/// <summary>
/// 版本号(1)
/// </summary>
public string Version
{
get { return _Version; }
set { _Version = value; }
}
string _PackageID;
/// <summary>
/// 包编号
/// </summary>
public string PackageID
{
get { return _PackageID; }
set { _PackageID = value; }
}
string _SenderName;
/// <summary>
/// 发送者姓名
/// </summary>
public string SenderName
{
get { return _SenderName; }
set { _SenderName = value; }
}
string _SenderHostName;
/// <summary>
/// 发送者主机名
/// </summary>
public string SenderHostName
{
get { return _SenderHostName; }
set { _SenderHostName = value; }
}
IPMSG_COMMAND _Msg_Command;
/// <summary>
/// 命令字
/// </summary>
public IPMSG_COMMAND Msg_Command
{
get { return _Msg_Command; }
set { _Msg_Command = value; }
}
string _Option_Data;
/// <summary>
/// 附加信息
/// </summary>
public string Option_Data
{
get { return _Option_Data; }
set { _Option_Data = value; }
}
/// <summary>
/// 转换
/// </summary>
public static IPMSG Parse(Byte[] bytes, int index, int count)
{
return Parse(Encoding.Default.GetString(bytes,index,count));
}
/// <summary>
/// 转换
/// </summary>
/// <param name="msgStr"></param>
/// <returns></returns>
public static IPMSG Parse( string msgStr)
{
if ( string .IsNullOrEmpty(msgStr))
{
throw new ArgumentNullException();
}
/// 版本号(1):包编号:发送者姓名:发送者主机名:命令字:附加信息
string [] strArr = msgStr.Split( new char [] { ' : ' }, StringSplitOptions.None);
Console.WriteLine( " Receive:{0} ,Split array length={1} " , msgStr, strArr.Length);
if (strArr.Length == 6 )
{
IPMSG msg = new IPMSG();
msg.Version = strArr[ 0 ];
msg.PackageID = strArr[ 1 ];
msg.SenderName = strArr[ 2 ];
msg.SenderHostName = strArr[ 3 ];
msg.Msg_Command = (IPMSG_COMMAND)Enum.Parse( typeof (IPMSG_COMMAND), strArr[ 4 ]);
msg.Option_Data = strArr[ 5 ];
return msg;
}
return null ;
}
/// <summary>
/// 转化到消息
/// </summary>
/// <returns></returns>
public string ToMsgStr()
{
return string .Format( " {0}:{1}:{2}:{3}:{4}:{5} " ,
Version ?? " 1 " ,
PackageID ?? DateTime.Now.Ticks.ToString(),
SenderName ?? "" ,
SenderHostName ?? "" ,
(( int )Msg_Command).ToString(),
Option_Data ?? "" );
}
public byte [] ToBytes()
{
string str = ToMsgStr();
Console.WriteLine( " send Msg: " + str);
return Encoding.Default.GetBytes(str);
}
}
}
上面的实体基本 包含了6个属性,对应协议里的6个部分。同时写了 转换的方法
ToBytes,ToMsgStr,Parse等。实现了消息对象和 字节 之间的转换,便于传输。
好了,现在我们构建Socket对象。
ProtocolType.Udp);
IPEndPoint ip = new IPEndPoint(IPAddress.Any, _Port);
_socket.Bind(ip);
标准的构建socket的代码。端口我们指定为: int _Port = 2425;
现在尝试发送消息,如下代码所示Dns.GetHostName是获得本级机器名的方法。 在下面的方法里,构建里一个IPMSG 对象,并对该对象的各个部分赋值,然后调用socket.SendTo方法,将数据包发送出去。
{
byte [] byts = null ;
IPMSG msg = new IPMSG();
msg.SenderName = MyName;
msg.SenderHostName = Dns.GetHostName();
msg.Msg_Command = IPMSG_COMMAND.IPMSG_SENDMSG; // IPMSG_SENDMSG
msg.Option_Data = msgText;
byts = msg.ToBytes();
EndPoint ep = (EndPoint) new IPEndPoint(IPAddress.Parse(targetIP), _Port);
_socket.SendTo(byts, ep);
}
那么如何接收消息呢?
while (_Running)
{
// Socket socketClient = socket.Accept();
EndPoint remote = (EndPoint)( new IPEndPoint(IPAddress.Any, 0 ));
int len = _socket.ReceiveFrom(buf, ref remote);
if (len > 0 )
{
string msg = Encoding.Default.GetString(buf, 0 , len);
Console.WriteLine(msg);
IPMSG msg1 = IPMSG.Parse(buf, 0 , len);
Action < IPMSG > act = new Action < IPMSG > (ProcessMessage);
if (msg != null )
{
act.BeginInvoke(msg1, null , null );
}
// Println(msg + Environment.NewLine);
len = 0 ;
}
}
调用socket.ReceiveFrom方法接受到网络发来的数据包,使用我们写好的解析法方法,我们的解析方法遵守了ipmsg协议。
string msg = Encoding.Default.GetString(buf, 0, len);
IPMSG msg1 = IPMSG.Parse(buf, 0, len);
这样我们就拿到一个IPMSG对象了,该消息对象包含了其他客户机发来的消息内容,这些内容可能是,文本消息,上线消息,回复消息,文件消息等。根据消息里的 指令类型 我们需要对接受到的消息做不同的处理。
注意上面的代码中:
Action<IPMSG> act = new Action<IPMSG>(ProcessMessage);
if (msg != null)
{
act.BeginInvoke(msg1, null, null);
}
我是使用了一个异步委托。代码执行到这里的时候不至于阻塞。
我们接收到了消息后,根据消息的内容做不同的处理,下面的处理消息的代码:
{
Console.WriteLine(msg.Msg_Command);
IPMSG_COMMAND mode = msg.Msg_Command;
if ((IPMSG_COMMAND.IPMSG_ANSENTRY & mode) == IPMSG_COMMAND.IPMSG_ANSENTRY)
{
// 让用户上线
ProcessUseOnLine(msg);
return ;
}
if ((IPMSG_COMMAND.IPMSG_BR_ENTRY & mode) == IPMSG_COMMAND.IPMSG_BR_ENTRY)
{
// 让用户上线
// ReceiveMsg(msg);
return ;
}
if ((IPMSG_COMMAND.IPMSG_SENDMSG & mode) == IPMSG_COMMAND.IPMSG_SENDMSG)
{
// 让用户上线
ReceiveMsg(msg);
return ;
}
// if ((IPMSG_COMMAND.IPMSG_RECVMSG & mode) == IPMSG_COMMAND_SENDCHECKOPT)
// {
// // 让用户上线
// ReceiveMsg(msg);
// }
}
我们是如何通知 我们上线了呢? 向局域网内的其他用户发送广播消息。收到这些消息的客户端就会将我们加入到他们的好友列表,同时各个客户端会回复一个消息给我们,籍此,我们就知道了网络上那些客户端,我们把这些客户端加入到我们的好友列表中。
{
byte [] byts = null ;
IPMSG msg = new IPMSG();
msg.SenderName = MyName;
msg.SenderHostName = Dns.GetHostName();
msg.Msg_Command = IPMSG_COMMAND.IPMSG_BR_ENTRY;
msg.Option_Data = string .Format( " {0}\0{1}\0{1}{2} " , MyName, MyGroup, "" );
byts = msg.ToBytes();
EndPoint ep = (EndPoint) new IPEndPoint(IPAddress.Parse( " 192.168.3.255 " ), _Port);
_socket.SendTo(byts, ep);
}
然后要处理收到的 其他客户机 给我们的回复,该回复了包含了 其他各个客户机的位置信息
{
// 让用户上线
ProcessUseOnLine(msg);
return ;
}
{
string optionData = msg.Option_Data;
string [] arr = optionData.Split( new char [ ' / ' ], StringSplitOptions.None);
string displayName = "" ;
string group = "" ;
if (arr != null && arr.Length > 0 )
{
if (arr.Length > 0 )
{
displayName = arr[ 0 ];
}
if (arr.Length > 1 )
{
group = arr[ 1 ];
}
}
_UserList.AddUser(msg.SenderName, msg.SenderHostName, displayName, group);
}
下面是我的好友类,和好友列表类:
* @名称 :
* @描述 :
* @创建人 : 张云飞
* @创建日期: 2011/7/11 14:27:48
* @修改记录:
* ---------------------------------------------------------- */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FeiQAPI
{
/// <summary>
/// 用户
/// </summary>
public struct User
{
public string UserName; // 用户名
public string HostName; // 机器名
// public string id; // 节点ID。
public string HostIp; // 存储IP信息,避免重复添加
public string Displayname;
public string Group;
// public object Inet; // 存储网络信息
}
}
* @名称 :
* @描述 :
* @创建人 : 张云飞
* @创建日期: 2011/7/11 14:35:45
* @修改记录:
* ---------------------------------------------------------- */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace FeiQAPI
{
/// <summary>
/// 用户列表
/// </summary>
public class UserList
{
Dictionary < string , User > _lstUsers = new Dictionary < string , User > ();
public event EventHandler < UserEventArgs > OnUserAdded;
public void AddUser( string userName, string hostName, string displayName, string group)
{
string ip = null ;
try
{
if (Dns.GetHostEntry(hostName) != null && Dns.GetHostEntry(hostName).AddressList != null && Dns.GetHostEntry(hostName).AddressList.Length > 0 )
{
ip = Dns.GetHostEntry(hostName).AddressList.Last().ToString();
User user = new User();
user.UserName = userName;
user.HostName = hostName;
user.HostIp = ip;
user.Displayname = displayName;
user.Group = group;
AddUser(user);
}
}
catch (SocketException)
{
}
catch (Exception)
{
throw ;
}
}
public void AddUser(User user)
{
if ( ! _lstUsers.ContainsKey(user.HostIp))
{
_lstUsers.Add(user.HostIp, user);
if (OnUserAdded != null )
{
OnUserAdded( this , new UserEventArgs(user));
}
}
}
/// <summary>
/// 获得用户
/// </summary>
/// <param name="senderName"></param>
/// <param name="hostName"></param>
/// <returns></returns>
public User ? GetUser( string senderName, string hostName)
{
return _lstUsers.Values.SingleOrDefault(p => p.UserName == senderName && p.HostName == hostName);
}
}
}
最后贴出我的完整的处理 消息 的服务器类:
* @名称 :
* @描述 :
* @创建人 : 张云飞
* @创建日期: 2011/7/11 14:01:32
* @修改记录:
* ---------------------------------------------------------- */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace FeiQAPI
{
/// <summary>
/// 消息服务器
/// </summary>
public class MsgServer
{
/// <summary>
/// 当接受的消息时
/// </summary>
public event EventHandler < IpMsg_EventArgs > OnReceiveMsg;
int _Port = 2425 ;
bool _Running = false ;
private string _MyName;
private string _MyGroup;
/// <summary>
/// 分组
/// </summary>
public string MyGroup
{
get { return _MyGroup; }
set { _MyGroup = value; }
}
public string MyName
{
get { return _MyName; }
set { _MyName = value; }
}
public bool Running
{
get { return _Running; }
}
Socket _socket;
UserList _UserList = new UserList();
/// <summary>
/// 用户列表
/// </summary>
public UserList UserList
{
get { return _UserList; }
}
// public void Send
public void ProcessUseOnLine(IPMSG msg)
{
string optionData = msg.Option_Data;
string [] arr = optionData.Split( new char [ ' / ' ], StringSplitOptions.None);
string displayName = "" ;
string group = "" ;
if (arr != null && arr.Length > 0 )
{
if (arr.Length > 0 )
{
displayName = arr[ 0 ];
}
if (arr.Length > 1 )
{
group = arr[ 1 ];
}
}
_UserList.AddUser(msg.SenderName, msg.SenderHostName, displayName, group);
}
public void Start()
{
ThreadPool.QueueUserWorkItem( new WaitCallback( delegate
{
Run();
}));
}
private void Run()
{
if (_Running == false )
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
ProtocolType.Udp);
IPEndPoint ip = new IPEndPoint(IPAddress.Any, _Port);
_socket.Bind(ip);
// socket.Listen(10);
_Running = true ;
SendMyOnline();
byte [] buf = new byte [ 1024 ];
while (_Running)
{
// Socket socketClient = socket.Accept();
EndPoint remote = (EndPoint)( new IPEndPoint(IPAddress.Any, 0 ));
int len = _socket.ReceiveFrom(buf, ref remote);
if (len > 0 )
{
string msg = Encoding.Default.GetString(buf, 0 , len);
Console.WriteLine(msg);
IPMSG msg1 = IPMSG.Parse(buf, 0 , len);
Action < IPMSG > act = new Action < IPMSG > (ProcessMessage);
if (msg != null )
{
act.BeginInvoke(msg1, null , null );
}
// Println(msg + Environment.NewLine);
len = 0 ;
}
}
}
}
private void ProcessMessage(IPMSG msg)
{
Console.WriteLine(msg.Msg_Command);
IPMSG_COMMAND mode = msg.Msg_Command;
if ((IPMSG_COMMAND.IPMSG_ANSENTRY & mode) == IPMSG_COMMAND.IPMSG_ANSENTRY)
{
// 让用户上线
ProcessUseOnLine(msg);
return ;
}
if ((IPMSG_COMMAND.IPMSG_BR_ENTRY & mode) == IPMSG_COMMAND.IPMSG_BR_ENTRY)
{
// 让用户上线
// ReceiveMsg(msg);
return ;
}
if ((IPMSG_COMMAND.IPMSG_SENDMSG & mode) == IPMSG_COMMAND.IPMSG_SENDMSG)
{
// 让用户上线
ReceiveMsg(msg);
return ;
}
// if ((IPMSG_COMMAND.IPMSG_RECVMSG & mode) == IPMSG_COMMAND_SENDCHECKOPT)
// {
// // 让用户上线
// ReceiveMsg(msg);
// }
}
private void ReceiveMsg(IPMSG msg)
{
if (OnReceiveMsg != null )
{
OnReceiveMsg( this , new IpMsg_EventArgs(msg));
}
}
public void SendMyOnline()
{
byte [] byts = null ;
IPMSG msg = new IPMSG();
msg.SenderName = MyName;
msg.SenderHostName = Dns.GetHostName();
msg.Msg_Command = IPMSG_COMMAND.IPMSG_BR_ENTRY;
msg.Option_Data = string .Format( " {0}\0{1}\0{1}{2} " , MyName, MyGroup, "" );
byts = msg.ToBytes();
EndPoint ep = (EndPoint) new IPEndPoint(IPAddress.Parse( " 192.168.3.255 " ), _Port);
_socket.SendTo(byts, ep);
}
public void Stop()
{
_Running = false ;
}
public void SendMsg(User _CurrentUser, string msgText)
{
SendMsg(_CurrentUser.HostIp, msgText);
}
public void SendMsg( string targetIP, string msgText)
{
byte [] byts = null ;
IPMSG msg = new IPMSG();
msg.SenderName = MyName;
msg.SenderHostName = Dns.GetHostName();
msg.Msg_Command = IPMSG_COMMAND.IPMSG_SENDMSG; // IPMSG_SENDMSG
msg.Option_Data = msgText;
byts = msg.ToBytes();
EndPoint ep = (EndPoint) new IPEndPoint(IPAddress.Parse(targetIP), _Port);
_socket.SendTo(byts, ep);
}
}
}