I'm developing Xamarin.Android app with multiple activities. I'm having trouble maintaining a tcp connection in a singleton.
public sealed class TcpService
{
public TcpClient _tcpClient;
public NetworkStream _networkStream;
public string _recievedMessege;
public Xamarin.Essentials.NetworkAccess networkAccess;
public bool _isAdmin;
private TcpService()
{
networkAccess = Connectivity.NetworkAccess;
_tcpClient = new TcpClient();
_tcpClient.Connect(IPAddress.Parse("10.0.2.2"), 100);
_networkStream = _tcpClient.GetStream();
Thread tcpReaderThread = new Thread(ReceiveTcpCommand);
tcpReaderThread.Start();
}
private static TcpService _instance;
private static readonly object InstanceLock = new object();
public static TcpService GetInstance()
{
if (_instance == null)
{
lock (InstanceLock)
{
if (_instance == null)
_instance = new TcpService();
}
}
return _instance;
}
public void ReceiveTcpCommand()
{
byte[] buffer = new byte[0];
byte[] tempBuffer = new byte[2];
int packageLength = 0;
while (true)
{
if (_tcpClient.Connected)
{
try
{
if (packageLength > 0)
{
_networkStream.Read(buffer, 0, packageLength);
packageLength = 0;
string recieved = Encoding.ASCII.GetString(buffer);
buffer = new byte[1];
_recievedMessege = recieved;
}
else
{
_networkStream.Read(tempBuffer, 0, tempBuffer.Length);
packageLength = BitConverter.ToInt16(tempBuffer);
tempBuffer = new byte[2];
buffer = new byte[packageLength];
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
tempBuffer = new byte[2];
buffer = new byte[1];
}
}
}
}
public void SendTcpCommand(string command)
{
if (_tcpClient.Connected && !string.IsNullOrEmpty(command))
{
try
{
byte[] tempBytes = Encoding.ASCII.GetBytes(command);
byte[] length = BitConverter.GetBytes((Int16)tempBytes.Length);
byte[] sendBytes = new byte[length.Length tempBytes.Length];
sendBytes[0] = length[0];
sendBytes[1] = length[1];
for (int i = 2, j = 0; i < sendBytes.Length; i )
{
sendBytes[i] = tempBytes[j ];
}
_networkStream.Write(sendBytes, 0, sendBytes.Length);
_networkStream.Flush();
}
catch (Exception ex)
{
}
};
}
public void KillTcpConnection()
{
SendTcpCommand(string.Format("Disconnect\n"));
_networkStream.Close();
_tcpClient.Close();
}
}
In the first activity in OnCreate, I create a singleton instance responsible for creating a connection to the server.
TcpService _tcpService;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.LoginLayout);
// Create your application here
//...
_tcpService = TcpService.GetInstance();
if (_tcpService.networkAccess == NetworkAccess.Internet
|| _tcpService.networkAccess == NetworkAccess.ConstrainedInternet
|| _tcpService.networkAccess == NetworkAccess.Local)
{
Thread tcpCallbackThread = new Thread(TcpCallback);
tcpCallbackThread.Start();
}
}
I decided that the best way to handle incoming data would be to place it in a variable inside a singleton, leaving handling for each activity in the form of a thread using the TcpCallback method.
public void TcpCallback()
{
string tempAdminBool=string.Empty;
while (true)
{
if (_tcpService._recievedMessege != null)
{
if (RegexMatcher.IsRegexMatch(_tcpService._recievedMessege, ClientRegexPattern.LoginAccepted))
{
if(RegexMatcher.RegexMatch(_tcpService._recievedMessege, ClientRegexPattern.LoginAccepted, 1).Equals(1))
_tcpService._isAdmin= true;
else
_tcpService._isAdmin= false;
Intent intent = new Intent(this, typeof(MenuActivity));
StartActivity(intent);
}
else if (_tcpService._recievedMessege.Contains("TryToLogin:Denied"))
{
}
else if (RegexMatcher.IsRegexMatch(_tcpService._recievedMessege, TCPServerCommand.InvalidCommand))
{
Button button = FindViewById<Button>(Resource.Id.button1);
button.Text = button.Text.Equals("dupa") ? "0" : "dupa";
//dupa is polish word for testing purposes
}
_tcpService._recievedMessege = null;
}
}
}
When the application is in the first activity, there is no problem with communication, but when I go to the second activity, the client can neither send nor receive data. Although the application correctly uploads data to the stream, they are not sent and I do not receive any error information.
As for the server, it is the simplest multi-threaded program. The server uses TcpListener and when a new connection is detected, it creates a new thread to handle the communication with the client.
public TCPServerController()
{
Console.WriteLine("Server is ready...");
_serverSocket = new TcpListener(_liseningPort);
_tcpClient = default(TcpClient);
_serverSocket.Start();
}
public void SetupServer()
{
int clientCounter = 0;
while (true)
{
if (clientCounter == int.MaxValue)
clientCounter = 0;
clientCounter ;
_tcpClient = _serverSocket.AcceptTcpClient();
Console.WriteLine(string.Format("Client No. {0} Connected", clientCounter));
new HandleClient(_tcpClient, clientCounter);
}
}
}
public class HandleClient
{
TcpClient _client;
int? _authorizedUserId;
bool _isUserAdmin = false;
int _serverClientNumber;
NetworkStream _networkStream;
public HandleClient(TcpClient client, int clientNumber)
{
_client = client;
_serverClientNumber = clientNumber;
Thread clientThread = new Thread(ClientThread);
clientThread.Start();
}
private void ClientThread()
{
byte[] bytesFrom = new byte[1024];
bool connected = true;
if (_client.Connected)
_networkStream = _client.GetStream();
while (connected)
{
try
{
if (_client.Connected)
{
_networkStream.Read(bytesFrom, 0, bytesFrom.Length);
string message = Encoding.ASCII.GetString(bytesFrom);
_networkStream.Flush();
if (!string.IsNullOrEmpty(message))
SendResponse(DecodeCommand(message));
}
else
{
_authorizedUserId = null;
_isUserAdmin = false;
connected = false;
}
}
catch (IOException ex)
{
Console.WriteLine(string.Format("Client No. {0}: The connection was aborted locally!", _serverClientNumber));
connected = false;
_networkStream.Close();
_client.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
bytesFrom = Array.Empty<byte>();
}
}
}
Has anyone an idea what could be wrong?
CodePudding user response:
OK, I find the solution: In server-side ClientThread method in finally I should use
Array.Clear(bytesFrom, 0, bytesFrom.Length);
instead of
bytesFrom = Array.Empty<byte>();
.