Commit ca06c7ab authored by justcoding121's avatar justcoding121

#466 add TcpTimeWait seconds and socket reuse option

parent 315592a9
......@@ -6,46 +6,63 @@ namespace Titanium.Web.Proxy.Extensions
{
internal static class TcpExtensions
{
private static readonly Func<Socket, bool> socketCleanedUpGetter;
static TcpExtensions()
internal static void CloseSocket(this TcpClient tcpClient)
{
var property = typeof(Socket).GetProperty("CleanedUp", BindingFlags.NonPublic | BindingFlags.Instance);
if (property != null)
if (tcpClient == null)
{
return;
}
try
{
var method = property.GetMethod;
if (method != null && method.ReturnType == typeof(bool))
{
socketCleanedUpGetter =
(Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method);
}
tcpClient.Close();
}
catch
{
// ignored
}
}
internal static void CloseSocket(this TcpClient tcpClient)
/// <summary>
/// Check if a TcpClient is good to be used.
/// This only checks if send is working so local socket is still connected.
/// Receive can only be verified by doing a valid read from server without exceptions.
/// So in our case we should retry with new connection from pool if first read after getting the connection fails.
/// https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected(v=vs.110).aspx
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
internal static bool IsGoodConnection(this TcpClient client)
{
if (tcpClient == null)
var socket = client.Client;
if (!client.Connected || !socket.Connected)
{
return;
return false;
}
// This is how you can determine whether a socket is still connected.
bool blockingState = socket.Blocking;
try
{
// This line is important!
// contributors please don't remove it without discussion
// It helps to avoid eventual deterioration of performance due to TCP port exhaustion
// due to default TCP CLOSE_WAIT timeout for 4 minutes
if (socketCleanedUpGetter == null || !socketCleanedUpGetter(tcpClient.Client))
{
tcpClient.LingerState = new LingerOption(true, 0);
}
var tmp = new byte[1];
tcpClient.Close();
socket.Blocking = false;
socket.Send(tmp, 0, 0);
//Connected.
}
catch
{
// ignored
//Should we let 10035 == WSAEWOULDBLOCK as valid connection?
return false;
}
finally
{
socket.Blocking = blockingState;
}
return true;
}
}
}
......@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Security;
#endif
using System.Net.Sockets;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Network.Tcp
......@@ -43,8 +44,16 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
public void Dispose()
{
proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
Task.Run(async () =>
{
//delay calling tcp connection close()
//so that client have enough time to call close first.
//This way we can push tcp Time_Wait to client side when possible.
await Task.Delay(1000);
proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
});
}
}
}
......@@ -191,7 +191,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3);
if (recentConnection.LastAccess > cutOff
&& isGoodConnection(recentConnection.TcpClient))
&& recentConnection.TcpClient.IsGoodConnection())
{
return recentConnection;
}
......@@ -273,7 +273,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendBufferSize = proxyServer.BufferSize,
ReceiveBufferSize = proxyServer.BufferSize
};
};
if(proxyServer.ReuseSocket)
{
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
tcpClient.LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds);
// If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useUpstreamProxy)
......@@ -501,47 +508,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
}
/// <summary>
/// Check if a TcpClient is good to be used.
/// This only checks if send is working so local socket is still connected.
/// Receive can only be verified by doing a valid read from server without exceptions.
/// So in our case we should retry with new connection from pool if first read after getting the connection fails.
/// https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected(v=vs.110).aspx
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
private static bool isGoodConnection(TcpClient client)
{
var socket = client.Client;
if (!client.Connected || !socket.Connected)
{
return false;
}
// This is how you can determine whether a socket is still connected.
bool blockingState = socket.Blocking;
try
{
var tmp = new byte[1];
socket.Blocking = false;
socket.Send(tmp, 0, 0);
//Connected.
}
catch
{
//Should we let 10035 == WSAEWOULDBLOCK as valid connection?
return false;
}
finally
{
socket.Blocking = blockingState;
}
return true;
}
public void Dispose()
{
runCleanUpTask = false;
......
......@@ -4,6 +4,7 @@ using System.Net;
using System.Net.Security;
#endif
using System.Net.Sockets;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
......@@ -85,9 +86,17 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
public void Dispose()
{
proxyServer.UpdateServerConnectionCount(false);
Stream?.Dispose();
tcpClient.CloseSocket();
Task.Run(async () =>
{
//delay calling tcp connection close()
//so that server have enough time to call close first.
//This way we can push tcp Time_Wait to server side when possible.
await Task.Delay(1000);
proxyServer.UpdateServerConnectionCount(false);
Stream?.Dispose();
tcpClient.CloseSocket();
});
}
}
}
......@@ -186,6 +186,18 @@ namespace Titanium.Web.Proxy
/// </summary>
public int MaxCachedConnections { get; set; } = 2;
/// <summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state.
/// Default value is 3.
/// </summary>
public int TcpTimeWaitSeconds { get; set; } = 3;
/// <summary>
/// Should we resues client/server tcp sockets.
/// Default is true.
/// </summary>
public bool ReuseSocket { get; set; } = true;
/// <summary>
/// Total number of active client connections.
/// </summary>
......@@ -619,6 +631,12 @@ namespace Titanium.Web.Proxy
private void listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
if (ReuseSocket)
{
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
try
{
endPoint.Listener.Start();
......@@ -718,6 +736,7 @@ namespace Titanium.Web.Proxy
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = BufferSize;
tcpClient.ReceiveBufferSize = BufferSize;
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
await InvokeConnectionCreateEvent(tcpClient, true);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment