===关于异步Socket的问题???==========

lz_0618 2005-12-27 06:54:18
按CSDN上的一篇文章,创建了一个异步Socket的客户端,但在发送数据的时候出现问题:
1,当我用Client建立连接后,服务端(用Delphi编写)关闭监听
2,客户端继续发送字符串,第一次发送不出错(当然服务器端是不可能接收到东西的)
3,第二次发送出错

我的问题是:
如何在客户端得知服务端(网络)已经关闭??
如何使得在第一次发送的时候就出错(当然,第一个问题如果解决的话,这似乎不成问题了)??
如何优雅地关闭客户端的Socket(在Socket类的宿主程序中,放在析构函数中出现问题,说某个变量已经析构)??

...全文
566 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
lz_0618 2006-01-02
  • 打赏
  • 举报
回复
关于"3) 这些就是线程安全的问题了",我原来以为C#已经处理好了,因为毕竟是在同一个类里,看来不是那么一回事情!但为什么不要lock _session,能说一下道理吗??

关于2),我一直说的是客户端如何优雅地关闭,我的服务器端是用delphi做的,服务器端做的有点问题,当客户端异常关闭时,引起服务器端出错,服务器端没有加判断,只是简单地关闭了服务器端的监听,所以假设我不想改服务器端,如何优雅地关闭客户端,以致于不引起服务器端的错误??(当然在实际开发中,这样的服务器端应该改的)
netmicro 2006-01-02
  • 打赏
  • 举报
回复
3) 如果你还记得MSDN上经常说的一句话——“线程安全:此类型的所有公共静态(Visual Basic 中为 Shared)成员是线程安全的。但不保证任何实例成员是线程安全的。”你就大概知道了吧?虽然是同一个类,但是只要没有线程同步代码,就有可能冲突。我说不要lock _session,因为 _session 可能已经被其他线程置空,导致 lock (null)。

2)“异常关闭”发生时,已经太迟了。要优雅关闭,只能在客户端“正常关闭”连接之前写一次 socket.Shutdown(SocketShutdown.Both)。客户端和服务器都应该为网络上极有可能出现的不可预料的错误做准备。
netmicro 2006-01-01
  • 打赏
  • 举报
回复
1) 肯定会有影响,缓冲区本身的作用就是提升性能,程序可以将数据提交到缓冲区,然后由底层服务负责发送;但是没有缓冲区的话,程序被迫在数据发送成功之前保持阻塞。但是如果你用的是异步操作,那这个影响就没什么大碍了

2) 客户端强制关闭之后也无所谓优雅不优雅了。所谓优雅关闭,就是完成所有缓冲发送的内容的发送,然后发送“断开”信号,然后在收到对方“断开确认”信号之后断开连接。既然客户端已经强制关闭了,这些动作都是废物

3) 这些就是线程安全的问题了,很复杂,即使判断了 if (_session != null) 之后,_session 也可能被另一个线程置空,导致后面的代码出错 (NullReferenceException)。研究一下 lock 语句吧。注意:不要 lock _session,找另一个东西 lock,例如一个 private object Handle = new object();。
netmicro 2005-12-31
  • 打赏
  • 举报
回复
[我的情况是:MyClass->ClientSocket->Socket]

你不明白.NET的GC是怎么样运行的。

如果ClientSocket和Socket没有任何其他引用,或者所有引用都已变成垃圾,那么只要MyClass变成垃圾,这三个东西都是垃圾,而GC可以用任意顺序回收他们。

至于你的其他要求,其实也很好办,就是保持BeginReceive运行

只要对方断开了,无论是优雅关闭还是强制关闭,BeginReceive最终会返回,而在你调用EndReceive的时候会抛异常。当然如果对方强制关闭,或者网络中断的话,会需要一点时间来发觉。但这点时间(大约30秒)是不可避免的。

