让人头疼的Socket断开Disconnect方法,前面是基于SocketAsyncEventArgs的操作。

Arnuonly 2012-02-29 03:42:05
我做了一个以TCP客户端方式连接到服务的异步类,期望它能够高效的运行,可是连接、发送、接收都很顺利,在断开的时候却是愁坏了我。
当我使用Disconnect或者DisconnectAsync方法的时候,为了使对象能够重用,设定了重用属性为true。但是当我想要断开的时候,却半天都没有断开。
代码送上,大家看看为什么。
    public interface ITCPAsyncClient
{
void Connect();
void Disconnect();
void Send(byte[] sendBytes);
void SetBuffer(byte[] buffer, int offset, int buffersize);
event ConnectionEventHandler Received;
event ConnectionEventHandler Connected;
event ConnectionEventHandler Disconnected;
event ConnectionEventHandler OutPutString;
}

public delegate void ConnectionEventHandler(object sender, EventArgs e);

/// <summary>
/// 这个类的目标是,生成一个socket,一个readEventArg,一个writeEventArg,并为他们分配缓冲区。
/// 以TCP客户端的方式对外进行连接,响应发送接收等操作。连接断开等操作。全部使用异步操作。
/// </summary>
public class TCPAsyncClient : ITCPAsyncClient
{
Socket m_socket;
SocketAsyncEventArgs m_readEventArg;
SocketAsyncEventArgs m_writeEventArg;
object m_userToken;
int m_key;
int m_buffersize;

bool m_bIsConnect = false;
/// <summary>
/// 构造函数,初始化各个变量,一个读取参数,一个写入参数,共用一个UserToken,
/// </summary>
public TCPAsyncClient()
{
// 初始化过程,初始化Socket,EventArg,UserToken
m_socket = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
m_userToken = new object();
m_readEventArg = new SocketAsyncEventArgs();
m_readEventArg.UserToken = m_userToken;
m_readEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
m_writeEventArg = new SocketAsyncEventArgs();
m_writeEventArg.UserToken = m_userToken;
m_writeEventArg.Completed +=new EventHandler<SocketAsyncEventArgs>(IO_Completed);
m_buffersize = -1;
}

/// <summary>
/// 设置远程地址,分配的Key
/// </summary>
/// <param name="serverEndPoint">远程地址</param>
/// <param name="key">分配的Key</param>
public TCPAsyncClient(IPEndPoint serverEndPoint, int key)
: this()
{
m_readEventArg.RemoteEndPoint = serverEndPoint;
m_writeEventArg.RemoteEndPoint = serverEndPoint;
this.m_key = key;
}

void IO_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
break;
case SocketAsyncOperation.Disconnect:
ProcessDisconnect(e);
break;
default:
OnOutPutString(new ConnectionEventArgs("some thing wrong!", null, null));
break;
}
}

/// <summary>
/// 连接到设定的服务端,需要设定buffer和远程地址,否则抛出异常
/// </summary>
public void Connect()
{
if (m_bIsConnect)
{
throw new Exception("Has Connected");
}
m_bIsConnect = true;

if (m_readEventArg.RemoteEndPoint == null)
{
throw new Exception("尚未设置远程IP地址");
}
if (m_readEventArg.Buffer == null || m_writeEventArg.Buffer == null)
{
throw new Exception("尚未设置缓冲区");
}
// 开始连接,到目标服务器
//m_socket = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
bool willRaiseEvent = m_socket.ConnectAsync(m_readEventArg);
if (!willRaiseEvent)
{
ProcessConnect(m_readEventArg);
}
}
catch(Exception ex)
{
OnOutPutString(new ConnectionEventArgs("连接失败", null, null));
OnOutPutString(new ConnectionEventArgs(ex.Message, null, null));
}
}

/// <summary>
/// 处理完成事件,对外触发连接事件,自身启动异步接收。如果连接失败,则触发断开过程
/// </summary>
/// <param name="EventArg">连接完成参数,对应ConnectAsync的参数</param>
private void ProcessConnect(SocketAsyncEventArgs EventArg)
{
// 启动异步接收。
// 发出连接事件。
if (EventArg.SocketError == SocketError.Success)
{
OnConnect(EventArg);
bool willRaiseEvent = m_socket.ReceiveAsync(m_readEventArg);
if (!willRaiseEvent)
{
ProcessReceive(m_readEventArg);
}
}
else
{
Disconnect(1);
m_bIsConnect = false;
}
}
/// <summary>
/// 处理完成接收事件
/// </summary>
/// <param name="e">接收参数,来自ReceiveAsync</param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
// check if the remote host closed the connection
//// 检查远程主机是否关闭连接
//if (!m_bIsConnect)
// return;

