本文章将讲解基于TCP连接的Socket通讯,使用Socket异步功能,并且无粘包现象,通过事件驱动使用。

在编写Socket代码之前,我们得要定义一下Socket的基本功能。

作为一个TCP连接,不论是客户端还是服务器端,它都得有以下接口:

  1. public interface ISocket
  2. {
  3. /// <summary>
  4. /// 获取是否已连接。
  5. /// </summary>
  6. bool IsConnected { get; }
  7. /// <summary>
  8. /// 发送数据。
  9. /// </summary>
  10. /// <param name="data">要发送的数据。</param>
  11. void Send(byte[] data);
  12. /// <summary>
  13. /// 异步发送数据。
  14. /// </summary>
  15. /// <param name="data">要发送的数据。</param>
  16. void SendAsync(byte[] data);
  17. /// <summary>
  18. /// 断开连接。
  19. /// </summary>
  20. void Disconnect();
  21. /// <summary>
  22. /// 异步断开连接。
  23. /// </summary>
  24. void DisconnectAsync();
  25. /// <summary>
  26. /// 断开完成时引发事件。
  27. /// </summary>
  28. event EventHandler<SocketEventArgs> DisconnectCompleted;
  29. /// <summary>
  30. /// 接收完成时引发事件。
  31. /// </summary>
  32. event EventHandler<SocketEventArgs> ReceiveCompleted;
  33. /// <summary>
  34. /// 发送完成时引发事件。
  35. /// </summary>
  36. event EventHandler<SocketEventArgs> SendCompleted;
  37. }

用到的事件参数SocketEventArgs。

  1. /// <summary>
  2. /// Socket事件参数
  3. /// </summary>
  4. public class SocketEventArgs : EventArgs
  5. {
  6. /// <summary>
  7. /// 实例化Socket事件参数
  8. /// </summary>
  9. /// <param name="socket">相关Socket</param>
  10. /// <param name="operation">操作类型</param>
  11. public SocketEventArgs(ISocket socket, SocketAsyncOperation operation)
  12. {
  13. if (socket == null)
  14. throw new ArgumentNullException("socket");
  15. Socket = socket;
  16. Operation = operation;
  17. }
  18.  
  19. /// <summary>
  20. /// 获取或设置事件相关数据。
  21. /// </summary>
  22. public byte[] Data { get; set; }
  23.  
  24. /// <summary>
  25. /// 获取数据长度。
  26. /// </summary>
  27. public int DataLength { get { return Data == null ? 0 : Data.Length; } }
  28.  
  29. /// <summary>
  30. /// 获取事件相关Socket
  31. /// </summary>
  32. public ISocket Socket { get; private set; }
  33.  
  34. /// <summary>
  35. /// 获取事件操作类型。
  36. /// </summary>
  37. public SocketAsyncOperation Operation { get; private set; }
  38. }

因为作为客户端只管收发,比较简单,所以这里从客户端开始做起。

定义类TCPClient继承接口ISocket和IDisposable

  1. /// <summary>
  2. /// TCP客户端
  3. /// </summary>
  4. public class TCPClient : ISocket, IDisposable
  5. {
  6. /// <summary>
  7. /// 获取是否已连接。
  8. /// </summary>
  9. public bool IsConnected { get; }
  10. /// <summary>
  11. /// 发送数据。
  12. /// </summary>
  13. /// <param name="data">要发送的数据。</param>
  14. public void Send(byte[] data)
  15. {
  16.  
  17. }
  18. /// <summary>
  19. /// 异步发送数据。
  20. /// </summary>
  21. /// <param name="data">要发送的数据。</param>
  22. public void SendAsync(byte[] data)
  23. {
  24.  
  25. }
  26. /// <summary>
  27. /// 断开连接。
  28. /// </summary>
  29. public void Disconnect()
  30. {
  31.  
  32. }
  33. /// <summary>
  34. /// 异步断开连接。
  35. /// </summary>
  36. public void DisconnectAsync()
  37. {
  38.  
  39. }
  40. /// <summary>
  41. /// 断开完成时引发事件。
  42. /// </summary>
  43. public event EventHandler<SocketEventArgs> DisconnectCompleted;
  44. /// <summary>
  45. /// 接收完成时引发事件。
  46. /// </summary>
  47. public event EventHandler<SocketEventArgs> ReceiveCompleted;
  48. /// <summary>
  49. /// 发送完成时引发事件。
  50. /// </summary>
  51. public event EventHandler<SocketEventArgs> SendCompleted;
  52.  
  53. /// <summary>
  54. /// 释放资源。
  55. /// </summary>
  56. public void Dispose()
  57. {
  58.  
  59. }
  60. }

