通过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;
    }
}

posted on 2012-03-14 09:33  豌小豆  阅读(1197)  评论(1编辑  收藏  举报