try
{
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
OnReceive(e);

OnOutPutString(new ConnectionEventArgs("再次启动对该客户端的数据接收", null, null));
bool willRaiseEvent = m_socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
OnOutPutString(new ConnectionEventArgs("服务端断开连接", null, null));
Disconnect(1);
}
}
catch (Exception ex)
{
OnOutPutString(new ConnectionEventArgs("接收异常:" + ex.Message, null, null));
Disconnect(1);
}
}

/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
if (!m_bIsConnect)
throw new Exception("Don't Connected");
Disconnect(1);
}

private void Disconnect(int key)
{
if (!m_bIsConnect)
return;
// 处理断开,先将socket关闭ShutDown,然后再断开,Disconnect,使用参数true,以保证可以重用
m_bIsConnect = false;
try
{
// 问题出现在这里,无论是用下面没有注释掉的代码还是使用上面被注释的代码,都不能迅速的断开连接。
//m_socket.Shutdown(SocketShutdown.Both);
//m_writeEventArg.DisconnectReuseSocket = true;
//bool willRaiseEvent = m_socket.DisconnectAsync(m_writeEventArg);
//if (!willRaiseEvent)
//{
// ProcessDisconnect(m_writeEventArg);
//}

// 以上是异步方式断开,以下是阻塞方式断开
m_socket.Shutdown(SocketShutdown.Both);
m_socket.Disconnect(true);
string ip = "";
if (m_readEventArg.RemoteEndPoint != null)
{
ip = ((IPEndPoint)m_readEventArg.RemoteEndPoint).Address.ToString() + ":"
+ ((IPEndPoint)m_readEventArg.RemoteEndPoint).Port.ToString();
}
OnDisconnect(new ConnectionEventArgs(ip, this.m_key.ToString(), null));
}
catch (Exception ex)
{
OnOutPutString(new ConnectionEventArgs(ex.Message, null, null));
}
}

private void ProcessDisconnect(SocketAsyncEventArgs e)
{
string ip = "";
if (e.RemoteEndPoint != null)
{
ip = ((IPEndPoint)e.RemoteEndPoint).Address.ToString() + ":"
+ ((IPEndPoint)e.RemoteEndPoint).Port.ToString();
}
OnDisconnect(new ConnectionEventArgs(ip, this.m_key.ToString(), null));
}

...全文
3468 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
CGabriel 2013-05-13
  • 打赏
  • 举报
回复
重用没有什么必要,百分之九十九点九的开销都用掉了。。不在乎那一点。。
HQTGS 2013-05-10
  • 打赏
  • 举报
回复
在用使用SocketAsyncEventArgs ,关注中!
cjl_852006 2013-03-14
  • 打赏
  • 举报
回复
setbuffer的函数实现呢?没有吗?
Arnuonly 2012-02-29
  • 打赏
  • 举报
回复
附上命令行模式的测试程序代码:

class Program
{
static void Main(string[] args)
{
new MainClass().Run();
}
}

class MainClass
{
ITCPAsyncClient asyncClient;
public MainClass()
{
IPEndPoint ip = new IPEndPoint(IPAddress.Parse("164.70.6.63"),9002);
asyncClient = new TCPAsyncClient(ip, 1);
asyncClient.Connected += new ConnectionEventHandler(DoEventThings);
asyncClient.Received += new ConnectionEventHandler(DoEventThings);
asyncClient.Disconnected +=new ConnectionEventHandler(DoEventThings);
asyncClient.OutPutString += new ConnectionEventHandler(DoEventThings);
byte[] buffer = new byte[1024];
asyncClient.SetBuffer(buffer, 0, 1024);
}

void DoEventThings(object sender, EventArgs e)
{
//throw new NotImplementedException();
string show = "";
ConnectionEventArgs cnEArg = e as ConnectionEventArgs;
if (cnEArg.Text != null)
show += cnEArg.Text + " ";
if (cnEArg.Key != null)
show += cnEArg.Key + ":";
if(cnEArg.Bytes!= null)
for (int i = 0; i < cnEArg.Bytes.Length; i++)
{
show += cnEArg.Bytes[i].ToString("X2") + " ";
}
Console.WriteLine(show);
}

public void Run()
{
asyncClient.Connect();

string input = "";
while (true)
{
Console.WriteLine("输入要发送的数据或者输入d回车断开连接");
input = Console.ReadLine();
if (input.ToLower() == "d")
{
asyncClient.Disconnect();
Console.WriteLine("看到断开提示后,输入c重新连接或者q退出程序");
input = Console.ReadLine();
if (input.ToLower() == "c")
{
asyncClient.Connect();
continue;
}
else
{
break;
}
}
else
{
if (input.Length == 0)
continue;
asyncClient.Send(Encoding.Default.GetBytes(input));
}
}
}
}
Arnuonly 2012-02-29
  • 打赏
  • 举报
