欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程语言 > C# >内容正文

C#

[C#]手把手教你打造Socket的TCP通讯连接(一)

发布时间:2025/1/21 C# 119 豆豆
生活随笔 收集整理的这篇文章主要介绍了 [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);//发送的数据不能为nullif (data == null)throw new ArgumentNullException("data");//发送的数据长度不能为0if (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);//发送的数据不能为nullif (data == null)throw new ArgumentNullException("data");//发送的数据长度不能为0if (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

转载于:https://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html

总结

以上是生活随笔为你收集整理的[C#]手把手教你打造Socket的TCP通讯连接(一)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。