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

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

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

public interface ISocket
{
/// <summary>
/// 获取是否已连接。
/// </summary>
bool IsConnected { get; }
/// <summary>
/// 发送数据。
/// </summary>
/// <param name="data">要发送的数据。</param>
void Send(byte[] data);
/// <summary>
/// 异步发送数据。
/// </summary>
/// <param name="data">要发送的数据。</param>
void SendAsync(byte[] data);
/// <summary>
/// 断开连接。
/// </summary>
void Disconnect();
/// <summary>
/// 异步断开连接。
/// </summary>
void DisconnectAsync();
/// <summary>
/// 断开完成时引发事件。
/// </summary>
event EventHandler<SocketEventArgs> DisconnectCompleted;
/// <summary>
/// 接收完成时引发事件。
/// </summary>
event EventHandler<SocketEventArgs> ReceiveCompleted;
/// <summary>
/// 发送完成时引发事件。
/// </summary>
event EventHandler<SocketEventArgs> SendCompleted;
}

用到的事件参数SocketEventArgs。

/// <summary>
/// Socket事件参数
/// </summary>
public class SocketEventArgs : EventArgs
{
/// <summary>
/// 实例化Socket事件参数
/// </summary>
/// <param name="socket">相关Socket</param>
/// <param name="operation">操作类型</param>
public SocketEventArgs(ISocket socket, SocketAsyncOperation operation)
{
if (socket == null)
throw new ArgumentNullException("socket");
Socket = socket;
Operation = operation;
} /// <summary>
/// 获取或设置事件相关数据。
/// </summary>
public byte[] Data { get; set; } /// <summary>
/// 获取数据长度。
/// </summary>
public int DataLength { get { return Data == null ? 0 : Data.Length; } } /// <summary>
/// 获取事件相关Socket
/// </summary>
public ISocket Socket { get; private set; } /// <summary>
/// 获取事件操作类型。
/// </summary>
public SocketAsyncOperation Operation { get; private set; }
}

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

定义类TCPClient继承接口ISocket和IDisposable

/// <summary>
/// TCP客户端
/// </summary>
public class TCPClient : ISocket, IDisposable
{
/// <summary>
/// 获取是否已连接。
/// </summary>
public bool IsConnected { get; }
/// <summary>
/// 发送数据。
/// </summary>
/// <param name="data">要发送的数据。</param>
public void Send(byte[] data)
{ }
/// <summary>
/// 异步发送数据。
/// </summary>
/// <param name="data">要发送的数据。</param>
public void SendAsync(byte[] data)
{ }
/// <summary>
/// 断开连接。
/// </summary>
public void Disconnect()
{ }
/// <summary>
/// 异步断开连接。
/// </summary>
public void DisconnectAsync()
{ }
/// <summary>
/// 断开完成时引发事件。
/// </summary>
public event EventHandler<SocketEventArgs> DisconnectCompleted;
/// <summary>
/// 接收完成时引发事件。
/// </summary>
public event EventHandler<SocketEventArgs> ReceiveCompleted;
/// <summary>
/// 发送完成时引发事件。
/// </summary>
public event EventHandler<SocketEventArgs> SendCompleted; /// <summary>
/// 释放资源。
/// </summary>
public void Dispose()
{ }
}

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

    /// <summary>
/// 连接至服务器。
/// </summary>
/// <param name="endpoint">服务器终结点。</param>
public void Connect(IPEndPoint endpoint)
{ } /// <summary>
/// 异步连接至服务器。
/// </summary>
/// <param name="endpoint"></param>
public void ConnectAsync(IPEndPoint endpoint)
{ }

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

把IsConnected指向Socket.Connected。

    private Socket Socket;
private Stream Stream;
/// <summary>
/// 实例化TCP客户端。
/// </summary>
public TCPClient()
{
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
} /// <summary>
/// 获取是否已连接。
/// </summary>
public bool IsConnected { get { return Socket.Connected; } }

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

internal class SocketAsyncState
{
/// <summary>
/// 是否完成。
/// </summary>
public bool Completed { get; set; } /// <summary>
/// 数据
/// </summary>
public byte[] Data { get; set; }
/// <summary>
/// 是否异步
/// </summary>
public bool IsAsync { get; set; }
}

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

    /// <summary>
