[C#]手把手教你打造Socket的TCP通讯连接(一)
本文章将讲解基于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