通过UCMA实现FileTransfer
目标:将HelloUCMA的控制台程序修改为Winform程序,取名FileTransfer实现文件传输,每个会话一个窗口。
1. 添加新建项目,选择.Net Framework 4.0的Windows窗口运行程序,取名FileTransfer
2. 设置目标框架为.Net Framework 4.0, app.config如下:
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
3. 删除form1.cs, 添加新窗口frmMain.cs
4. Program中定义两个全局变量
//协同平台对象
public static CollaborationPlatform collaborationPlatform = null;
//用户终结点对象
public static UserEndpoint userEndPoint = null;
5. frmMain中定义两个全局变量
//指示协同平台是否启动完成
private AutoResetEvent _platformStartupCompleted = new AutoResetEvent(false);
//指示用户终结点是否建立完成
private AutoResetEvent _userEndpointEstablishComplete = new AutoResetEvent(false);
6. 因为Lync事件多为异步调用,所以在窗口构造函数中添加以下语句实现跨纯种控件调用。
Control.CheckForIllegalCrossThreadCalls = false;
7. 界面上放置几个文本框和按钮:
txtServerAddress:服务器地址
txtSIP:自己的SIP账号
如果是域成员计算机,以下三项可省略
txtUserName: 自己的登陆用户名
txtPasword:自动的登陆密码
txtDomain: 域名
btnLogin: “登陆”按钮
8. 添加登陆事件btnLogin_Click
//实例化一个用户终结点设置对象
UserEndpointSettings userEndpointSetting = new UserEndpointSettings(txtSIP.Text, txtServerAddress.Text);
if (Program.collaborationPlatform == null)
{
//自己发布状态
userEndpointSetting.AutomaticPresencePublicationEnabled = true;
//非域成员计算机身份验证
userEndpointSetting.Credential = new NetworkCredential(txtUserName.Text, txtPassword.Text, txtDomain.Text);
//实例化一个协同平台设置对象,初始化名称及通信协议
ClientPlatformSettings clientPlatformSetting = new ClientPlatformSettings("AppTest", SipTransportType.Tls);
//实例化协同平台
Program.collaborationPlatform = new CollaborationPlatform(clientPlatformSetting);
//不禁用传递通知等待,具体作用没弄明白,好像可以不设置
Program.collaborationPlatform.InstantMessagingSettings.WaitingForDeliveryNotificationDisabled = false;
//设置支持的通信方式或内容
Program.collaborationPlatform.InstantMessagingSettings.MessageConsumptionMode = InstantMessageConsumptionMode.ProxiedToRemoteEntity;
Program.collaborationPlatform.InstantMessagingSettings.ToastFormatSupport = CapabilitySupport.Supported;
Program.collaborationPlatform.InstantMessagingSettings.SupportedFormats = InstantMessagingFormat.All;
}
if (Program.userEndPoint == null)
{
//实例化用户终结点
Program.userEndPoint = new UserEndpoint(Program.collaborationPlatform, userEndpointSetting);
//启用协同平台
Program.userEndPoint.Platform.BeginStartup(CallStarttupComplete, Program.userEndPoint);
_platformStartupCompleted.WaitOne();
//建立用户终结点连接
Program.userEndPoint.BeginEstablish(CallEstablishUserEndpointComplete, Program.userEndPoint);
_userEndpointEstablishComplete.WaitOne();
//收到会议邀请时触发的事件
Program.userEndPoint.ConferenceInvitationReceived += new EventHandler<ConferenceInvitationReceivedEventArgs>(userEndPoint_ConferenceInvitationReceived);
//禁用“登陆”按钮
btnLogin.Enabled = false;
}
//协同平台启动完成
void CallStarttupComplete(IAsyncResult result)
{
UserEndpoint userEndPoint = result.AsyncState as UserEndpoint;
CollaborationPlatform collabPlatform = userEndPoint.Platform;
try
{
collabPlatform.EndStartup(result);
_platformStartupCompleted.Set();
}
catch (Exception e)
{
throw e;
}
}
//用户终结点连接建立完成
void CallEstablishUserEndpointComplete(IAsyncResult result)
{
try
{
UserEndpoint userEndpoint = result.AsyncState as UserEndpoint;
userEndpoint.EndEstablish(result);
_userEndpointEstablishComplete.Set();
}
catch (Exception e)
{
throw e;
}
}
//收到会议邀请时触发的事件
void userEndPoint_ConferenceInvitationReceived(object sender, ConferenceInvitationReceivedEventArgs e)
{
//弹出会话窗口,好像即时消息与会议邀请不一样,收到即时消息不会触发此事件
frmConversation conversation = new frmConversation(e.RemoteParticipant.Uri, e.Invitation.Conversation);
conversation.Show();
}
9. 添加一个TextBox和一个Button用于输入通信目标用户SIP地址和启动连接。
txtParticipantUri: 目标用户SIP地址
btnConnect: “连接”按钮,单击后连接
private void btnConnect_Click(object sender, EventArgs e)
{
//弹出会话窗口
frmConversation conversation = new frmConversation(txtParticipantUri.Text, null);
conversation.Show();
}
10. 新建文件传输会话实体类FileTransferSession.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mime;
namespace FileTransfer
{
/// <summary>
/// 文件传输方向
/// </summary>
public enum TransferDirection
{
/// <summary>
/// 传出文件
/// </summary>
OutBound,
/// <summary>
/// 传入文件
/// </summary>
InBound
}
public enum TransferState
{
/// <summary>
/// 等待中
/// </summary>
Waiting,
/// <summary>
/// 传输中
/// </summary>
Transfering,
/// <summary>
/// 传输完成
/// </summary>
Compeleted,
/// <summary>
/// 已拒绝
/// </summary>
Rejected
}
/// <summary>
/// 文件传输会话类
/// </summary>
public class FileTransferSession
{
/// <summary>
/// 实例化一个文件传输会话对象
/// </summary>
public FileTransferSession()
{
}
/// <summary>
/// 会话参数
/// </summary>
public Dictionary<string, string> arguments = new Dictionary<string, string>();
/// <summary>
/// 内容类型
/// </summary>
public ContentType conentType = null;
/// <summary>
/// 传输方向
/// </summary>
public TransferDirection transferDirection = TransferDirection.InBound;
/// <summary>
/// 传输状态
/// </summary>
public TransferState transferState = TransferState.Waiting;
/// <summary>
/// 本地文件路径
/// </summary>
public string localFilePath = null;
/// <summary>
/// 已发送
/// </summary>
public long offset = 0;
}
}
11. 新建文件传输队列实体类FileTransferSessions.cs,继承自List<FileTransferSession>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FileTransfer
{
/// <summary>
/// 文件传输会话集合类
/// </summary>
public class FileTransferSessions : List<FileTransferSession>
{
/// <summary>
/// 通过Invitation-Cookie索引文件传输会话对象
/// </summary>
/// <param name="InvitationCookie">Invitation-Cookie</param>
/// <returns>文件传输会话对象</returns>
public FileTransferSession this[string InvitationCookie]
{
get
{
FileTransferSession result = null;
foreach (FileTransferSession session in this)
{
if (session.arguments.ContainsKey("Invitation-Cookie") &&
session.arguments["Invitation-Cookie"] == InvitationCookie)
{
result = session;
}
}
return result;
}
}
/// <summary>
/// 实例化一个文件传输会话集合
/// </summary>
public FileTransferSessions()
{
}
public void RemoveSession(string InvitationCookie)
{
for (int n = this.Count - 1; n >= 0; n--)
{
FileTransferSession session = this[n];
if (session.arguments["Invitation-Cookie"] == InvitationCookie)
{
this.RemoveAt(n);
}
}
}
}
}
12. 添加一个用户会话的窗口:frmConversation.cs
声明几个全局变量:
//消息控制流对象
private InstantMessagingFlow msgFlow = null;
//消息呼叫对象
private InstantMessagingCall messageCall = null;
//会话对象
private Conversation conversation = null;
//传输中的文件队列
private FileTransferSessions fileTransferSessions = new FileTransferSessions();
//指示与远程用户连接是否建立完成
private AutoResetEvent _callEstablishComplete = new AutoResetEvent(false);
窗体上放置如下控件:
txtMsgRecvs: 消息显示文本框
txtMsgInput: 消息输入文本框
btnSend: 发送消息按钮
btnAccept: 接受文件传送按钮
btnSaveAs: 授收文件另存为按钮
btnCancel: 拒绝接收文件按钮
13. 窗口实例化时判断conversion是否为null,如是,则新建一个会话
public frmConversation(string remoteSIP, Conversation conversation)
{
InitializeComponent();
{
if (conversation == null)
{
ConversationSettings conversationSettings = new ConversationSettings();
//不设置优先级和主题
//conversationSettings.Priority = ConversationPriority.Normal;
//conversationSettings.Subject = txtTopic.Text;
conversation = new Conversation(Program.userEndPoint, conversationSettings);
this.conversation = conversation;
//会话状态变更时触发事件
conversation.StateChanged += new EventHandler<StateChangedEventArgs<ConversationState>>(conversation_StateChanged);
}
}
txtMsgInput.Enabled = true;
btnSend.Enabled = true;
//实例化消息呼叫对象
messageCall = new InstantMessagingCall(conversation);
//与远程用户建立连接
messageCall.BeginEstablish(remoteSIP, null, null, CallEstablishCompleted, messageCall);
_callEstablishComplete.WaitOne();
msgFlow = messageCall.Flow;
//收到消息时触发事件
msgFlow.MessageReceived += new EventHandler<InstantMessageReceivedEventArgs>(flow_MessageReceived);
//远程用户输入状态变更时触发事件
msgFlow.RemoteComposingStateChanged += new EventHandler<ComposingStateChangedEventArgs>(flow_RemoteComposingStateChanged);
}
//会话状态变更时触发事件
void conversation_StateChanged(object sender, StateChangedEventArgs<ConversationState> e)
{
if (e.State == ConversationState.Terminated)
{//会话断开时禁止发送消息
conversation = null;
txtMsgInput.Enabled = false;
btnSend.Enabled = false;
}
}
//与远程用户连接建立完成
void CallEstablishCompleted(IAsyncResult result)
{
try
{
InstantMessagingCall messageCall = result.AsyncState as InstantMessagingCall;
messageCall.EndEstablish(result);
_callEstablishComplete.Set();
}
catch (Exception e)
{
throw e;
}
}
/// <summary>
/// 显示消息
/// </summary>
/// <param name="content">消息内容</param>
void ShowMessage(string content)
{
ShowMessage(content, "Text");
}
/// <summary>
/// 显示消息
/// </summary>
/// <param name="content">消息内容</param>
/// <param name="type">消息类型</param>
void ShowMessage(string content, string type)
{
switch (type)
{
case "Text":
txtMsgRecvs.Text += content + "\r\n";
break;
}
}
//远程用户输入状态变更时触发事件
void flow_RemoteComposingStateChanged(object sender, ComposingStateChangedEventArgs e)
{
ShowMessage(e.Participant.Uri + " " + e.ComposingState.ToString());
}
14. 发送消息按钮事件
//消息发送完成回调
void CallSendInstantMessageComplete(IAsyncResult result)
{
}
private void btnSend_Click(object sender, EventArgs e)
{
if (conversation == null) return;
//会话不为空才能发送消息
string chatString = txtMsgInput.Text;
ShowMessage("我说:" + chatString);
msgFlow.BeginSendInstantMessage(chatString, CallSendInstantMessageComplete, msgFlow);
}
15. 收到消息时触发事件
void flow_MessageReceived(object sender, InstantMessageReceivedEventArgs e)
{
switch (e.ContentType.MediaType)
{
//收到文件传送消息
case "text/x-msmsgsinvite":
{
string textBody = e.TextBody;
ShowMessage(e.Sender.ToString() + " 说:" + e.TextBody.ToString());
//取出消息参数
Dictionary<string, string> Args = new Dictionary<string, string>();
foreach (string Arg in textBody.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
{
string key = Arg.Substring(0, Arg.IndexOf(':')).Trim();
string value = Arg.Substring(Arg.IndexOf(':') + 1).Trim();
if (Args.ContainsKey(key))
{
Args[key] = value;
}
else
{
Args.Add(key, value);
}
}
//判断消息类型
switch (Args["Invitation-Command"])
{
//远程用户请求发送文件
case "INVITE":
{
{
FileTransferSession session = new FileTransferSession();
session.transferDirection = TransferDirection.InBound;
session.conentType = e.ContentType;
session.arguments = Args;
fileTransferSessions.Add(session);
}
//请求传送文件
ShowMessage(e.Sender.ToString() + " 请求发送文件:" + Args["Application-File"]);
btnAccept.Enabled = true;
btnSaveAs.Enabled = true;
btnCancel.Enabled = true;
}
break;
//远程用户接爱发送文件
case "ACCEPT":
{
string InvitationCookie = Args["Invitation-Cookie"];
FileTransferSession session = fileTransferSessions[InvitationCookie];
switch (session.transferDirection)
{
//文件传输方向是传出
case TransferDirection.OutBound:
{
string filePath = session.localFilePath;
using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
long dataLength = fs.Length;
while (session.offset < dataLength)
{
long remain = dataLength - session.offset;
int count = remain > 4096 ? 4096 : Convert.ToInt32(remain);
byte[] data = new byte[count];
fs.Seek(session.offset, SeekOrigin.Begin);
fs.Read(data, 0, count);
//发送data
session.offset += count;
}
}
}
break;
}
}
break;
//远程用户无法接受文件
case "CANCEL":
{
string InvitationCookie = Args["Invitation-Cookie"];
FileTransferSession session = fileTransferSessions[InvitationCookie];
if (session != null)
{
//无法传送文件
switch (Args["Cancel-Code"])
{
//因传输失败引起
case "FAIL":
ShowMessage(e.Sender.ToString() + " 文件“" + session.arguments["Application-File"] + "”传输失败!");
fileTransferSessions.RemoveSession(InvitationCookie);
break;
//因远程用户拒绝
case "REJECT":
ShowMessage(e.Sender.ToString() + " 拒绝接收文件:" + session.arguments["Application-File"]);
fileTransferSessions.RemoveSession(InvitationCookie);
break;
}
}
}
break;
}
}
break;
default:
//默认为文本消息处理
ShowMessage(e.Sender.ToString() + " 说:" + e.TextBody.ToString());
break;
}
}
16. 发送文件、接收文件或拒绝接收文件
//当文件拖到txtMsgInput上时
private void txtMsgInput_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Link;
else e.Effect = DragDropEffects.None;
}
//当拖动文件到txtMsgInput上并释放鼠标时
private void txtMsgInput_DragDrop(object sender, DragEventArgs e)
{
string filePath = "";
try
{
//文件路径
filePath = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString();
txtMsgInput.Text = filePath;
FileInfo fileInfo = new FileInfo(filePath);
//取出文件长度
long dataLength = 0;
using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
dataLength = fs.Length;
}
//实例化一个文件传输会话对象
FileTransferSession session = new FileTransferSession();
session.transferDirection = TransferDirection.OutBound;
session.localFilePath = filePath;
session.conentType = new ContentType("text/x-msmsgsinvite");
session.conentType.CharSet = "UTF-8";
session.arguments.Add("Application-Name", "File Transfer");//伪官方应用程序名称
session.arguments.Add("Application-GUID", "{5D3E02AB-6190-11d3-BBBB-00C04F795683}");//固定
session.arguments.Add("Invitation-Command", "INVITE");//请求传送文件
session.arguments.Add("Invitation-Cookie", "" + (new Random()).Next(1, Int32.MaxValue) + "");//文件传输会话唯一标识
session.arguments.Add("Application-File", "" + fileInfo.Name + "");//文件名
session.arguments.Add("Application-FileSize", "" + dataLength.ToString() + "");//文件大小
session.arguments.Add("Connectivity", "N");//不懂是什么
session.arguments.Add("Encryption", "S");//R/S,完全传输?还没弄懂
StringBuilder sb = new StringBuilder();
foreach (string key in session.arguments.Keys)
{
sb.Append(string.Format("{0}: {1}\r\n", key, session.arguments[key]));
}
string str = sb.ToString();
byte[] bytes = Encoding.UTF8.GetBytes(str);
//发送消息
msgFlow.BeginSendInstantMessage(session.conentType, bytes, CallSendInstantMessageComplete, msgFlow);
//将文件传输会话加入列表
fileTransferSessions.Add(session);
}
catch
{
}
}
//拒绝接收文件
private void btnCancel_Click(object sender, EventArgs e)
{
bool hasMoreSession = false;
bool isRejected = false;
for (int n = fileTransferSessions.Count - 1; n >= 0; n--)
{
FileTransferSession session = fileTransferSessions[n];
//只查找入站并处理等待中的文件传输请求
if (session.transferDirection == TransferDirection.InBound && session.transferState == TransferState.Waiting)
{
//从最新一个开始拒绝,每点击一次拒绝一个
if (!isRejected)
{
ContentType conentType = new ContentType("text/x-msmsgsinvite");
conentType.CharSet = "UTF-8";
Dictionary<string, string> arguments = new Dictionary<string, string>();
arguments.Add("Invitation-Command", "CANCEL");//取消文件传输
arguments.Add("Invitation-Cookie", session.arguments["Invitation-Cookie"]);
arguments.Add("Cancel-Code", "REJECT");//拒绝
StringBuilder sb = new StringBuilder();
foreach (string key in arguments.Keys)
{
sb.Append(string.Format("{0}: {1}\r\n", key, arguments[key]));
}
string str = sb.ToString();
byte[] bytes = Encoding.UTF8.GetBytes(str);
//发送响应
msgFlow.BeginSendInstantMessage(session.conentType, bytes, CallSendInstantMessageComplete, msgFlow);
ShowMessage("已拒绝接收文件:" + session.arguments["Application-File"]);
//移除文件传输会话
fileTransferSessions.RemoveSession(session.arguments["Invitation-Cookie"]);
isRejected = true;
}
else
{
//存在更多未处理的文件传输等待请求
hasMoreSession = true;
break;
}
}
}
if (!hasMoreSession)
{//没有更多等待中的请求时禁用接受、另存为、拒绝三个按钮
btnAccept.Enabled = false;
btnSaveAs.Enabled = false;
btnCancel.Enabled = false;
}
}
//接收文件
private void btnAccept_Click(object sender, EventArgs e)
{
bool hasMoreSession = false;
bool isAccepted = false;
for (int n = fileTransferSessions.Count - 1; n >= 0; n--)
{
FileTransferSession session = fileTransferSessions[n];
//同上,从最新开始查找等待中的入站文件传输请求
if (session.transferDirection == TransferDirection.InBound && session.transferState == TransferState.Waiting)
{
if (!isAccepted)
{//找到一个入站文件传输请求
ContentType conentType = new ContentType("text/x-msmsgsinvite");
conentType.CharSet = "UTF-8";
Dictionary<string, string> arguments = new Dictionary<string, string>();
arguments.Add("Invitation-Command", "ACCEPT");//授受
arguments.Add("Invitation-Cookie", session.arguments["Invitation-Cookie"]);
arguments.Add("Request-Data", "IP-Address:");
arguments.Add("Encryption-Key", "qkmvcN6Cb+PDYrA13Jg62AEIpA9oSlvs");//不知道怎么生成的
arguments.Add("Hash-Key", "9h89aUwU6SjQnW0iLspXpCoXTPyzhg07");//不知道怎么生成的
//不知道上边这两个Key怎么生成出来的,只能实现文件传输提示,没实现文件传输
IPAddress myIPAddress = msgFlow.SignalingContext.Connection.LocalEndpoint.Address;
arguments.Add("IP-Address", myIPAddress.ToString());//IP地址
arguments.Add("Port", "6892");
arguments.Add("PortX", "11181");
arguments.Add("AuthCookie", "" + (new Random()).Next(1, Int32.MaxValue) + "");
arguments.Add("Request-Data: IP-Address", "");
arguments.Add("Sender-Connect", "TRUE");
StringBuilder sb = new StringBuilder();
foreach (string key in arguments.Keys)
{
sb.Append(string.Format("{0}: {1}\r\n", key, arguments[key]));
}
string str = sb.ToString();
byte[] bytes = Encoding.UTF8.GetBytes(str);
//发送响应
msgFlow.BeginSendInstantMessage(session.conentType, bytes, CallSendInstantMessageComplete, msgFlow);
session.transferState = TransferState.Transfering;
ShowMessage("接受传送文件:" + session.arguments["Application-File"]);
isAccepted = true;
}
else
{
hasMoreSession = true;
break;
}
}
}
if (!hasMoreSession)
{//没有更多等待中的请求时禁用接受、另存为、拒绝三个按钮
btnAccept.Enabled = false;
btnSaveAs.Enabled = false;
btnCancel.Enabled = false;
}
}