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 ...@@ -6,46 +6,63 @@ namespace Titanium.Web.Proxy.Extensions
{ {
internal static class TcpExtensions internal static class TcpExtensions
{ {
private static readonly Func<Socket, bool> socketCleanedUpGetter; internal static void CloseSocket(this TcpClient tcpClient)
static TcpExtensions()
{ {
var property = typeof(Socket).GetProperty("CleanedUp", BindingFlags.NonPublic | BindingFlags.Instance); if (tcpClient == null)
if (property != null) {
return;
}
try
{ {
var method = property.GetMethod; tcpClient.Close();
if (method != null && method.ReturnType == typeof(bool)) }
{ catch
socketCleanedUpGetter = {
(Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method); // 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 try
{ {
// This line is important! var tmp = new byte[1];
// 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);
}
tcpClient.Close(); socket.Blocking = false;
socket.Send(tmp, 0, 0);
//Connected.
} }
catch 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; ...@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
#endif #endif
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
...@@ -43,8 +44,16 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -43,8 +44,16 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
proxyServer.UpdateClientConnectionCount(false); Task.Run(async () =>
tcpClient.CloseSocket(); {
//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 ...@@ -191,7 +191,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3); var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3);
if (recentConnection.LastAccess > cutOff if (recentConnection.LastAccess > cutOff
&& isGoodConnection(recentConnection.TcpClient)) && recentConnection.TcpClient.IsGoodConnection())
{ {
return recentConnection; return recentConnection;
} }
...@@ -273,7 +273,14 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -273,7 +273,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendBufferSize = proxyServer.BufferSize, SendBufferSize = proxyServer.BufferSize,
ReceiveBufferSize = 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 this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useUpstreamProxy) if (useUpstreamProxy)
...@@ -501,47 +508,6 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -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() public void Dispose()
{ {
runCleanUpTask = false; runCleanUpTask = false;
......
...@@ -4,6 +4,7 @@ using System.Net; ...@@ -4,6 +4,7 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
#endif #endif
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -85,9 +86,17 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -85,9 +86,17 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
proxyServer.UpdateServerConnectionCount(false); Task.Run(async () =>
Stream?.Dispose(); {
tcpClient.CloseSocket(); //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 ...@@ -186,6 +186,18 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public int MaxCachedConnections { get; set; } = 2; 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> /// <summary>
/// Total number of active client connections. /// Total number of active client connections.
/// </summary> /// </summary>
...@@ -619,6 +631,12 @@ namespace Titanium.Web.Proxy ...@@ -619,6 +631,12 @@ namespace Titanium.Web.Proxy
private void listen(ProxyEndPoint endPoint) private void listen(ProxyEndPoint endPoint)
{ {
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
if (ReuseSocket)
{
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
try try
{ {
endPoint.Listener.Start(); endPoint.Listener.Start();
...@@ -718,6 +736,7 @@ namespace Titanium.Web.Proxy ...@@ -718,6 +736,7 @@ namespace Titanium.Web.Proxy
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000; tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = BufferSize; tcpClient.SendBufferSize = BufferSize;
tcpClient.ReceiveBufferSize = BufferSize; tcpClient.ReceiveBufferSize = BufferSize;
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
await InvokeConnectionCreateEvent(tcpClient, true); 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