回复
上面的Send函数中,将

m_writeEventArg.SendPacketsSendSize = sendBytes.Length;

这句改成下面这句。

m_writeEventArg.SetBuffer(m_writeEventArg.Offset, sendBytes.Length);
Arnuonly 2012-02-29
  • 打赏
  • 举报
回复
代码还没完,继续贴代码。

/// <summary>
/// 发送数据,将来源数据发送到已连接的服务端
/// </summary>
/// <param name="sendBytes">要发送的数据</param>
public void Send(byte[] sendBytes)
{
if (!m_bIsConnect)
throw new Exception("Don't Connected");
if (!m_socket.Connected)
throw new Exception("Don't Connected");
if(m_writeEventArg.Buffer == null)
throw new ArgumentNullException("buffer","缓冲区尚未设置");
if (sendBytes.Length > m_writeEventArg.Count)
throw new ArgumentOutOfRangeException("sendBytes", "发送字节数超出缓冲区");
for (int i = 0; i < sendBytes.Length; i++)
{
m_writeEventArg.Buffer[i + m_writeEventArg.Offset] = sendBytes[i];
}
m_writeEventArg.SendPacketsSendSize = sendBytes.Length;
bool willRaiseEvent = m_socket.SendAsync(m_writeEventArg);
if (!willRaiseEvent)
{
ProcessSend(m_writeEventArg);
}
}

private void ProcessSend(SocketAsyncEventArgs m_writeEventArg)
{
// throw new NotImplementedException();
// 如果发送失败,报到断开连接
if (m_writeEventArg.SocketError != SocketError.Success)
{
Disconnect();
}
}

public event ConnectionEventHandler Received;
public event ConnectionEventHandler Connected;
public event ConnectionEventHandler Disconnected;
public event ConnectionEventHandler OutPutString;

/// <summary>
/// 触发接收事件,实际上参数是SocketAsyncEventArgs,包含了所有的数据和节点信息
/// </summary>
/// <param name="e"></param>
protected void OnReceive(EventArgs e)
{
if (Received != null)
{
SocketAsyncEventArgs saea = e as SocketAsyncEventArgs;

string text = "";
if (saea.RemoteEndPoint != null)
{
text = ((IPEndPoint)saea.RemoteEndPoint).Address.ToString() + ":"
+ ((IPEndPoint)saea.RemoteEndPoint).Port.ToString();
}
byte[] recvBytes = new byte[saea.BytesTransferred];
for (int i = 0; i < recvBytes.Length; i++)
{
recvBytes[i] = saea.Buffer[i + saea.Offset];
}
Received(this, new ConnectionEventArgs(text, this.m_key.ToString(), recvBytes));
}
}

/// <summary>
/// 触发连接完成事件,该事件包含连接的远程地址和为本地分配的Key
/// </summary>
/// <param name="e">来自ProcessConnect的参数</param>
protected void OnConnect(EventArgs e)
{
if (Connected != null)
{
SocketAsyncEventArgs saEArg = e as SocketAsyncEventArgs;
string ip = "";
if (saEArg.RemoteEndPoint != null)
{
ip = ((IPEndPoint)saEArg.RemoteEndPoint).Address.ToString() + ":"
+ ((IPEndPoint)saEArg.RemoteEndPoint).Port.ToString();
}

Connected(this, new ConnectionEventArgs(ip, this.m_key.ToString(), null));
}
}

protected void OnDisconnect(EventArgs e)
{
if (Disconnected != null)
Disconnected(this, e);
}

protected void OnOutPutString(EventArgs e)
{
if (OutPutString != null)
OutPutString(this, e);
}
}

public class ConnectionEventArgs : EventArgs
{
// 输出字符串
public string Text { get; private set; }
// 事件来源者的Key
public string Key { get; private set; }
// 事件需要输出的字节
public byte[] Bytes { get; private set; }

public ConnectionEventArgs(string text, string key, byte[] bytes)
{
Text = text;
Key = key;
Bytes = bytes;
}
}

17,741

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 .NET Framework
社区管理员
  • .NET Framework社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