并在此之上,增加以下方法

  1. /// <summary>
  2. /// 连接至服务器。
  3. /// </summary>
  4. /// <param name="endpoint">服务器终结点。</param>
  5. public void Connect(IPEndPoint endpoint)
  6. {
  7.  
  8. }
  9.  
  10. /// <summary>
  11. /// 异步连接至服务器。
  12. /// </summary>
  13. /// <param name="endpoint"></param>
  14. public void ConnectAsync(IPEndPoint endpoint)
  15. {
  16.  
  17. }

下面我们开始编写构造函数,实例化一个Socket并保存到私有变量里。

把IsConnected指向Socket.Connected。

  1. private Socket Socket;
  2. private Stream Stream;
  3. /// <summary>
  4. /// 实例化TCP客户端。
  5. /// </summary>
  6. public TCPClient()
  7. {
  8. Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  9. }
  10.  
  11. /// <summary>
  12. /// 获取是否已连接。
  13. /// </summary>
  14. public bool IsConnected { get { return Socket.Connected; } }

因为接下来我们开始编写Socket的异步功能,所以在此之前,我们要做一个状态类,用来保存异步状态。

  1. internal class SocketAsyncState
  2. {
  3. /// <summary>
  4. /// 是否完成。
  5. /// </summary>
  6. public bool Completed { get; set; }
  7.  
  8. /// <summary>
  9. /// 数据
  10. /// </summary>
  11. public byte[] Data { get; set; }
  12. /// <summary>
  13. /// 是否异步
  14. /// </summary>
  15. public bool IsAsync { get; set; }
  16. }

下面我们开始编写TCP连接功能。

  1. /// <summary>
  2. /// 连接至服务器。
  3. /// </summary>
  4. /// <param name="endpoint">服务器终结点。</param>
  5. public void Connect(IPEndPoint endpoint)
  6. {
  7. //判断是否已连接
  8. if (IsConnected)
  9. throw new InvalidOperationException("已连接至服务器。");
  10. if (endpoint == null)
  11. throw new ArgumentNullException("endpoint");
  12. //锁定自己,避免多线程同时操作
  13. lock (this)
  14. {
  15. SocketAsyncState state = new SocketAsyncState();
  16. //Socket异步连接
  17. Socket.BeginConnect(endpoint, EndConnect, state).AsyncWaitHandle.WaitOne();
  18. //等待异步全部处理完成
  19. while (!state.Completed) { }
  20. }
  21. }
  22.  
  23. /// <summary>
  24. /// 异步连接至服务器。
  25. /// </summary>
  26. /// <param name="endpoint"></param>
  27. public void ConnectAsync(IPEndPoint endpoint)
  28. {
  29. //判断是否已连接
  30. if (IsConnected)
  31. throw new InvalidOperationException("已连接至服务器。");
  32. if (endpoint == null)
  33. throw new ArgumentNullException("endpoint");
  34. //锁定自己,避免多线程同时操作
  35. lock (this)
  36. {
  37. SocketAsyncState state = new SocketAsyncState();
  38. //设置状态为异步
  39. state.IsAsync = true;
  40. //Socket异步连接
  41. Socket.BeginConnect(endpoint, EndConnect, state);
  42. }
  43. }
  44.  
  45. private void EndConnect(IAsyncResult result)
  46. {
  47. SocketAsyncState state = (SocketAsyncState)result.AsyncState;
  48.  
  49. try
  50. {
  51. Socket.EndConnect(result);
  52. }
  53. catch
  54. {
  55. //出现异常,连接失败。
  56. state.Completed = true;
  57. //判断是否为异步,异步则引发事件
  58. if (state.IsAsync && ConnectCompleted != null)
  59. ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));
  60. return;
  61. }
  62.  
  63. //连接成功。
  64. //创建Socket网络流
  65. Stream = new NetworkStream(Socket);
  66. //连接完成
  67. state.Completed = true;
  68. if (state.IsAsync && ConnectCompleted != null)
  69. {
  70. ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));
  71. }
  72.  
  73. //开始接收数据
  74. Handler.BeginReceive(Stream, EndReceive, state);
  75. }
  76.  
  77.     /// <summary>
        /// 连接完成时引发事件。
        /// </summary>
        public event EventHandler<SocketEventArgs> ConnectCompleted;

