client封装总体框架

client编程基于堵塞同步模式,仅仅有数据正常发送或接收才返回,假设错误发生则抛出异常,基于TcpClient进行封装,主要类结构例如以下图:

TcpClient:NET系统封装,实现了底层Socket操作,提供了堵塞和非堵塞调用;

OutgoingDataAssembler m_outgoingDataAssembler:协议组装器,用来组装往外发送的命令,主要用于组装协议格式;

DynamicBufferManager m_sendBuffer:用于把命令和数据同一时候写入到缓存中,调用一次发送,这样server就仅仅会产生一次IOCP回调,能够提高性能;

IncomingDataParser m_incomingDataParser:收到数据的解析器,用于解析返回的内容,主要是解析文本格式;

protected DynamicBufferManager m_recvBuffer:接收数据的缓存,数据存到缓存中后,能够解析命令和数据;

TcpClient说明,堵塞和非堵塞

TcpClient封装了NET的底层Socket操作,基于TCP协议,提供了堵塞和非堵塞模式调用,详细是设置m_tcpClient.Client.Blocking = true表示使用堵塞模式,反之则使用非堵塞模式。堵塞模式表示接收完指定长度的数据才返回,非堵塞模式表示收到一点数据就返回。

如我们调用m_tcpClient.Client.Receive(m_recvBuffer.Buffer, sizeof(int), packetLength, SocketFlags.None),如果传入的长度为1024,堵塞模式一点要等到数据达到1024长度才返回,否则一直等待Socket超时或者链路断了,非堵塞模式则不同,增加收到8字节了,则返回调用者,调用者使用循环继续接受1024-8=1016的数据。

发送命令

发送数据和服务端同样,主要是对数据进行组包,然后调用发送函数发送,详细代码例如以下:

  1. public void SendCommand(byte[] buffer, int offset, int count)
  2. {
  3. string commandText = m_outgoingDataAssembler.GetProtocolText();
  4. byte[] bufferUTF8 = Encoding.UTF8.GetBytes(commandText);
  5. int totalLength = sizeof(int) + bufferUTF8.Length + count; //获取总大小
  6. m_sendBuffer.Clear();
  7. m_sendBuffer.WriteInt(totalLength, false); //写入总大小
  8. m_sendBuffer.WriteInt(bufferUTF8.Length, false); //写入命令大小
  9. m_sendBuffer.WriteBuffer(bufferUTF8); //写入命令内容
  10. m_sendBuffer.WriteBuffer(buffer, offset, count); //写入二进制数据
  11. m_tcpClient.Client.Send(m_sendBuffer.Buffer, 0, m_sendBuffer.DataCount, SocketFlags.None);
  12. }

接收命令

接收命令和发送相反,先接收长度,然后接收内容,然后对数据进行解包,详细代码例如以下:

  1. public bool RecvCommand(out byte[] buffer, out int offset, out int size)
  2. {
  3. m_recvBuffer.Clear();
  4. m_tcpClient.Client.Receive(m_recvBuffer.Buffer, sizeof(int), SocketFlags.None);
  5. int packetLength = BitConverter.ToInt32(m_recvBuffer.Buffer, 0); //获取包长度
  6. if (NetByteOrder)
  7. packetLength = System.Net.IPAddress.NetworkToHostOrder(packetLength); //把网络字节顺序转为本地字节顺序
  8. m_recvBuffer.SetBufferSize(sizeof(int) + packetLength); //保证接收有足够的空间
  9. m_tcpClient.Client.Receive(m_recvBuffer.Buffer, sizeof(int), packetLength, SocketFlags.None);
  10. int commandLen = BitConverter.ToInt32(m_recvBuffer.Buffer, sizeof(int)); //取出命令长度
  11. string tmpStr = Encoding.UTF8.GetString(m_recvBuffer.Buffer, sizeof(int) + sizeof(int), commandLen);
  12. if (!m_incomingDataParser.DecodeProtocolText(tmpStr)) //解析命令
  13. {
  14. buffer = null;
  15. offset = 0;
  16. size = 0;
  17. return false;
  18. }
  19. else
  20. {
  21. buffer = m_recvBuffer.Buffer;
  22. offset = commandLen + sizeof(int) + sizeof(int);
  23. size = packetLength - offset;
  24. return true;
  25. }
  26. }

命令交互

封装了底层Socket操作和协议解析后,实现一个命令交互如登录代码例如以下:

  1. public bool DoLogin(string userName, string password)
  2. {
  3. try
  4. {
  5. m_outgoingDataAssembler.Clear();
  6. m_outgoingDataAssembler.AddRequest();
  7. m_outgoingDataAssembler.AddCommand(AsyncSocketServer.ProtocolKey.Login);
  8. m_outgoingDataAssembler.AddValue(AsyncSocketServer.ProtocolKey.UserName, userName);
  9. m_outgoingDataAssembler.AddValue(AsyncSocketServer.ProtocolKey.Password, AsyncSocketServer.BasicFunc.MD5String(password));
  10. SendCommand();
  11. bool bSuccess = RecvCommand();
  12. if (bSuccess)
  13. {
  14. bSuccess = CheckErrorCode();
  15. if (bSuccess)
  16. {
  17. m_userName = userName;
  18. m_password = password;
  19. }
  20. return bSuccess;
  21. }
  22. else
  23. return false;
  24. }
  25. catch (Exception E)
  26. {
  27. //记录日志
  28. m_errorString = E.Message;
  29. return false;
  30. }
  31. }

上传协议

上传协议主要分为三个命令,第一个是Upload,向server请求上传的文件,假设server有同样的文件,则返回是否传完,假设未传完,返回须要续传的文件位置,然后client则从上一个位置開始传输,数据传输server仅仅接收,不应答,client传输完后,发完毕(EOF)命令。因此三个命令封装代码例如以下:

  1. public bool DoUpload(string dirName, string fileName, ref long fileSize)
  2. {
  3. bool bConnect = ReConnectAndLogin(); //检測连接是否还在,假设断开则重连并登录
  4. if (!bConnect)
  5. return bConnect;
  6. try
  7. {
  8. m_outgoingDataAssembler.Clear();
  9. m_outgoingDataAssembler.AddRequest();
  10. m_outgoingDataAssembler.AddCommand(AsyncSocketServer.ProtocolKey.Upload);
  11. m_outgoingDataAssembler.AddValue(AsyncSocketServer.ProtocolKey.DirName, dirName);
  12. m_outgoingDataAssembler.AddValue(AsyncSocketServer.ProtocolKey.FileName, fileName);
  13. SendCommand();
  14. bool bSuccess = RecvCommand();
  15. if (bSuccess)
  16. {
  17. bSuccess = CheckErrorCode();
  18. if (bSuccess)
  19. {
  20. bSuccess = m_incomingDataParser.GetValue(AsyncSocketServer.ProtocolKey.FileSize, ref fileSize);
  21. }
  22. return bSuccess;
  23. }
  24. else
  25. return false;
  26. }
  27. catch (Exception E)
  28. {
  29. //记录日志
  30. m_errorString = E.Message;
  31. return false;
  32. }
  33. }
  34.  
  35. public bool DoData(byte[] buffer, int offset, int count)
  36. {
  37. try
  38. {
  39. m_outgoingDataAssembler.Clear();
  40. m_outgoingDataAssembler.AddRequest();
  41. m_outgoingDataAssembler.AddCommand(AsyncSocketServer.ProtocolKey.Data);
  42. SendCommand(buffer, offset, count);
  43. return true;
  44. }
  45. catch (Exception E)
  46. {
  47. //记录日志
  48. m_errorString = E.Message;
  49. return false;
  50. }
  51. }
  52.  
  53. public bool DoEof(Int64 fileSize)
  54. {
  55. try
  56. {
  57. m_outgoingDataAssembler.Clear();
  58. m_outgoingDataAssembler.AddRequest();
  59. m_outgoingDataAssembler.AddCommand(AsyncSocketServer.ProtocolKey.Eof);
  60. SendCommand();
  61. bool bSuccess = RecvCommand();
  62. if (bSuccess)
  63. return CheckErrorCode();
  64. else
  65. return false;
  66. }
  67. catch (Exception E)
  68. {
  69. //记录日志
  70. m_errorString = E.Message;
  71. return false;
  72. }
  73. }

调用过程:

  1. protected static bool SendFile(string fileName, ClientUploadSocket uploadSocket)
  2. {
  3. FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite);
  4. try
  5. {
  6. try
  7. {
  8. long fileSize = 0;
  9. if (!uploadSocket.DoUpload("", Path.GetFileName(fileName), ref fileSize))
  10. throw new Exception(uploadSocket.ErrorString);
  11. fileStream.Position = fileSize;
  12. byte[] readBuffer = new byte[PacketSize];
  13. while (fileStream.Position < fileStream.Length)
  14. {
  15. int count = fileStream.Read(readBuffer, 0, PacketSize);
  16. if (!uploadSocket.DoData(readBuffer, 0, count))
  17. throw new Exception(uploadSocket.ErrorString);
  18. }
  19. if (!uploadSocket.DoEof(fileStream.Length))
  20. throw new Exception(uploadSocket.ErrorString);
  21. return true;
  22. }
  23. catch (Exception E)
  24. {
  25. Console.WriteLine("Upload File Error: " + E.Message);
  26. return false;
  27. }
  28. }
  29. finally
  30. {
  31. fileStream.Close();
  32. }
  33. }

DEMO下载地址:http://download.csdn.net/detail/sqldebug_fan/7467745

免责声明:此代码仅仅是为了演示C#完毕port编程,仅用于学习和研究,切勿用于商业用途。水平有限,C#也属于初学,错误在所难免,欢迎指正和指导。邮箱地址:fansheng_hx@163.com。

C#高性能大容量SOCKET并发(十一):编写上传client的更多相关文章

  1. C#高性能大容量SOCKET并发(十一):编写上传客户端

    原文:C#高性能大容量SOCKET并发(十一):编写上传客户端 客户端封装整体框架 客户端编程基于阻塞同步模式,只有数据正常发送或接收才返回,如果发生错误则抛出异常,基于TcpClient进行封装,主 ...

  2. C#高性能大容量SOCKET并发(转)

    C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs ...

  3. C#高性能大容量SOCKET并发(四):缓存设计

    原文:C#高性能大容量SOCKET并发(四):缓存设计 在编写服务端大并发的应用程序,需要非常注意缓存设计,缓存的设计是一个折衷的结果,需要通过并发测试反复验证.有很多服务程序是在启动时申请足够的内存 ...

  4. C#高性能大容量SOCKET并发(零):代码结构说明

    原文:C#高性能大容量SOCKET并发(零):代码结构说明 C#版完成端口具有以下特点: 连接在线管理(提供在线连接维护,连接会话管理,数据接收,连接断开等相关事件跟踪): 发送数据智能合并(组件会根 ...

  5. C#高性能大容量SOCKET并发(九):断点续传

    原文:C#高性能大容量SOCKET并发(九):断点续传 上传断点续传 断点续传主要是用在上传或下载文件,一般做法是开始上传的时候,服务器返回上次已经上传的大小,如果上传完成,则返回-1:下载开始的时候 ...

  6. C#高性能大容量SOCKET并发(七):协议字符集

    原文:C#高性能大容量SOCKET并发(七):协议字符集 UTF-8 UTF-8是UNICODE的一种变长字符编码又称万国码,由Ken Thompson于1992年创建.现在已经标准化为RFC 362 ...

  7. C#高性能大容量SOCKET并发(五):粘包、分包、解包

    原文:C#高性能大容量SOCKET并发(五):粘包.分包.解包 粘包 使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一 ...

  8. C#高性能大容量SOCKET并发(三):接收、发送

    原文:C#高性能大容量SOCKET并发(三):接收.发送 异步数据接收有可能收到的数据不是一个完整包,或者接收到的数据超过一个包的大小,因此我们需要把接收的数据进行缓存.异步发送我们也需要把每个发送的 ...

  9. C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装

    原文:C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 1.SocketAsyncEventArgs介绍 SocketAsyncEventArgs是微软提供的高性能 ...

  10. C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型

    原文:C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型 线程模型 SocketAsyncEventArgs编程模式不支持设置同时工作线程个数,使用的NET的IO ...

随机推荐

  1. C# byte[]与char[]、string与char[]、byte[] 与 string 互转

    1. byte array -> char array Byte[] b=new byte[5]{0x01,0x02,0x03,0x04,0x05};  Char[] c=Encoding.AS ...

  2. wp8模拟器操作键盘

    当前焦点在模拟器上的某一个输入控件上时候, 按pagedown/pageup可以切换是用pc键盘还是模拟器键盘

  3. 【mac osx安装opencv,python总结】

    在macosx下安装opencv,最大的困难在于协调python版本.由于在opencv官网上,强烈建议安装完整版的python(不建议使用mac 内置的python),所以会碰到这个多个python ...

  4. MFC的初始化过程和消息映射技术

    1.删除#include <windows.h>--win32中的-(使用win32工程编程mfc必须删除) 添加#include <afxwin.h> -- mfc中的- 2 ...

  5. VC:CString用法整理(转载)

    1.CString::IsEmpty BOOL IsEmpty( ) const; 返回值:如果CString 对象的长度为0,则返回非零值:否则返回0. 说明:此成员函数用来测试一个CString ...

  6. android之ListView,详细介绍实现步骤,举例,自定义listview适配器

    android之ListView,详细介绍实现步骤,举例,自定义listview适配器 本文来源于www.ifyao.com禁止转载!www.ifyao.com android中如何使用listVie ...

  7. 混入模式(max-in)实现继承

    混入模式并不是一种复制完整的对象,而是从多个对象中复制出任意的成员并将这些成员组合成一个新的对象. 实现如下: function mix(){ var arg,prop,child = {}; for ...

  8. Struts2中的get、set方法作用:

    Struts2中的get.set方法作用: 在Struts2中,客户端和服务器之间的数据传输全部要用到get.set方法:用set方法 ,可以将表单中的值存入Action类.通过Struts2.0标签 ...

  9. Phalcon自动加载(PHP自动加载)

    自动加载(phalcon\Loader) 转载请注明来源 一.php文件引入 通过 include() 或 require() 函数,可以在PHP程序执行之前在该文件中插入一个文件的内容. 区别:处理 ...

  10. [1] Tornado Todo Day0

    Github地址: day0 初始化数据库: jakeychen@JC:~/Public/tornado_todo$ pwd /home/jakeychen/Public/tornado_todo j ...