Commit 5faaa0b2 authored by justcoding121's avatar justcoding121

#184 add connection count; TODO: Find out why large number of client connections are active

parent 4047a006
...@@ -106,6 +106,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -106,6 +106,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//intecept & cancel redirect or update requests //intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine("Active Connections:" + (sender as ProxyServer).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
//read request headers //read request headers
......
...@@ -55,10 +55,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -55,10 +55,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
Stream?.Close(); Stream?.Close();
Stream?.Dispose(); Stream?.Dispose();
StreamReader?.Dispose(); StreamReader?.Dispose();
TcpClient.LingerState = new LingerOption(true, 0); TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Close(); TcpClient?.Close();
} }
} }
} }
...@@ -133,10 +133,6 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -133,10 +133,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
client.ReceiveTimeout = connectionTimeOutSeconds * 1000; client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000; client.SendTimeout = connectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection return new TcpConnection
{ {
UpStreamHttpProxy = externalHttpProxy, UpStreamHttpProxy = externalHttpProxy,
......
...@@ -226,6 +226,8 @@ namespace Titanium.Web.Proxy ...@@ -226,6 +226,8 @@ namespace Titanium.Web.Proxy
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
public int ClientConnectionCount { get; private set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
...@@ -495,7 +497,7 @@ namespace Titanium.Web.Proxy ...@@ -495,7 +497,7 @@ namespace Titanium.Web.Proxy
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start(); endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint) endPoint.Listener.LocalEndpoint).Port; endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously // accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint); endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
...@@ -560,7 +562,7 @@ namespace Titanium.Web.Proxy ...@@ -560,7 +562,7 @@ namespace Titanium.Web.Proxy
/// <param name="asyn"></param> /// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn) private void OnAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint) asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null; TcpClient tcpClient = null;
...@@ -586,6 +588,8 @@ namespace Titanium.Web.Proxy ...@@ -586,6 +588,8 @@ namespace Titanium.Web.Proxy
{ {
Task.Run(async () => Task.Run(async () =>
{ {
ClientConnectionCount++;
try try
{ {
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
...@@ -599,15 +603,14 @@ namespace Titanium.Web.Proxy ...@@ -599,15 +603,14 @@ namespace Titanium.Web.Proxy
} }
finally finally
{ {
if (tcpClient != null) //This line is important!
{ //contributors please don't remove it without discussion
//This line is important! //It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//contributors please don't remove it without discussion //due to default TCP CLOSE_WAIT timeout for 4 minutes
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion tcpClient.LingerState = new LingerOption(true, 0);
//due to default TCP CLOSE_WAIT timeout for 4 minutes tcpClient?.Close();
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Close(); ClientConnectionCount--;
}
} }
}); });
} }
......
...@@ -33,9 +33,10 @@ namespace Titanium.Web.Proxy ...@@ -33,9 +33,10 @@ namespace Titanium.Web.Proxy
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine}; var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
Uri httpRemoteUri; Uri httpRemoteUri;
try try
{ {
//read the first line HTTP command //read the first line HTTP command
...@@ -119,7 +120,7 @@ namespace Titanium.Web.Proxy ...@@ -119,7 +120,7 @@ namespace Titanium.Web.Proxy
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine}; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
} }
catch catch
{ {
...@@ -156,7 +157,9 @@ namespace Titanium.Web.Proxy ...@@ -156,7 +157,9 @@ namespace Titanium.Web.Proxy
} }
catch (Exception) catch (Exception)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream,
clientStreamReader,
clientStreamWriter, null);
} }
} }
...@@ -187,21 +190,24 @@ namespace Titanium.Web.Proxy ...@@ -187,21 +190,24 @@ namespace Titanium.Web.Proxy
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine}; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
} }
catch (Exception) catch (Exception)
{ {
sslStream.Dispose(); sslStream.Dispose();
Dispose(sslStream, clientStreamReader, clientStreamWriter, null); Dispose(sslStream,
clientStreamReader,
clientStreamWriter, null);
return; return;
} }
} }
else else
{ {
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine}; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
} }
//now read the request line //now read the request line
...@@ -212,7 +218,8 @@ namespace Titanium.Web.Proxy ...@@ -212,7 +218,8 @@ namespace Titanium.Web.Proxy
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
} }
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool closeConnection) private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args,
ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool closeConnection)
{ {
try try
{ {
...@@ -241,7 +248,10 @@ namespace Titanium.Web.Proxy ...@@ -241,7 +248,10 @@ namespace Titanium.Web.Proxy
args.IsHttps, SupportedSslProtocols, args.IsHttps, SupportedSslProtocols,
ValidateServerCertificate, ValidateServerCertificate,
SelectClientCertificate, SelectClientCertificate,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint); customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
args.ProxyClient.ClientStream,
UpStreamEndPoint);
} }
args.WebSession.Request.RequestLocked = true; args.WebSession.Request.RequestLocked = true;
...@@ -249,8 +259,12 @@ namespace Titanium.Web.Proxy ...@@ -249,8 +259,12 @@ namespace Titanium.Web.Proxy
//If request was cancelled by user then dispose the client //If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); Dispose(args.ProxyClient.ClientStream,
return; args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
//if expect continue is enabled then send the headers first //if expect continue is enabled then send the headers first
...@@ -314,28 +328,50 @@ namespace Titanium.Web.Proxy ...@@ -314,28 +328,50 @@ namespace Titanium.Web.Proxy
//If not expectation failed response was returned by server then parse response //If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed) if (!args.WebSession.Request.ExpectationFailed)
{ {
await HandleHttpSessionResponse(args); var result = await HandleHttpSessionResponse(args);
//already disposed inside above method
if (result == false)
{
return false;
}
} }
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); Dispose(args.ProxyClient.ClientStream,
return; args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return; Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
if (closeConnection) if (closeConnection)
{ {
//dispose //dispose
connection?.Dispose(); Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
return true;
} }
/// <summary> /// <summary>
...@@ -356,28 +392,30 @@ namespace Titanium.Web.Proxy ...@@ -356,28 +392,30 @@ namespace Titanium.Web.Proxy
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null) ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{ {
TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection //Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client) //(assuming HTTP connection is kept alive by client)
while (true) while (true)
{ {
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
null);
break; break;
} }
var args = var args =
new SessionEventArgs(BufferSize, HandleHttpSessionResponse) new SessionEventArgs(BufferSize, HandleHttpSessionResponse)
{ {
ProxyClient = {TcpClient = client}, ProxyClient = { TcpClient = client },
WebSession = {ConnectHeaders = connectHeaders} WebSession = { ConnectHeaders = connectHeaders }
}; };
args.WebSession.ProcessId = new Lazy<int>(() => args.WebSession.ProcessId = new Lazy<int>(() =>
{ {
var remoteEndPoint = (IPEndPoint) args.ProxyClient.TcpClient.Client.RemoteEndPoint; var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id //If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address)) if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
...@@ -388,6 +426,7 @@ namespace Titanium.Web.Proxy ...@@ -388,6 +426,7 @@ namespace Titanium.Web.Proxy
//can't access process Id of remote request from remote machine //can't access process Id of remote request from remote machine
return -1; return -1;
}); });
try try
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
...@@ -426,8 +465,7 @@ namespace Titanium.Web.Proxy ...@@ -426,8 +465,7 @@ namespace Titanium.Web.Proxy
{ {
var existing = args.WebSession.Request.RequestHeaders[newHeader.Name]; var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader}; var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader };
args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders); args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
args.WebSession.Request.RequestHeaders.Remove(newHeader.Name); args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
...@@ -450,9 +488,14 @@ namespace Titanium.Web.Proxy ...@@ -450,9 +488,14 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false)) if (httpsHostName == null &&
(await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false))
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, args); Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break; break;
} }
...@@ -467,7 +510,7 @@ namespace Titanium.Web.Proxy ...@@ -467,7 +510,7 @@ namespace Titanium.Web.Proxy
for (var i = 0; i < invocationList.Length; i++) for (var i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(null, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
...@@ -482,22 +525,41 @@ namespace Titanium.Web.Proxy ...@@ -482,22 +525,41 @@ namespace Titanium.Web.Proxy
SelectClientCertificate, SelectClientCertificate,
clientStream, tcpConnectionFactory, UpStreamEndPoint); clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args); Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break; break;
} }
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(null, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false); var result = await HandleHttpSessionRequestInternal(null, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false);
if (result == false)
{
//already disposed inside above method
break;
}
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break; break;
} }
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break; break;
} }
...@@ -507,13 +569,15 @@ namespace Titanium.Web.Proxy ...@@ -507,13 +569,15 @@ namespace Titanium.Web.Proxy
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break; break;
} }
} }
//dispose
connection?.Dispose();
} }
/// <summary> /// <summary>
......
...@@ -9,6 +9,8 @@ using Titanium.Web.Proxy.Exceptions; ...@@ -9,6 +9,8 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using System.Net.Sockets;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -21,14 +23,14 @@ namespace Titanium.Web.Proxy ...@@ -21,14 +23,14 @@ namespace Titanium.Web.Proxy
/// Called asynchronously when a request was successfully and we received the response /// Called asynchronously when a request was successfully and we received the response
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns>true if no errors</returns>
private async Task HandleHttpSessionResponse(SessionEventArgs args) private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
{ {
//read response & headers from server
await args.WebSession.ReceiveResponse();
try try
{ {
//read response & headers from server
await args.WebSession.ReceiveResponse();
if (!args.WebSession.Response.ResponseBodyRead) if (!args.WebSession.Response.ResponseBodyRead)
{ {
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
...@@ -44,7 +46,7 @@ namespace Titanium.Web.Proxy ...@@ -44,7 +46,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(this, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
...@@ -53,7 +55,7 @@ namespace Titanium.Web.Proxy ...@@ -53,7 +55,7 @@ namespace Titanium.Web.Proxy
if (args.ReRequest) if (args.ReRequest)
{ {
await HandleHttpSessionRequestInternal(null, args, null, null, true); await HandleHttpSessionRequestInternal(null, args, null, null, true);
return; return true;
} }
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
...@@ -125,14 +127,16 @@ namespace Titanium.Web.Proxy ...@@ -125,14 +127,16 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args); args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
}
finally return false;
{
args.Dispose();
} }
args.Dispose();
return true;
} }
/// <summary> /// <summary>
...@@ -219,24 +223,24 @@ namespace Titanium.Web.Proxy ...@@ -219,24 +223,24 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Handle dispose of a client/server session /// Handle dispose of a client/server session
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="args"></param> /// <param name="serverConnection"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, private void Dispose(Stream clientStream,
StreamWriter clientStreamWriter, IDisposable args) CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{ {
clientStream?.Close(); clientStream?.Close();
clientStream?.Dispose(); clientStream?.Dispose();
clientStreamReader?.Dispose(); clientStreamReader?.Dispose();
clientStreamWriter?.Close();
clientStreamWriter?.Dispose(); clientStreamWriter?.Dispose();
args?.Dispose(); serverConnection?.Dispose();
} }
} }
} }
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