以上为连接服务器的代码,EndConnect中最后的Handler为一个处理IO收发的类,这留到后面再说。

接下来我们开始做断开服务器的方法。

  1. /// <summary>
  2. /// 断开与服务器的连接。
  3. /// </summary>
  4. public void Disconnect()
  5. {
  6. //判断是否已连接
  7. if (!IsConnected)
  8. throw new InvalidOperationException("未连接至服务器。");
  9. lock (this)
  10. {
  11. //Socket异步断开并等待完成
  12. Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne();
  13. }
  14. }
  15.  
  16. /// <summary>
  17. /// 异步断开与服务器的连接。
  18. /// </summary>
  19. public void DisconnectAsync()
  20. {
  21. //判断是否已连接
  22. if (!IsConnected)
  23. throw new InvalidOperationException("未连接至服务器。");
  24. lock (this)
  25. {
  26. //Socket异步断开
  27. Socket.BeginDisconnect(true, EndDisconnect, false);
  28. }
  29. }
  30.  
  31. private void EndDisconnect(IAsyncResult result)
  32. {
  33. try
  34. {
  35. Socket.EndDisconnect(result);
  36. }
  37. catch
  38. {
  39.  
  40. }
  41. //是否同步
  42. bool sync = (bool)result.AsyncState;
  43.  
  44. if (!sync && DisconnectCompleted!=null)
  45. {
  46. DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));
  47. }
  48. }
  49.  
  50. //这是一个给收发异常准备的断开引发事件方法
  51. private void Disconnected(bool raiseEvent)
  52. {
  53. if (raiseEvent && DisconnectCompleted != null)
  54. DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));
  55. }

至此,我们已经完成了客户端的连接于断开功能。

现在我们开始写客户端的发送接收功能。

对于Socket的发送与接收,在大量数据吞吐的时候,容易造成粘包问题,要解决这个问题,我们先定义一个ISocketHandler接口。

该接口定义了Socket的发送与接收。

  1. public interface ISocketHandler
  2. {
  3. /// <summary>
  4. /// 开始接收
  5. /// </summary>
  6. /// <param name="stream">Socket网络流</param>
  7. /// <param name="callback">回调函数</param>
  8. /// <param name="state">自定义状态</param>
  9. /// <returns>异步结果</returns>
  10. IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state);
  11. /// <summary>
  12. /// 结束接收
  13. /// </summary>
  14. /// <param name="asyncResult">异步结果</param>
  15. /// <returns>接收到的数据</returns>
  16. byte[] EndReceive(IAsyncResult asyncResult);
  17. /// <summary>
  18. /// 开始发送
  19. /// </summary>
  20. /// <param name="data">要发送的数据</param>
  21. /// <param name="offset">数据偏移</param>
  22. /// <param name="count">发送长度</param>
  23. /// <param name="stream">Socket网络流</param>
  24. /// <param name="callback">回调函数</param>
  25. /// <param name="state">自定义状态</param>
  26. /// <returns>异步结果</returns>
  27. IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state);
  28. /// <summary>
  29. /// 结束发送
  30. /// </summary>
  31. /// <param name="asyncResult">异步结果</param>
  32. /// <returns>发送是否成功</returns>
  33. bool EndSend(IAsyncResult asyncResult);
  34. }

在TCPClient中添加一个属性。

  1. /// <summary>
  2. /// Socket处理程序
  3. /// </summary>
  4. public ISocketHandler Handler { get; set; }

这个ISocketHandler在上面的EndConnect里有使用到BeginReceive()。