/// 连接至服务器。
/// </summary>
/// <param name="endpoint">服务器终结点。</param>
public void Connect(IPEndPoint endpoint)
{
//判断是否已连接
if (IsConnected)
throw new InvalidOperationException("已连接至服务器。");
if (endpoint == null)
throw new ArgumentNullException("endpoint");
//锁定自己,避免多线程同时操作
lock (this)
{
SocketAsyncState state = new SocketAsyncState();
//Socket异步连接
Socket.BeginConnect(endpoint, EndConnect, state).AsyncWaitHandle.WaitOne();
//等待异步全部处理完成
while (!state.Completed) { }
}
} /// <summary>
/// 异步连接至服务器。
/// </summary>
/// <param name="endpoint"></param>
public void ConnectAsync(IPEndPoint endpoint)
{
//判断是否已连接
if (IsConnected)
throw new InvalidOperationException("已连接至服务器。");
if (endpoint == null)
throw new ArgumentNullException("endpoint");
//锁定自己,避免多线程同时操作
lock (this)
{
SocketAsyncState state = new SocketAsyncState();
//设置状态为异步
state.IsAsync = true;
//Socket异步连接
Socket.BeginConnect(endpoint, EndConnect, state);
}
} private void EndConnect(IAsyncResult result)
{
SocketAsyncState state = (SocketAsyncState)result.AsyncState; try
{
Socket.EndConnect(result);
}
catch
{
//出现异常,连接失败。
state.Completed = true;
//判断是否为异步,异步则引发事件
if (state.IsAsync && ConnectCompleted != null)
ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));
return;
} //连接成功。
//创建Socket网络流
Stream = new NetworkStream(Socket);
//连接完成
state.Completed = true;
if (state.IsAsync && ConnectCompleted != null)
{
ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));
} //开始接收数据
Handler.BeginReceive(Stream, EndReceive, state);
}     /// <summary>
    /// 连接完成时引发事件。
    /// </summary>
    public event EventHandler<SocketEventArgs> ConnectCompleted;

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

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

    /// <summary>
/// 断开与服务器的连接。
/// </summary>
public void Disconnect()
{
//判断是否已连接
if (!IsConnected)
throw new InvalidOperationException("未连接至服务器。");
lock (this)
{
//Socket异步断开并等待完成
Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne();
}
} /// <summary>
/// 异步断开与服务器的连接。
/// </summary>
public void DisconnectAsync()
{
//判断是否已连接
if (!IsConnected)
throw new InvalidOperationException("未连接至服务器。");
lock (this)
{
//Socket异步断开
Socket.BeginDisconnect(true, EndDisconnect, false);
}
} private void EndDisconnect(IAsyncResult result)
{
try
{
Socket.EndDisconnect(result);
}
catch
{ }
//是否同步
bool sync = (bool)result.AsyncState; if (!sync && DisconnectCompleted!=null)
{
DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));
}
} //这是一个给收发异常准备的断开引发事件方法
private void Disconnected(bool raiseEvent)
{
if (raiseEvent && DisconnectCompleted != null)
DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));
}

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

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

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

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

public interface ISocketHandler
{
/// <summary>
/// 开始接收
/// </summary>
/// <param name="stream">Socket网络流</param>
/// <param name="callback">回调函数</param>
/// <param name="state">自定义状态</param>
/// <returns>异步结果</returns>
IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state);
/// <summary>
/// 结束接收
/// </summary>
/// <param name="asyncResult">异步结果</param>
/// <returns>接收到的数据</returns>
byte[] EndReceive(IAsyncResult asyncResult);
/// <summary>
/// 开始发送
/// </summary>
/// <param name="data">要发送的数据</param>
/// <param name="offset">数据偏移</param>
/// <param name="count">发送长度</param>
/// <param name="stream">Socket网络流</param>
/// <param name="callback">回调函数</param>
/// <param name="state">自定义状态</param>
/// <returns>异步结果</returns>
IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state);
/// <summary>
/// 结束发送
/// </summary>
/// <param name="asyncResult">异步结果</param>
/// <returns>发送是否成功</returns>
bool EndSend(IAsyncResult asyncResult);
}

在TCPClient中添加一个属性。

    /// <summary>
/// Socket处理程序
/// </summary>
public ISocketHandler Handler { get; set; }

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

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

    private void EndReceive(IAsyncResult result)
{
SocketAsyncState state = (SocketAsyncState)result.AsyncState;
//接收到的数据
byte[] data = Handler.EndReceive(result);
//如果数据长度为0,则断开Socket连接
if (data.Length == 0)
{
Disconnected(true);
return;
} //再次开始接收数据
Handler.BeginReceive(Stream, EndReceive, state); //引发接收完成事件
if (ReceiveCompleted != null)
ReceiveCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Receive) { Data = data });
}

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

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

    /// <summary>