Send和零发送缓冲也有用——在TCP的世界里,发送任何数据包都必须收到确认。如果设置零发送缓冲,那么程序就被迫在Send或者EndSend在收到对方确认之前不能返回(也就是说,如果默认发送缓冲[8192字节]的话,对方即使断掉了,你还可以“成功将数据发送到缓冲区”,EndSend能正常返回,让你以为连接还在),于是对方的不正常断线将在大约30秒后导致Send或EndSend抛出异常。
zhouabc 2005-12-31
  • 打赏
  • 举报
回复

up
lz_0618 2005-12-31
  • 打赏
  • 举报
回复
1,首先请问
SendBufferSize设为0 后是否会影响性能.

2,但在客户端强制关闭时,实际上EndReceive并不会退出,也就是说一直阻塞着呢,也不会抛出异常,但在服务器端(非正常)退出时或调用Socket的Close时,EndReceive能完成但返回0,所以根本无法"优雅关闭"


3,另外在BeginReceive的回调函数中存取了_session变量,ClientSocket的Close函数中也存取了_session变量,按MSDN的解释,这两次调用不是在一个线程中引起 if (_session != null)的判断的表现比较怪异!
lz_0618 2005-12-30
  • 打赏
  • 举报
回复
至少在我的程序中是这样
lz_0618 2005-12-30
  • 打赏
  • 举报
回复
HappyGame(),
朋友,感谢你的回答,但你可以试一下,当强制关闭客户端时,"户端是能感应到的"根本不可能!
HappyGame 2005-12-30
  • 打赏
  • 举报
回复
老大,这个问题太简单了,想想吗!

服务器端断开的时候,要给客户端发送断开请求,如果在最初建立连接的时候,你的客户端就处于监听状态(发送、监听),这时候,你的客户端是能感应到的,将收到长度为0的字节,这就代表服务器端关闭了连接
lz_0618 2005-12-30
  • 打赏
  • 举报
回复
是啊,用IDisposable 接口必须主动调用Dispose()接口,我是想在析构函数中提供一个保险,万一使用我的类的人没有调用我的Dispose(),也可正确关闭Socket.

关于你举的 A B C D的例子我没有看明白:
既然D指向B,在析构函数,B就不应该失去引用??

我的情况是:MyClass->ClientSocket->Socket
在我的Myclass的析构函数中,发现Socket的handle已经释放?按理说,ClientSocket作为MyClass的一个成员对象,在Myclass的析构函数中,应该还没有失去引用,Socket作为ClientSocket的成员对象也应该没有失去引用,但实际情况好象不是!!
netmicro 2005-12-29
  • 打赏
  • 举报