而使用BeginReceive的回调函数则是这个。

  1. private void EndReceive(IAsyncResult result)
  2. {
  3. SocketAsyncState state = (SocketAsyncState)result.AsyncState;
  4. //接收到的数据
  5. byte[] data = Handler.EndReceive(result);
  6. //如果数据长度为0,则断开Socket连接
  7. if (data.Length == 0)
  8. {
  9. Disconnected(true);
  10. return;
  11. }
  12.  
  13. //再次开始接收数据
  14. Handler.BeginReceive(Stream, EndReceive, state);
  15.  
  16. //引发接收完成事件
  17. if (ReceiveCompleted != null)
  18. ReceiveCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Receive) { Data = data });
  19. }

有了这个回调函数,我们的客户端就能持续的接收数据。

现在剩下发送数据的功能要完成。

  1. /// <summary>
  2. /// 发送数据。
  3. /// </summary>
  4. /// <param name="data">要发送的数据。</param>
  5. public void Send(byte[] data)
  6. {
  7. //是否已连接
  8. if (!IsConnected)
  9. throw new SocketException(10057);
  10. //发送的数据不能为null
  11. if (data == null)
  12. throw new ArgumentNullException("data");
  13. //发送的数据长度不能为0
  14. if (data.Length == 0)
  15. throw new ArgumentException("data的长度不能为0");
  16.  
  17. //设置异步状态
  18. SocketAsyncState state = new SocketAsyncState();
  19. state.IsAsync = false;
  20. state.Data = data;
  21. try
  22. {
  23. //开始发送数据
  24. Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state).AsyncWaitHandle.WaitOne();
  25. }
  26. catch
  27. {
  28. //出现异常则断开Socket连接
  29. Disconnected(true);
  30. }
  31. }
  32.  
  33. /// <summary>
  34. /// 异步发送数据。
  35. /// </summary>
  36. /// <param name="data">要发送的数据。</param>
  37. public void SendAsync(byte[] data)
  38. {
  39. //是否已连接
  40. if (!IsConnected)
  41. throw new SocketException(10057);
  42. //发送的数据不能为null
  43. if (data == null)
  44. throw new ArgumentNullException("data");
  45. //发送的数据长度不能为0
  46. if (data.Length == 0)
  47. throw new ArgumentException("data的长度不能为0");
  48.  
  49. //设置异步状态
  50. SocketAsyncState state = new SocketAsyncState();
  51. state.IsAsync = true;
  52. state.Data = data;
  53. try
  54. {
  55. //开始发送数据并等待完成
  56. Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state);
  57. }
  58. catch
  59. {
  60. //出现异常则断开Socket连接
  61. Disconnected(true);
  62. }
  63. }
  64.  
  65. private void EndSend(IAsyncResult result)
  66. {
  67. SocketAsyncState state = (SocketAsyncState)result.AsyncState;
  68.  
  69. //是否完成
  70. state.Completed = Handler.EndSend(result);
  71. //没有完成则断开Socket连接
  72. if (!state.Completed)
  73. Disconnected(true);
  74. //引发发送结束事件
  75. if (state.IsAsync && SendCompleted != null)
  76. {
  77. SendCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Send) { Data = state.Data });
  78. }
  79. }

至此,客户端的发送接收也完成了。

我们再写一下释放资源的方法。

  1. /// <summary>
  2. /// 释放资源
  3. /// </summary>
  4. public void Dispose()
  5. {
  6. lock (this)
  7. {
  8. if (IsConnected)
  9. Socket.Disconnect(false);
  10. Socket.Close();
  11. }
  12. }

整个客户端编写完成。

下一篇将讲解ISocketHandler的实现方法,用ISocketHandler来完成Socket的IO工作。

你可以在ISocketHandler中定义你自己的Socket通讯协议。

原文地址:http://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html

