手把手教你打造Socket的TCP通讯连接
转自:http://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html
本文章将讲解基于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接口。
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); }
做一个类SocketHandler继承ISocketHandler接口
/// <summary> /// Socket处理程序 /// </summary> public class SocketHandler : ISocketHandler { /// <summary> /// 开始接收 /// </summary> /// <param name="stream">Socket网络流</param> /// <param name="callback">回调函数</param> /// <param name="state">自定义状态</param> /// <returns>异步结果</returns> public IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state) { } /// <summary> /// 结束接收 /// </summary> /// <param name="asyncResult">异步结果</param> /// <returns>接收到的数据</returns> public 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> public IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state) { } /// <summary> /// 结束发送 /// </summary> /// <param name="asyncResult">异步结果</param> /// <returns>发送是否成功</returns> public bool EndSend(IAsyncResult asyncResult) { } }
增加两个属性与构造函数。
//异步处理关系集合 private Dictionary<IAsyncResult, SocketHandlerState> StateSet; //发送队列 private List<SocketHandlerState> SendQueue; /// <summary> /// 实例化Socket处理程序 /// </summary> public SocketHandler() { StateSet = new Dictionary<IAsyncResult, SocketHandlerState>(); SendQueue = new List<SocketHandlerState>(); }
StateSet可以保存我们的异步调用结果等数据
SendQueue用来做一个发送队列
接下来我们从发送数据开始。
由于需要用到Stream的异步方法,我们需要定义一个State类。
internal class SocketHandlerState { /// <summary> /// 数据 /// </summary> public byte[] Data { get; set; } /// <summary> /// 异步结果 /// </summary> public IAsyncResult AsyncResult { get; set; } /// <summary> /// Socket网络流 /// </summary> public Stream Stream { get; set; } /// <summary> /// 异步回调函数 /// </summary> public AsyncCallback AsyncCallBack { get; set; } /// <summary> /// 是否完成 /// </summary> public bool Completed { get; set; } /// <summary> /// 数据长度 /// </summary> public int DataLength { get; set; } }
因为我们需要返回IAsyncResult,所以我们继承该接口做一个SocketHandlerState类。
/// <summary> /// Socket异步操作状态 /// </summary> public class SocketAsyncResult : IAsyncResult { /// <summary> /// 实例化Socket异步操作状态 /// </summary> /// <param name="state"></param> public SocketAsyncResult(object state) { AsyncState = state; AsyncWaitHandle = new AutoResetEvent(false); } /// <summary> /// 获取用户定义的对象,它限定或包含关于异步操作的信息。 /// </summary> public object AsyncState { get; private set; } /// <summary> /// 获取用于等待异步操作完成的 System.Threading.WaitHandle。 /// </summary> public WaitHandle AsyncWaitHandle { get; private set; } /// <summary> /// 获取一个值,该值指示异步操作是否同步完成。 /// </summary> public bool CompletedSynchronously { get { return false; } } /// <summary> /// 获取一个值,该值指示异步操作是否已完成。 /// </summary> public bool IsCompleted { get; internal set; } }
然后开始编写发送数据相关函数。
这里我将发送数据的大小限制为最大65535。
只需发送长度为2的头信息即可把数据长度发送到对方。
/// <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> public IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state) { //data不能为null if (data == null) throw new ArgumentNullException("data"); //offset不能小于0和超过data长度 if (offset > data.Length || offset < 0) throw new ArgumentOutOfRangeException("offset"); //count不能大于65535 if (count <= 0 || count > data.Length - offset || count > ushort.MaxValue) throw new ArgumentOutOfRangeException("count"); //stream不能为null if (stream == null) throw new ArgumentNullException("stream"); //回调函数不能为null if (callback == null) throw new ArgumentNullException("callback"); //stream异常 if (!stream.CanWrite) throw new ArgumentException("stream不支持写入。"); SocketAsyncResult result = new SocketAsyncResult(state); //初始化SocketHandlerState SocketHandlerState shs = new SocketHandlerState(); shs.Data = data; shs.AsyncResult = result; shs.Stream = stream; shs.AsyncCallBack = callback; shs.DataLength = 0; //锁定SendQueue //避免多线程同时发送数据 lock (SendQueue) { //添加状态 SendQueue.Add(shs); //如果SendQueue数量大于1,则表示有数据尚未发送完成 if (SendQueue.Count > 1) return result; } //获取数据长度 //ushort的最大值为65535 //转换为byte[]长度为2 var dataLength = BitConverter.GetBytes((ushort)data.Length); //向对方发送长度为2的头信息,表示接下来要发送的数据长度 stream.Write(dataLength, 0, dataLength.Length); //开始异步发送数据 stream.BeginWrite(shs.Data, 0, shs.Data.Length, EndWrite, shs).AsyncWaitHandle.WaitOne(); return result; } //stream异步结束写入 private void EndWrite(IAsyncResult ar) { SocketHandlerState state = (SocketHandlerState)ar.AsyncState; //锁定StateSet lock (StateSet) StateSet.Add(state.AsyncResult, state); try { state.Stream.EndWrite(ar); } catch { //出现Socket异常,发送失败 state.Completed = false; //允许等待线程继续 ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set(); //执行异步回调函数 state.AsyncCallBack(state.AsyncResult); return; } //发送成功 state.Completed = true; //允许等待线程继续 ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set(); //执行异步回调函数 state.AsyncCallBack(state.AsyncResult); //锁定SendQueue lock (SendQueue) { SocketHandlerState prepare = null; //移除当前发送完成的数据 SendQueue.Remove(state); //如果SendQueue还有数据存在,则继续发送 if (SendQueue.Count > 0) { prepare = SendQueue[0]; } if (prepare != null) { //获取数据长度 //ushort的最大值为65535 //转换为byte[]长度为2 var dataLength = BitConverter.GetBytes((ushort)prepare.Data.Length); //向对方发送长度为2的头信息,表示接下来要发送的数据长度 prepare.Stream.Write(dataLength, 0, dataLength.Length); //开始异步发送数据 prepare.Stream.BeginWrite(prepare.Data, 0, prepare.Data.Length, EndWrite, prepare).AsyncWaitHandle.WaitOne(); } } } /// <summary> /// 结束发送 /// </summary> /// <param name="asyncResult">异步结果</param> /// <returns>发送是否成功</returns> public bool EndSend(IAsyncResult asyncResult) { //判断异步操作状态是否属于当前处理程序 if (!StateSet.ContainsKey(asyncResult)) throw new ArgumentException("无法识别的asyncResult。"); SocketHandlerState state = StateSet[asyncResult]; lock (StateSet) StateSet.Remove(asyncResult); return state.Completed; }
接下来是接收数据的相关方法。
/// <summary> /// 开始接收 /// </summary> /// <param name="stream">Socket网络流</param> /// <param name="callback">回调函数</param> /// <param name="state">自定义状态</param> /// <returns>异步结果</returns> public IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state) { //stream不能为null if (stream == null) throw new ArgumentNullException("stream"); //回调函数不能为null if (callback == null) throw new ArgumentNullException("callback"); //stream异常 if (!stream.CanRead) throw new ArgumentException("stream不支持读取。"); SocketAsyncResult result = new SocketAsyncResult(state); //初始化SocketHandlerState SocketHandlerState shs = new SocketHandlerState(); shs.Data = new byte[2]; shs.AsyncResult = result; shs.Stream = stream; shs.AsyncCallBack = callback; shs.Completed = true; //开始异步接收长度为2的头信息 //该头信息包含要接收的主要数据长度 stream.BeginRead(shs.Data, 0, 2, EndRead, shs); return result; } //stream异步结束读取 private void EndRead(IAsyncResult ar) { SocketHandlerState state = (SocketHandlerState)ar.AsyncState; int dataLength; try { dataLength = state.Stream.EndRead(ar); } catch { dataLength = 0; } //dataLength为0则表示Socket断开连接 if (dataLength == 0) { lock (StateSet) StateSet.Add(state.AsyncResult, state); //设定接收到的数据位空byte数组 state.Data = new byte[0]; //允许等待线程继续 ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set(); //执行异步回调函数 state.AsyncCallBack(state.AsyncResult); return; } //如果是已完成状态,则表示state.Data的数据是头信息 if (state.Completed) { //设定状态为未完成 state.Completed = false; //已接收得数据长度为0 state.DataLength = 0; //获取主要数据长度 var length = BitConverter.ToUInt16(state.Data, 0); //初始化数据的byte数组 state.Data = new byte[length]; try { //开始异步接收主要数据 state.Stream.BeginRead(state.Data, 0, length, EndRead, state); } catch { //出现Socket异常 lock (StateSet) StateSet.Add(state.AsyncResult, state); state.Data = new byte[0]; ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set(); state.AsyncCallBack(state.AsyncResult); } return; } //接收到主要数据 else { //判断是否接收了完整的数据 if (dataLength + state.DataLength != state.Data.Length) { //增加已接收数据长度 state.DataLength += dataLength; try { //继续接收数据 state.Stream.BeginRead(state.Data, state.DataLength, state.Data.Length - state.DataLength, EndRead, state); } catch { //出现Socket异常 lock (StateSet) StateSet.Add(state.AsyncResult, state); state.Data = new byte[0]; ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set(); state.AsyncCallBack(state.AsyncResult); return; } return; } //接收完成 state.Completed = true; lock (StateSet) StateSet.Add(state.AsyncResult, state); ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set(); state.AsyncCallBack(state.AsyncResult); } } /// <summary> /// 结束接收 /// </summary> /// <param name="asyncResult">异步结果</param> /// <returns>接收到的数据</returns> public byte[] EndReceive(IAsyncResult asyncResult) { //判断异步操作状态是否属于当前处理程序 if (!StateSet.ContainsKey(asyncResult)) throw new ArgumentException("无法识别的asyncResult。"); SocketHandlerState state = StateSet[asyncResult]; lock (StateSet) StateSet.Remove(asyncResult); return state.Data; }
至此,SocketHandler的功能已经实现。
现在我们只剩下服务器端了。
服务器端包含两个类,一个TCPListener,一个TCPListenerClient。
TCPListener只管Start与Stop还有Accept。
TCPListenerClient是连接到服务器的客户端,相当于TCPClient在TCPListener上的体现。
现在我们开始编写TCPListener。
/// <summary> /// TCP监听端 /// </summary> public class TCPListener : IEnumerable<TCPListenerClient> { private Socket socket; private HashSet<TCPListenerClient> clients; /// <summary> /// 实例化TCP监听者。 /// </summary> public TCPListener() { clients = new HashSet<TCPListenerClient>(); IsStarted = false; Handler = new SocketHandler(); } public ISocketHandler Handler { get; set; } private int port; /// <summary> /// 监听端口。 /// </summary> public int Port { get { return port; } set { if (value < 0 || value > 65535) throw new ArgumentOutOfRangeException(port + "不是有效端口。"); port = value; } } /// <summary> /// 服务启动中 /// </summary> public bool IsStarted { get; private set; } /// <summary> /// 开始服务。 /// </summary> public void Start() { } /// <summary> /// 停止服务。 /// </summary> public void Stop() { } /// <summary> /// 接收完成时引发事件。 /// </summary> public event EventHandler<SocketEventArgs> ReceiveCompleted; /// <summary> /// 接受客户完成时引发事件。 /// </summary> public event EventHandler<SocketEventArgs> AcceptCompleted; /// <summary> /// 客户断开完成时引发事件。 /// </summary> public event EventHandler<SocketEventArgs> DisconnectCompleted; /// <summary> /// 发送完成时引发事件。 /// </summary> public event EventHandler<SocketEventArgs> SendCompleted; /// <summary> /// 获取客户端泛型。 /// </summary> /// <returns></returns> public IEnumerator<TCPListenerClient> GetEnumerator() { return clients.GetEnumerator(); } /// <summary> /// 获取客户端泛型。 /// </summary> /// <returns></returns> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return clients.GetEnumerator(); } /// <summary> /// 释放资源。 /// </summary> /// <returns></returns> public void Dispose() { } }
TCPListener继承IEnumerable<TCPListenerClient>与IDisposable
clients保存所有已连接的客户端。
编写Start方法。
/// <summary> /// 开始服务。 /// </summary> public void Start() { lock (this) { if (IsStarted) throw new InvalidOperationException("已经开始服务。"); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //绑定端口 //可以引发端口被占用异常 socket.Bind(new IPEndPoint(IPAddress.Any, port)); //监听队列 socket.Listen(512); //如果端口是0,则是随机端口,把这个端口赋值给port port = ((IPEndPoint)socket.LocalEndPoint).Port; //服务启动中设置为true IsStarted = true; //开始异步监听 socket.BeginAccept(EndAccept, null); } } //异步监听结束 private void EndAccept(IAsyncResult result) { //获得客户端Socket Socket clientSocket = socket.EndAccept(result); //实例化客户端类 TCPListenerClient client = new TCPListenerClient(this, clientSocket); //增加事件钩子 client.SendCompleted += client_SendCompleted; client.ReceiveCompleted += client_ReceiveCompleted; client.DisconnectCompleted += client_DisconnectCompleted; socket.BeginAccept(EndAccept, null); //增加客户端 lock (clients) clients.Add(client); //客户端连接事件 if (AcceptCompleted != null) AcceptCompleted(this, new SocketEventArgs(client, SocketAsyncOperation.Accept)); } //客户端断开连接 private void client_DisconnectCompleted(object sender, SocketEventArgs e) { //移除客户端 lock (clients) clients.Remove((TCPListenerClient)e.Socket); e.Socket.DisconnectCompleted -= client_DisconnectCompleted; e.Socket.ReceiveCompleted -= client_ReceiveCompleted; e.Socket.SendCompleted -= client_SendCompleted; if (DisconnectCompleted != null) DisconnectCompleted(this, e); } //收到客户端发送的数据 private void client_ReceiveCompleted(object sender, SocketEventArgs e) { if (ReceiveCompleted != null) ReceiveCompleted(this, e); } //向客户端发送数据完成 private void client_SendCompleted(object sender, SocketEventArgs e) { if (SendCompleted != null) SendCompleted(this, e); }
编写Stop与Dispose方法。
/// <summary> /// 停止服务。 /// </summary> public void Stop() { lock (this) { if (!IsStarted) throw new InvalidOperationException("没有开始服务。"); foreach (TCPListenerClient client in clients) { client.Disconnect(); client.DisconnectCompleted -= client_DisconnectCompleted; client.ReceiveCompleted -= client_ReceiveCompleted; client.SendCompleted -= client_SendCompleted; } socket.Close(); socket = null; IsStarted = false; } } /// <summary> /// 释放资源 /// </summary> public void Dispose() { if (socket == null) return; Stop(); }
轮到TCPListenerClient了,TCPListenerClient其实和TCPClient差不多,也是要继承ISocket和IDisposable。
既然重复代码做么多,要不要合并起来呢?答案是肯定的。
做一个SocketBase类,继承ISocket和IDisposable。
大部分代码直接从TCPClient复制过来。
View Code
public class SocketBase : ISocket, IDisposable { protected Socket Socket { get; private set; } protected Stream Stream { get; set; } /// <summary> /// 实例化TCP客户端。 /// </summary> public SocketBase(Socket socket, ISocketHandler socketHandler) { if (socket == null) throw new ArgumentNullException("socket"); if (socketHandler == null) throw new ArgumentNullException("socketHandler"); Socket = socket; Handler = socketHandler; } /// <summary> /// Socket处理程序 /// </summary> public ISocketHandler Handler { get; set; } /// <summary> /// 获取是否已连接。 /// </summary> public bool IsConnected { get { return Socket.Connected; } } #region 断开连接 /// <summary> /// 断开与服务器的连接。 /// </summary> public void Disconnect() { //判断是否已连接 if (!IsConnected) throw new SocketException(10057); lock (this) { //Socket异步断开并等待完成 Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne(); } } /// <summary> /// 异步断开与服务器的连接。 /// </summary> public void DisconnectAsync() { //判断是否已连接 if (!IsConnected) throw new SocketException(10057); 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)); } #endregion #region 发送数据 /// <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 }); } } #endregion #region 接收数据 protected 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 }); } #endregion #region 事件 ///// <summary> ///// 断开完成时引发事件。 ///// </summary> public event EventHandler<SocketEventArgs> DisconnectCompleted; ///// <summary> ///// 接收完成时引发事件。 ///// </summary> public event EventHandler<SocketEventArgs> ReceiveCompleted; ///// <summary> ///// 发送完成时引发事件。 ///// </summary> public event EventHandler<SocketEventArgs> SendCompleted; #endregion /// <summary> /// 释放资源 /// </summary> public void Dispose() { lock (this) { if (IsConnected) Socket.Disconnect(false); Socket.Close(); } } }
然后我们再写TCPListenerClient,继承SocketBase。
public class TCPListenerClient : SocketBase { internal TCPListenerClient(TCPListener listener, Socket socket) :base(socket,listener.Handler) { this["RemoteEndPoint"] = socket.RemoteEndPoint; //创建Socket网络流 Stream = new NetworkStream(socket); //设置服务器 Listener = listener; data = new Dictionary<string, object>(); //开始异步接收数据 SocketAsyncState state = new SocketAsyncState(); Handler.BeginReceive(Stream, EndReceive, state); } public TCPListener Listener { get; private set; } }
我们还可以给TCPListenerClient加上点东西,比如类似Session的东西。
private Dictionary<string, object> data; public object this[string key] { get { key = key.ToLower(); if (data.ContainsKey(key)) return data[key]; return null; } set { key = key.ToLower(); if (value == null) { if (data.ContainsKey(key)) data.Remove(key); return; } if (data.ContainsKey(key)) data[key] = value; else data.Add(key, value); } }
为构造函数添加以下代码。
data = new Dictionary<string, object>(); //保存IP地址到字典 this["RemoteEndPoint"] = socket.RemoteEndPoint;
这样,我们的TCPListenerClient就完成了。
接下来我们再把TCPClient修改以下,继承SocketBase。
View Code
/// <summary> /// TCP客户端 /// </summary> public class TCPClient : SocketBase { /// <summary> /// 实例化TCP客户端。 /// </summary> public TCPClient() : base(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), new SocketHandler()) { } public bool IsUseAuthenticate { get; set; } /// <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); if (IsUseAuthenticate) { NegotiateStream negotiate = new NegotiateStream(Stream); negotiate.AuthenticateAsClient(); while (!negotiate.IsMutuallyAuthenticated) { Thread.Sleep(10); } } //连接完成 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; }
所有工作,全部完成。
这个Socket还有很多功能可以增加、改造。
比如你自己写一个Handler内置加密解密,或者压缩与解压缩。
还可以再改写一下Stream,可以弄成NegotiateStream验证等等。
服务器开启后,开始异步监听客户端连接。客户端连接时,实例化TCPListenerClient,并开始异步监听数据。接收到数据时,判断数据长度,0则为断开连接,不为0则引发接收数据完成事件。
可以通过TCPListenerClient发送数据或断开连接。
服务器关闭时要先断开所有客户端连接。

客户端连接服务器,开始异步接收服务器数据。接收到数据时,判断数据长度,0则为断开连接,不为0则引发接收数据完成事件。
客户端连接服务器后,可以发送数据与断开连接。

SocketHandler是专门处理接收发送的对象。
发送数据时,要判断发送队列是否有数据正在发送或等待发送。如果有数据,则把要发送的数据加入发送队列。
发送数据过程,先发送要发送数据的长度=>ushor类型=>byte[]类型。然后再发送主数据。
发送完成时,判断发送队列是否还有数据,有则继续发送。返回是否发送成功。遇到异常则不成功。
接收数据时,先接收byte[2]的主要数据长度头信息,转换为ushort类型。
然后接收这个长度的数据,EndRead里要判断是否接收完全,不完全则继续接收。
接收完成后直接返回接收到的数据。

下面发一个简易测试延迟程序的示例代码。
客户端。
View Code
namespace PingTesterClient { class Program { static TCPClient client; static void Main(string[] args) { client = new TCPClient(); client.ReceiveCompleted += Receive; Console.WriteLine("请输入IP地址:"); client.Connect(new System.Net.IPEndPoint(IPAddress.Parse(Console.ReadLine()), 5000)); byte[] data = BitConverter.GetBytes(DateTime.Now.TimeOfDay.TotalMilliseconds); client.SendAsync(data); Console.ReadLine(); } private static void Receive(object sender, SocketEventArgs e) { Console.WriteLine(DateTime.Now.TimeOfDay.TotalMilliseconds - BitConverter.ToDouble(e.Data, 0)); System.Threading.Thread.Sleep(100); byte[] data = BitConverter.GetBytes(DateTime.Now.TimeOfDay.TotalMilliseconds); client.SendAsync(data); } } }
服务器。
View Code
namespace PingTesterServer { class Program { static void Main(string[] args) { var listener = new TCPListener(); listener.Port = 5000; listener.ReceiveCompleted += listener_ReceiveCompleted; listener.Start(); Console.ReadLine(); } static void listener_ReceiveCompleted(object sender, SocketEventArgs e) { e.Socket.SendAsync(e.Data); } } }
客户端发送当前时间转byte[]数据
服务器接收到后又发送客户端发送的数据。
客户端收到数据后,用当前时间减去收到的时间,除以2,得到网络延迟。


浙公网安备 33010602011771号