回复
[我说垃圾回收有问题是指,C#应该在垃圾回收前,提供一个机制,能让我做点什么]

如前,我的建议是不要等GC,主动释放资源,例如实现 IDisposable 接口,在 Dispose() 方法里面释放所有应该释放的东西,并且当对象不再需要时主动调用 Dispose()
netmicro 2005-12-29
  • 打赏
  • 举报
回复
你想得太完美了

众所周知,一个对象可能含有对其他对象的引用,例如总共有4个对象 A B C D,其中 D 引用了对象 B

经过一段时间之后,B C D 都失去了引用,不再需要,GC出来收垃圾

当然它可以用任意它喜欢的顺序来收垃圾,例如 B->C->D

这样的话,当D被回收的时候,如果它以为B还在它掌握之内,那就大错特错了

析构函数里可以用的“对象”,是GC没办法管的对象,也就是“非托管资源”,但是我编了差不多1年C#了,都没碰到这种资源——也就是说我从来没用过析构函数
lz_0618 2005-12-29
  • 打赏
  • 举报
回复
在C#的析构函数中,竟然发现Socket类成员handle已经释放,这就怪了,按我的想法,应该在析构后才释放成员对象啊!!!!!
lz_0618 2005-12-29
  • 打赏
  • 举报
回复
我说垃圾回收有问题是指,C#应该在垃圾回收前,提供一个机制,能让我做点什么
netmicro 2005-12-28
  • 打赏
  • 举报
回复
嗯,我上面那行解决的是你的第二个问题

第一个问题,只能通过第二个问题来解决,这是MSDN自己说的,要得知当前的远端状态,只能通过尝试发送数据测试。

第三个问题:析构函数调用的时候你的程序都不知道运行到什么地方了,建议主动关闭socket(socket.Close()),如果你用的是TCP,关闭之前调用一次 Shutdown(SocketShutdown.Both),但是如果对方已经断掉,你Shut不Shutdown都一样了,Close()释放资源便可。
netmicro 2005-12-28
  • 打赏
  • 举报
回复
一行搞定你的问题

socket.SendBufferSize = 0;
netmicro 2005-12-28
  • 打赏
  • 举报
回复
不能怪垃圾回收,因为垃圾回收的运行机制是非常不可预知的

析构函数里不可以对托管资源进行任何处理,因为你使用的托管资源可能已经先被回收——即你碰到的“某个变量已经析构”之类的情况

另外,SendBufferSize = 0 有用不?这是我最近研究出来的,如果用的是TCP,那么设定发送缓冲为0长度之后,从Send()或者EndSend()返回就意味着信息已送达远端——不能送达的话就直接抛异常

如果你用的是 .NET 1.1,没有直接的属性可用,那么只能绕路:

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 0);

如果你用的是 .NET 2.0 并且用 BeginSend - EndSend,最好加一句

socket.UseOnlyOverlappedIO = true;

否则在某些防火墙的环境下会抛出无法捕捉的 AccessViolationException
lz_0618 2005-12-28
  • 打赏
  • 举报
回复
感谢 netmicro(麦),看来垃圾收集还是有不少问题!!!!!!!!

问题1已解决!!!!
我的代码来自:
C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)
在RecvData函数中增加了一句"_session.Close(); //Add by lz_0618"

修改后的代码如下:

/// <summary>
/// 数据接收处理函数
/// </summary>
/// <param name="iar">异步Socket</param>
protected virtual void RecvData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;

try
{
int recv = remote.EndReceive(iar);

//正常的退出
if (recv == 0)
{
_session.Close(); //Add by lz_0618
_session.TypeOfExit = Session.ExitType.NormalExit;

if (DisConnectedServer != null)
{
DisConnectedServer(this, new NetEventArgs(_session));
}

return;
}

string receivedData = _coder.GetEncodingString(_recvDataBuffer, recv);

....
}
lz_0618 2005-12-27
  • 打赏
  • 举报
回复
/// <summary>
/// 发送数据报文
/// </summary>
/// <param name="datagram"></param>
public virtual void Send(string datagram)
{
if (datagram.Length == 0)
{
return;
}

if ((!_isConnected) && (!_session.ClientSocket.Connected))
{
throw (new ApplicationException("没有连接服务器,不能发送数据"));
}

//获得报文的编码字节
byte[] data = _coder.GetEncodingBytes(datagram);

_session.ClientSocket.BeginSend(data, 0, data.Length, SocketFlags.None,
new AsyncCallback(SendDataEnd), _session.ClientSocket);
}

/// <summary>
/// 数据发送完成处理函数
/// 按MSDN的解释,该函数和BeginSend不在同一个线程中执行
/// 在EndSend时阻塞,直到发送了指定的字节或出错,所以这里应该需要错误处理
/// </summary>
/// <param name="iar"></param>
protected virtual void SendDataEnd(IAsyncResult iar)
{
try
{
Socket remote = (Socket)iar.AsyncState;
int sent = remote.EndSend(iar);
Debug.Assert(sent != 0);

if (SendDataComplete != null)
{
SendDataComplete(this, new NetEventArgs(_session));
}
}
catch (Exception e)
{
Debug.Print("SendDataEnd错误!");
}
}


110,534

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

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