[C#]手把手教你打造Socket的TCP通讯连接(一)的更多相关文章

  1. Android:手把手教你打造可缩放移动的ImageView(下)

    在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...

  2. C# Socket的TCP通讯

    Socket的TCP通讯 一. socket的通讯原理 服务器端的步骤如下. (1)建立服务器端的Socket,开始侦听整个网络中的连接请求. (2)当检测到来自客户端的连接请求时,向客户端发送收到连 ...

  3. 手把手教你打造高效的 Kubernetes 命令行终端

    Kubernetes 作为云原生时代的操作系统,熟悉和使用它是每名用户的必备技能.本文将介绍一些提高操作 Kubernetes 效率的技巧以及如何打造一个高效的 Kubernetes 命令行终端的方法 ...

  4. 手把手教你打造一个纯CSS图标库

    来,干了这碗安利 写这篇文章的目的其实就是为了安利一下我的图标库:iconoo,所以,开门见山,star吧少年少妇们!(这样的我是不是应该要加个github互粉的团伙了?) 主题说完了,下面进入正题. ...

  5. 手把手教你打造一个心电图效果View Android自定义View

    大家好,看我像不像蘑菇-因为我在学校呆的发霉了. 思而不学则殆 丽丽说得对,我有奇怪的疑问,大都是思而不学造成的,在我书读不够的情况下想太多,大多等于白想,所以革命没成功,同志仍需努力. 好了废话不说 ...

  6. Android:手把手教你打造可缩放移动的ImageView(上)

    定义ImageView,实现功能如下: 1.初始化时图片垂直居中显示,拉伸图片宽度至ImageView宽度. 2.使用两根手指放大缩小图片,可设置最大放大倍数,当图片小于ImageView宽度时,在手 ...

  7. 手把手教你打造ImageView支持手势放大缩小

    写在前面 最近有了新的任务,学习的时间比以前少了不少,Java回炉的文估计是得缓缓了,不过每周一篇尽量保质保量.最近感觉我文写的有点不好,因为我写东西除非必要,不然概念性的东西我基本上都是一笔带过…… ...

  8. 手把手教你打造一个 Mac 风格的 Windows10(手动滑稽)

    Mark  https://www.sqlsec.com/2018/04/winmac.html 大佬写得很好,资瓷!! MyDock可能不是最新的,给出官方维护的网盘:https://pan.bai ...

  9. 手把手教你打造一款轻量级canvas渲染引擎

    背景 当我们开发一个canvas应用的时候,出于效率的考量,免不了要选择一个渲染引擎(比如PixiJS)或者更强大一点的游戏引擎(比如Cocos Creator.Layabox). 渲染引擎通常会有S ...

随机推荐

  1. CF1081A

    CF1081A 题意: 从

  2. hdu4052矩形面积并

    建模需要注意下细节,,这是做扫描线的惯例,就是最好把模型建立在笛卡尔坐标系上 剩下的看链接和注释https://blog.csdn.net/shiqi_614/article/details/7983 ...

  3. [SDOI2012]象棋

    题解: sd的题目也真是奇怪 第一题有了最短路第二题还有 第二题有了网络流第三题还有 显然是可以网络流的 但考虑每个点只能存在一个这个条件 刚开始我以为是建分层图..但发现这个时间复杂度太高了 其实我 ...

  4. json转成java对象

    avro生成的代码里,String是CharSequence,不能通过Gson反序列化,于是有了下面的代码,ParseArray里还不完善: static <T> List<T> ...

  5. Python 2维数组90度旋转

    一.二维列表 a = [[col for col in range(4)] for row in range(4)] [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3] ...

  6. 查找mac下腾讯视频下载地址

    mac 腾讯视频下载的视频是不可见的,也许是因为版权原因吧.使用以下方法可以在文件中找到缓存的视频(不过都是被断开的很多短视频). 在terminal输入: cd Library/Containers ...

  7. P2246 SAC#1 - Hello World(升级版)

    P2246 SAC#1 - Hello World(升级版)典型的字符串dpf[i][j]表示a串匹配到i,b串匹配到j的方案数.if(a[i]==b[j])f[i][j]=f[i-1][j-1]+f ...

  8. 仿win10环境变量助手

    相信很多人用过win10后再换回win7或者xp等系统都会有些不习惯,所以在这里安利一款博主自己开发的小软件:环境变量助手. 继承于Win10风格的环境变量,操作简单方便,使用时需要账户权限. 注意: ...

  9. mysql字符类型大小写敏感的讨论

    mysql字符类型默认是不区分大小写的,即select * from t where name='AAA'与='aaa'没区别,以下是测试的例子 (root)); (root,,,,'BbB'); ( ...

  10. 使用tortoisegit简化命令

    1. 如果希望git保存用户名和密码,后续操作都无需输入密码: git命令: git config --global credential.helper store   或者通过tortoisegit ...