/// 发送数据。
/// </summary>
/// <param name="data">要发送的数据。</param>
public void Send(byte[] data)
{
//是否已连接
if (!IsConnected)
throw new SocketException(10057);
//发送的数据不能为null
if (data == null)
throw new ArgumentNullException("data");
//发送的数据长度不能为0
if (data.Length == 0)
throw new ArgumentException("data的长度不能为0"); //设置异步状态
SocketAsyncState state = new SocketAsyncState();
state.IsAsync = false;
state.Data = data;
try
{
//开始发送数据
Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state).AsyncWaitHandle.WaitOne();
}
catch
{
//出现异常则断开Socket连接
Disconnected(true);
}
} /// <summary>
/// 异步发送数据。
/// </summary>
/// <param name="data">要发送的数据。</param>
public void SendAsync(byte[] data)
{
//是否已连接
if (!IsConnected)
throw new SocketException(10057);
//发送的数据不能为null
if (data == null)
throw new ArgumentNullException("data");
//发送的数据长度不能为0
if (data.Length == 0)
throw new ArgumentException("data的长度不能为0"); //设置异步状态
SocketAsyncState state = new SocketAsyncState();
state.IsAsync = true;
state.Data = data;
try
{
//开始发送数据并等待完成
Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state);
}
catch
{
//出现异常则断开Socket连接
Disconnected(true);
}
} private void EndSend(IAsyncResult result)
{
SocketAsyncState state = (SocketAsyncState)result.AsyncState; //是否完成
state.Completed = Handler.EndSend(result);
//没有完成则断开Socket连接
if (!state.Completed)
Disconnected(true);
//引发发送结束事件
if (state.IsAsync && SendCompleted != null)
{
SendCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Send) { Data = state.Data });
}
}

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

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

    /// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
lock (this)
{
if (IsConnected)
Socket.Disconnect(false);
Socket.Close();
}
}

整个客户端编写完成。

下一篇将讲解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. vue构建项目全过程

    1.node版本请更新到6.9.X版本以上,不然npm依赖会出问题 2.命令行里运行npm install --global vue-cli 3.npm install --global webpac ...

  2. LeetCode(36): 有效的数独

    Medium! 题目描述: 判断一个 9x9 的数独是否有效.只需要根据以下规则,验证已经填入的数字是否有效即可. 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1 ...

  3. 步步为营-58-SQLite的使用

    说明:文档型关系数据库,多用于移动端 1.1 添加引用 ` 1.1.1 System.Data.SQLite.xml 1.1.2 System.Data.SQLite.dll 1.2 连接字符串的设置 ...

  4. stl 常用代码

    CString类型的replace ; while((pos = it->m_strFile.find(_T("%UC_INSTALL_ROOT%\\"), pos)) != ...

  5. poj 1961 (求字符串中的重复子串)

    Sample Input 3aaa12aabaabaabaab0Sample Output Test case #12 23 3 Test case #22 2 //aa有2个a6 2 //aabaa ...

  6. java 泛型 ? 和 T的区别

    看了一个CSDN的问题,感觉就清楚了:http://bbs.csdn.net/topics/300181589/ 摘录其中的重点: 泛型方法: public <T extends Object& ...

  7. Codeforces 247D Mike and Fish

    Mike and Fish 我们可以把这个模型转换一下就变成有两类点,一类是X轴, 一类是Y轴, 每个点相当于对应的点之间建一条边, 如果这条边变红两点同时+1, 变蓝两点同时-1. 我们能发现这个图 ...

  8. Codeforces 251C Number Transformation

    Number Transformation 我们能发现这个东西是以2 - k的lcm作为一个循环节, 然后bfs就好啦. #include<bits/stdc++.h> #define L ...

  9. P2399 non hates math

    P2399 non hates math将分数化成小数的模拟题,把循环减掉就可以了.1.1(234)*10^4==11234.234*10^1==11.2349999*(1.1(234))==1122 ...

  10. 20172302 《Java软件结构与数据结构》第六周学习总结

    2018年学习总结博客总目录:第一周 第二周 第三周 第四周 第五周 第六周 教材学习内容总结 1.树的概述及基本概念 (1)树是一种非线性数据结构,其中的元素被组织成了一个层次结构. (2)树由一个 ...