最近在Socket编程的时候发现只能设置Send和Recieve的Timeout时间,而Connect方法的Timeout是固定的,大概有20-40s,如果用同步方法界面会卡死很长时间
下面搜集了几种通过异步的方式解决此问题的方法:
1.使用BeginConnect方法
IAsyncResult connResult = mySocket.BeginConnect(yourAddress, yourPort, null, null);
//WaitHandle.WaitOne 方法阻止当前线程,直到当前的 WaitHandle 收到信号
connResult.AsyncWaitHandle.WaitOne(2000, true); //等待2秒
if (!connResult.IsCompleted)
{
mySocket.Close();
//处理连接不成功的动作
}
else
{
//处理连接成功的动作
}
这种方法很好的控制了连接超时时间而且代码非常简单,但是界面仍然会有2秒的卡死产生。如果想解决该问题,则需要创建一个额外的线程来执行WaitOne方法。
2.使用ConnectAsync方法
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.Completed += new EventHandler<SocketAsyncEventArgs>(AsyncConnected);
e.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(yourAddress), yourPort);
mySocket.ConnectAsync(e);
这是一种事件触发的方式,将连接成功后的动作放在Completed事件的处理过程中,从而使界面不会卡死。同时也可以创建一个额外线程来监视连接时间,从而达到控制连接超时的目的。
3.
class TimeOutSocket
{
private static bool IsConnectionSuccessful = false;
private static Exception socketexception;
private static ManualResetEvent TimeoutObject = new ManualResetEvent(false);
public static TcpClient Connect(IPEndPoint remoteEndPoint, int timeoutMSec)
{
TimeoutObject.Reset();
socketexception = null;
string serverip = Convert.ToString(remoteEndPoint.Address);
int serverport = remoteEndPoint.Port;
TcpClient tcpclient = new TcpClient();
tcpclient.BeginConnect(serverip, serverport,
new AsyncCallback(CallBackMethod), tcpclient);
if (TimeoutObject.WaitOne(timeoutMSec, false))
{
if (IsConnectionSuccessful)
{
return tcpclient;
}
else
{
throw socketexception;
}
}
else
{
tcpclient.Close();
throw new TimeoutException("TimeOut Exception");
}
}
private static void CallBackMethod(IAsyncResult asyncresult)
{
try
{
IsConnectionSuccessful = false;
TcpClient tcpclient = asyncresult.AsyncState as TcpClient;
if (tcpclient.Client != null)
{
tcpclient.EndConnect(asyncresult);
IsConnectionSuccessful = true;
}
}
catch (Exception ex)
{
IsConnectionSuccessful = false;
socketexception = ex;
}
finally
{
TimeoutObject.Set();
}
}
}
这里,ManualResetEvent的WaitOne(TimeSpan, Boolean)起到了主要的作用。它将阻止当前线程,直到ManualResetEvent对象被Set或者超过timeout时间。上面的代码中,调用BeginConnect后通过WaitOne方法阻止当前线程,如果在timeoutMSec时间内连接成功,将在CallBackMethod回调中调用TimeoutObject.Set,解除被阻塞的连接线程并返回;否则,连接线程会在等待超时后,主动关闭连接并抛出TimeoutException。
4.
private string ConnectSocket(IPEndPoint ipep, Socket s)
{
string m = "";
try
{
s.Connect(ipep);
}
catch (System.Exception ex)
{
m = ex.Message;
}
return m;
}
/// <summary>
/// 创建TCP连接
/// </summary>
/// <returns>连接状态</returns>
public bool Modbus_TCP_Open()
{
int Timeout = 500;
//创建一个套接字
//将服务器IP地址存放在字符串
//将端口号强制为32位整型,存放在port中
IPEndPoint ie = new IPEndPoint(IPAddress.Parse(IP_Address), Convert.ToInt32(PC_Port));
newclient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
CSD d = ConnectSocket;
IAsyncResult iaresult = d.BeginInvoke(ie, newclient, null, null);
bool connectSuccess = iaresult.AsyncWaitHandle.WaitOne(Timeout, false);
if (!connectSuccess)
{
MessageBox.Show(string.Format("失败!错误信息:{0}", "连接超时"));
return false;
}
//EndInvoke方法一直阻塞,直到被调用的方法执行完毕。会导致用户界面假死
string m = d.EndInvoke(iaresult);
if (!string.IsNullOrEmpty(m))
{
MessageBox.Show(string.Format("失败!错误信息:{0}", m));
return false;
}
return true;
}