So what I am specifically trying to do is get a serial proxy going to pipe an arduino into Unity3d to run on linux. System.IO.Ports just doesn't work on linux with unity.
So I have gotten a python based serial proxy script, I have that up and running just fine. I can netcat into that localhost and actually get output from the arduino.
nc localhost 8082
g'day from bitty 1.0 -- type 'logout' to disconnect
Connecting to /dev/tty.usbmodem5d11... connected.
HELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLOHELLO
I have the arduino only printing out HELLO on repeat.
But the serial proxy is up and running, and has data going over it.
Now I am trying to write the code in Unity to receive this data, and I just can't get it to work.
This is my code for that:
public Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
public byte[] buffer = new byte[256];
int pinRead = 0;
void Start ()
{
socket.Connect(IPAddress.Parse("127.0.0.1"),8082);
}
void Update ()
{
if (socket.IsBound)
{
try
{
int bytesRead;
bytesRead = socket.Receive(buffer);
string incommingdata = System.Text.Encoding.ASCII.GetString(buffer);
Debug.Log(bytesRead+" || "+incommingdata);
}
catch (System.Exception e)
{
}
}
bytesRead is ALWAYS 0 and incomingData just doesn't have anything. It connects, and isBound returns true. I just can't get the code right to receive the data and put it in a format that is usable.
Please help. I need to get this working and its far out of my area of expertise. How do I make this work?
So I got this to work using tcpclient objects and some snippets of code that were posted on a random unity blog....
Here it is if anyone wants to see. I would still be really curious to know how to get the Socket implementation functioning though.
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System;
using System.Text;
using System.IO;
using System.Threading;
public class ArduinoTest : MonoBehaviour
{
private bool socketReady = false;
private TcpClient mySocket;
private NetworkStream theStream;
private StreamWriter theWriter;
private StreamReader theReader;
private string Host = "localhost";
private Int32 Port = 9900;
private int pinRead = 0;
void Start ()
{
setupSocket();
}
void Update ()
{
string read = readSocket();
if (read != "")
{
int value = int.Parse(read);
if ( value > 53)
{
Debug.Log((value-54)+" DOWN");
}
else
{
Debug.Log(value+" UP");
}
}
}
void OnApplicationQuit()
{
writeSocket("logout");
closeSocket();
}
public void setupSocket()
{
try
{
mySocket = new TcpClient(Host, Port);
theStream = mySocket.GetStream();
theWriter = new StreamWriter(theStream);
theReader = new StreamReader(theStream);
socketReady = true;
}
catch (Exception e)
{
Debug.Log("Socket error:" + e);
}
}
public void writeSocket(string theLine)
{
if (!socketReady)
return;
String tmpString = theLine + "\r\n";
theWriter.Write(tmpString);
theWriter.Flush();
}
public string readSocket()
{
if (!socketReady)
return "";
if (theStream.DataAvailable)
return theReader.Read().ToString();
return "";
}
public void closeSocket()
{
if (!socketReady)
return;
theWriter.Close();
theReader.Close();
mySocket.Close();
socketReady = false;
}
public void maintainConnection()
{
if(!theStream.CanRead)
{
setupSocket();
}
}
}
(Sorry I can't post comments yet or would do so there)
BytesRead = 0 typically means the remote side disconnected.
For what it's worth, your code works fine when I run a test TCP server listening for connections on port 8082 and sending out some text messages. So I doubt the problem is on the C# client-side.
Beyond this simple example, "getting this to work" is not necessarily a simple question. Here are a couple things you should keep in mind:
You are making a blocking Receive call, which means you will need to call Update in a loop.
TCP is a streaming protocol, so you might get partial messages or many messages bundled together in the same Read call. Look up "TCP Framing" to get more details on this.
You can try using telnet on this socket, just to see if you can connect to it.
telnet 127.0.0.1 8082
Related
I have a DEALER/DEALER connection.
For some reason, there seems to be an importance to who runs first. If I run the C# one (NetMQ), followed by the python one, the message sent via C# will block until the python dealer is running (as expected - Send blocks until it can send). However...
sometimes, the response I send from the python dealer is not received by the C# poller (the method added to ReceiveReady isn't called). This seems to happen when I send a message from C#, then wait a few (tried ~10) seconds before I run the python client.
However the moment I either dispose of the poller on the C# end or the socket on the python end, I get the message (in the C# app, I seem to only get the first message. But if I dispose of the socket on the python end, I get all messages at once).
I'm not sure what's going on. I would have guessed some kind of timeout on the messages, if it weren't for the fact they end up being received.
C# code (about half the lines are just for disposing):
using UnityEngine;
using AsyncIO;
using NetMQ;
using NetMQ.Sockets;
using System;
using System.Threading.Tasks;
public class MsgClient_BASIC : MonoBehaviour
{
DealerSocket socket;
NetMQPoller poller;
private bool requesterIsStarted;
public static MsgClient_BASIC this_msgclient;
private void OnReceive(object s, NetMQ.NetMQSocketEventArgs a)
{
bool more;
string FS = a.Socket.ReceiveFrameString(out more);
Debug.Log(FS);
}
void Start()
{
this_msgclient = this;
ForceDotNet.Force();
socket = new DealerSocket();
socket.Bind("tcp://localhost:5558");
poller = new NetMQPoller { socket };
socket.ReceiveReady += OnReceive;
poller.RunAsync();
}
private void Update()
{
if (Input.GetKeyDown("m"))
{
Debug.Log("Sending S");
socket.SendFrame("S");
}
}
void OnDestroy()
{
Debug.Log("Stopping async poller");
try
{
poller.StopAsync();
}
catch
{
}
Debug.Log("Disposing Poller");
try
{
poller.Dispose();
}
catch
{
}
Debug.Log("Closing socket");
try
{
socket.Close();
}
catch
{
}
Debug.Log("Disposing of socket");
try
{
((IDisposable)socket).Dispose();
}
catch
{
}
Debug.Log("Cleaning up");
NetMQConfig.Cleanup(true);
}
}
Python code:
import zmq
import json
from datetime import datetime as dt
from os import path
context = zmq.Context()
class Client:
def __init__(self,port):
self.socket = context.socket(zmq.DEALER)
self.socket.setsockopt(zmq.LINGER,500)
self.socket.connect("tcp://localhost:%d" % port)
def check_msgs(self):
try:
response = self.socket.recv_string(flags=zmq.NOBLOCK)
except zmq.Again as e:
response = None
print(response)
return response
def send_dict(self,d):
jd = json.dumps(d)
self.socket.send_string(jd,flags=zmq.NOBLOCK)
def destroy(self):
self.socket.close()
context.destroy(5)
print("Destroyed")
I attached two of the following scripts to a single GameObject.
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class TestScript : MonoBehaviour
{
static UdpClient udp;
private string udp_message = "";
Thread thread;
public int port = 8888;
void Start()
{
udp = new UdpClient(port);
thread = new Thread(new ThreadStart(ThreadMethod));
thread.Start();
}
// Update is called once per frame
void Update()
{
string port_string = port.ToString();
Debug.Log(port_string + ":" + udp_message);
}
void ThreadMethod()
{
while (true)
{
try
{
IPEndPoint remoteEP = null;
byte[] data = udp.Receive(ref remoteEP);
udp_message = Encoding.ASCII.GetString(data);
}
catch
{
}
}
}
}
Then, I used 8888 as the port for one script, as shown in the following image. The other script used port 8889.
I am using two different ports on 8888 and 8889.
However, when I send data to only the 8888 port, the UDP on the 8889 port also seems to be responding.
What is the cause of this?
How can I fix it?
Well your udp field is static!
Both instances of your component overwrite it with different references in
udp = new UdpClient(port);
So basically whichever script runs last "wins" and you are only ever using that UdpClient instance.
Later both your threads will access the very same udp reference for doing
udp.Receive(...);
Simply do not make you field static and you should be fine ^^
private UdpClient udp;
And to your empty catch block ... I would at least make it
catch(Exception e)
{
Debug.LogException(e);
}
which displays an error in the console but doesn't interrupt the thread.
And then also make sure to clean up!
private void OnDestroy()
{
thread?.Abort();
udp?.Dispose();
}
otherwise you might end up with zomby threads or blocked UDP ports ;)
I am working on HoloLens (Unity-UWP) and trying to make a connection with PC (UWP) or Android phone work (Xamarin). So far I tried client and host with both Bluetooth and TCP (even two versions with different libraries) on Android and UWP. I kept the code entirely separated from user interface, so that it is easier to use, to understand and modular. An Action<string> is used to output results (error logs and sent messages).
Everything that is not on the HoloLens works fine (even though it's exactly the same code). It worked from PC (UWP) to Android with client and host switched. But it doesn't even work between HoloLens and PC (UWP). The behavior ranged from crashes (mostly for Bluetooth) to instant disconnection. The last tests resulted in disconnection once bytes are about to be received. It could even read the first 4 bytes (uint for the length of the following UTF-8 message), but then it was disconnected. The other devices seemed to work fine.
What I know: Capabilities are set, the code works, the issue is likely something that is common for everything that has to do with networking and HoloLens.
So the question is, is Unity or HoloLens incompatible with something I am using? What I used which is worth mentioning: StreamSocket, BinaryWriter, BinaryReader, Task (async, await). Or is HoloLens actively blocking communication with applications on other devices? I know it can connect to devices with Bluetooth and that it can connect via TCP, and it looks like people succeed to get it to work. Are there known issues? Or is there something with Unity that causes this - a bad setting maybe? Do I have to use async methods or only sync? Are there incompatibility issues with Tasks/Threads and Unity? Is this possibly the issue (inability to consent to permissions)?
Another thing to note is that I cannot ping HoloLens via its IP by using the cmd, even though the IP is correct.
I'd appreciate any advice, answer or guess. I can provide more information if requested (see also the comments below). I would suggest to focus on the TCP connection as it seemed to be working better and appears to be more "basic." Here is the code:
using System;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Windows.Networking;
using Windows.Networking.Sockets;
#region Common
public abstract class TcpCore
{
protected StreamSocket Socket;
protected BinaryWriter BWriter;
protected BinaryReader BReader;
protected Task ReadingTask;
public bool DetailedInfos { get; set; } = false;
public bool Listening { get; protected set; }
public ActionSingle<string> MessageOutput { get; protected set; } = new ActionSingle<string> (); // Used for message and debug output. They wrap an Action and allow safer use.
public ActionSingle<string> LogOutput { get; protected set; } = new ActionSingle<string> ();
protected const string USED_PORT = "1337";
protected readonly Encoding USED_ENCODING = Encoding.UTF8;
public abstract void Disconnect ();
protected void StartCommunication ()
{
Stream streamOut = Socket.OutputStream.AsStreamForWrite ();
Stream streamIn = Socket.InputStream.AsStreamForRead ();
BWriter = new BinaryWriter (streamOut); //{ AutoFlush = true };
BReader = new BinaryReader (streamIn);
LogOutput.Trigger ("Connection established.");
ReadingTask = new Task (() => StartReading ());
ReadingTask.Start ();
}
public void SendMessage (string message)
{
// There's no need to send a zero length message.
if (string.IsNullOrEmpty (message)) return;
// Make sure that the connection is still up and there is a message to send.
if (Socket == null || BWriter == null) { LogOutput.Trigger ("Cannot send message: No clients connected."); return; }
uint length = (uint) message.Length;
byte[] countBuffer = BitConverter.GetBytes (length);
byte[] buffer = USED_ENCODING.GetBytes (message);
if (DetailedInfos) LogOutput.Trigger ("Sending: " + message);
BWriter.Write (countBuffer);
BWriter.Write (buffer);
BWriter.Flush ();
}
protected void StartReading ()
{
if (DetailedInfos) LogOutput.Trigger ("Starting to listen for input.");
Listening = true;
while (Listening)
{
try
{
if (DetailedInfos) LogOutput.Trigger ("Starting a listen iteration.");
// Based on the protocol we've defined, the first uint is the size of the message. [UInt (4)] + [Message (1*n)] - The UInt describes the length of the message (=n).
uint length = BReader.ReadUInt32 ();
if (DetailedInfos) LogOutput.Trigger ("ReadLength: " + length.ToString ());
MessageOutput.Trigger ("A");
byte[] messageBuffer = BReader.ReadBytes ((int) length);
MessageOutput.Trigger ("B");
string message = USED_ENCODING.GetString (messageBuffer);
MessageOutput.Trigger ("Received Message: " + message);
}
catch (Exception e)
{
// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus (e.HResult) == SocketErrorStatus.Unknown)
{
// Seems to occur on disconnects. Let's not throw().
Listening = false;
Disconnect ();
LogOutput.Trigger ("Unknown error occurred: " + e.Message);
break;
}
else
{
Listening = false;
Disconnect ();
break;
}
}
}
LogOutput.Trigger ("Stopped to listen for input.");
}
}
#endregion
#region Client
public class GTcpClient : TcpCore
{
public async void Connect (string target, string port = USED_PORT) // Target is IP address.
{
try
{
Socket = new StreamSocket ();
HostName serverHost = new HostName (target);
await Socket.ConnectAsync (serverHost, port);
LogOutput.Trigger ("Connection successful to: " + target + ":" + port);
StartCommunication ();
}
catch (Exception e)
{
LogOutput.Trigger ("Connection error: " + e.Message);
}
}
public override void Disconnect ()
{
Listening = false;
if (BWriter != null) { BWriter.Dispose (); BWriter.Dispose (); BWriter = null; }
if (BReader != null) { BReader.Dispose (); BReader.Dispose (); BReader = null; }
if (Socket != null) { Socket.Dispose (); Socket = null; }
if (ReadingTask != null) { ReadingTask = null; }
}
}
#endregion
#region Server
public class GTcpServer : TcpCore
{
private StreamSocketListener socketListener;
public bool AutoResponse { get; set; } = false;
public async void StartServer ()
{
try
{
//Create a StreamSocketListener to start listening for TCP connections.
socketListener = new StreamSocketListener ();
//Hook up an event handler to call when connections are received.
socketListener.ConnectionReceived += ConnectionReceived;
//Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
await socketListener.BindServiceNameAsync (USED_PORT);
}
catch (Exception e)
{
LogOutput.Trigger ("Connection error: " + e.Message);
}
}
private void ConnectionReceived (StreamSocketListener listener, StreamSocketListenerConnectionReceivedEventArgs args)
{
try
{
listener.Dispose ();
Socket = args.Socket;
if (DetailedInfos) LogOutput.Trigger ("Connection received from: " + Socket.Information.RemoteAddress + ":" + Socket.Information.RemotePort);
StartCommunication ();
}
catch (Exception e)
{
LogOutput.Trigger ("Connection Received error: " + e.Message);
}
}
public override void Disconnect ()
{
Listening = false;
if (socketListener != null) { socketListener.Dispose (); socketListener = null; }
if (BWriter != null) { BWriter.Dispose (); BWriter.Dispose (); BWriter = null; }
if (BReader != null) { BReader.Dispose (); BReader.Dispose (); BReader = null; }
if (Socket != null) { Socket.Dispose (); Socket = null; }
if (ReadingTask != null) { ReadingTask = null; }
}
}
#endregion
Coincidentially, I just implemented a BT connection between HoloLens and an UWP app. I followed the sample at https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BluetoothRfcommChat.
As capabilities, I set "Bluetooth" (of course), "Internet (Client & Server)" and "Private Networks (Client & Server)". The steps on the server side then are:
Create an RfcommServiceProvider for your own or an existing (eg OBEX object push) service ID.
Create a StreamSocketListener and wire its ConnectionReceived Event.
Bind the service Name on the listener: listener.BindServiceNameAsync(provider.ServiceId.AsString(), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
If you have a custom service ID, set its name along with other attributes you may want to configure. See the sample linked above for this. I think, this is mostly optional.
Start advertising the BT service: provider.StartAdvertising(listener, true);
Once a client connects, there is a StreamSocket in the StreamSocketListenerConnectionReceivedEventArgs that you can use to create a DataReader and DataWriter on like on any other stream. If you only want to allow one client, you can also stop advertising now.
On the client side, you would:
Show the DevicePicker and let the user select the peer device. Do not forget setting a filter like picker.Filter.SupportedDeviceSelectors.Add(BluetoothDevice.GetDeviceSelectorFromPairingState(true)); You can also allow unpaired devices, but you need to call PairAsync before you can continue in step 2. Also, I think there is no way to circumvent the user consent dialogue in this case, so I would recommend pairing before. To be honest, I did not check whether the unpaired stuff works on HoloLens.
You get a DeviceInformation instance from the picker, which you can use to obtain a BT device like await BluetoothDevice.FromIdAsync(info.Id);
Get the services from the device like device.GetRfcommServicesAsync(BluetoothCacheMode.Uncached); and select the one you are interested in. Note that I found that the built-in filtering did not behave as expected, so I just enumerated the result and compared the UUIDs manually. I believe that the UWP implementation performs a case-sensitive string comparison at some point, which might lead to the requested service not showing up although it is there.
Once you found your service, which I will call s from now on, create a StreamSocket and connect using socket.ConnectAsync(s.ConnectionHostName, s.ConnectionServiceName, SocketProtectionLevel.PlainSocket);
Again, you can not create the stream readers and writers like on the server side.
The answer is Threading.
For whoever may have similar issues, I found the solution. It was due to Unity itself, not HoloLens specifically. My issue was that I wrote my code separately in an own class instead of commingle it with UI code, which would have made it 1. unreadable and 2. not modular to use. So I tried a better coding approach (in my opinion). Everybody could download it and easily integrate it and have basic code for text messaging. While this was no issue for Xamarin and UWP, it was an issue for Unity itself (and there the Unity-UWP solution as well).
The receiving end of Bluetooth and TCP seemed to create an own thread (maybe even something else, but I didn't do it actively), which was unable to write on the main thread in Unity, which solely handles GameObjects (like the output log). Thus I got weird log outputs when I tested it on HoloLens.
I created a new TCP code which works for Unity but not the Unity-UWP solution, using TcpClient/TcpListener, in order to try another version with TCP connection. Luckily when I ran that in the editor it finally pointed on the issue itself: The main thread could not be accessed, which would have written into the UI - the text box for the log output. In order to solve that, I just had to use Unity's Update() method in order to set the text to the output. The variables themselves still could be accessed, but not the GameObjects.
I have been following an old tutorial for making a chat program and I have been dissecting it to fit into a new form, although I have gotten it to work as it was intended it runs into the error: "Unable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall."
that points to this part of the code.
while (Connected)
{
// Show the messages in the log TextBox
this.Invoke(new UpdateLogCallback(this.UpdateLog), new object[] { srReceiver.ReadLine() });
}
I only get the error upon closing the client, or disconnecting.
this is the majority of the client code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
namespace Table_Top_RPG
{
public partial class Connect : Form
{
// Will hold the user name
private string UserName = "Unknown";
public static StreamWriter swSender;
private StreamReader srReceiver;
private TcpClient tcpServer;
// Needed to update the form with messages from another thread
private delegate void UpdateLogCallback(string strMessage);
// Needed to set the form to a "disconnected" state from another thread
private delegate void CloseConnectionCallback(string strReason);
private Thread thrMessaging;
private IPAddress ipAddr;
private bool Connected;
public Connect()
{
// On application exit, don't forget to disconnect first
Application.ApplicationExit += new EventHandler(OnApplicationExit);
InitializeComponent();
}
private void BtnConnect_Click(object sender, EventArgs e)
{
// If we are not currently connected but awaiting to connect
if (Connected == false)
{
InitializeConnection();
}
else // We are connected, thus disconnect
{
CloseConnection("Disconnected at user's request.");
}
}
// The event handler for application exit
public void OnApplicationExit(object sender, EventArgs e)
{
if (Connected == true)
{
// Closes the connections, streams, etc.
Connected = false;
swSender.Close();
srReceiver.Close();
tcpServer.Close();
}
}
private void InitializeConnection()
{
// Parse the IP address from the TextBox into an IPAddress object
ipAddr = IPAddress.Parse(Connect.IpBox.Text);
// Start a new TCP connections to the chat server
tcpServer = new TcpClient();
tcpServer.Connect(ipAddr, int.Parse(Connect.PortBox.Text));
// Helps us track whether we're connected or not
Connected = true;
// Prepare the form
UserName = Connect.NameBox.Text;
// Disable and enable the appropriate fields
IpBox.Enabled = false;
NameBox.Enabled = false;
Main.TxtMsg.Enabled = true;
Connect.BtnConnect.Text = "Disconnect";
// Send the desired username to the server
swSender = new StreamWriter(tcpServer.GetStream());
swSender.WriteLine(UserName);
swSender.Flush();
// Start the thread for receiving messages and further communication
thrMessaging = new Thread(new ThreadStart(ReceiveMessages));
thrMessaging.Start();
}
private void ReceiveMessages()
{
// Receive the response from the server
srReceiver = new StreamReader(tcpServer.GetStream());
// If the first character of the response is 1, connection was successful
string ConResponse = srReceiver.ReadLine();
// If the first character is a 1, connection was successful
if (ConResponse[0] == '1')
{
// Update the form to tell it we are now connected
this.Invoke(new UpdateLogCallback(this.UpdateLog), new object[] { "Connected Successfully!" });
}
else // If the first character is not a 1 (probably a 0), the connection was unsuccessful
{
string Reason = "Not Connected: ";
// Extract the reason out of the response message. The reason starts at the 3rd character
Reason += ConResponse.Substring(2, ConResponse.Length - 2);
// Update the form with the reason why we couldn't connect
this.Invoke(new CloseConnectionCallback(this.CloseConnection), new object[] { Reason });
// Exit the method
return;
}
// While we are successfully connected, read incoming lines from the server
while (Connected)
{
// Show the messages in the log TextBox
this.Invoke(new UpdateLogCallback(this.UpdateLog), new object[] { srReceiver.ReadLine() });
}
}
internal void CloseConnection(string Reason)
{
// Show the reason why the connection is ending
Main.ChatLog.AppendText(Reason + "\r\n");
// Enable and disable the appropriate controls on the form
IpBox.Enabled = true;
NameBox.Enabled = true;
Main.TxtMsg.Enabled = false;
BtnConnect.Text = "Connect";
// Close the objects
Connected = false;
swSender.Close();
srReceiver.Close();
tcpServer.Close();
}
// This method is called from a different thread in order to update the log TextBox
private void UpdateLog(string strMessage)
{
// Append text also scrolls the TextBox to the bottom each time
Main.ChatLog.AppendText(strMessage + "\r\n");
}
}
}
there is another form called Main where all the chat dialog is sent, but the majority of its code is not relevant.
if anyone knows a better way to handle this or knows of a good chat program tutorial i can go through for better examples of how clients connect and disconnect is handled properly without crashing I would be much appreciative.
You should probably think about using Asynchronous programming, where there are no blocking calls.
The problem is, as you surely know, that you close your client while there is an active call.
I'm pretty sure NetworkStream and StreamReader/Writer does have some asynchronous methods. Try to look at some here:
http://msdn.microsoft.com/en-us/library/system.io.streamreader.readasync.aspx
I believe you need to close and dispose each stream per use in your case because you are performing them synchronously. Consider using the using statement for writing...and do something similar for reading. Plus don't forget to remove them from the CloseConnection...
using (NetworkStream ns=tcpServer.GetStream())
{
swSender = new StreamWriter(ns);
swSender.WriteLine(UserName);
swSender.Flush();
ns.Close();
ns.Dispose();
swSender = null;
}
I have been doing several hours of research on a topic that I thought would've been very trivial. So far I've come up empty handed and wanted to see what you guys think. I'm currently messing with XNA (which is actually quite irrelevant now that I think about it) building a client/server architecture for a "game" project. Really nothing more than playing around with networking, animations, etc.
Anyway I've read quite a bit about the differences between Synchronous and Asynchronous networks and the viability of threading synchronous applications to simulate asynchronous behavior and have decided to do just that. Now I know my code isn't pretty but I'm just testing right now. Here's how it's (sort of) set up:
Game is run in the main thread.
On initialization->Connect to server.
Send x position of player class's sprite object to server.
Server receives, acknowledges with a print to console and sends the same data back.
Data is read into a logfile.
I've begun work on a messenger class that will eventually read (or sniff) the packets coming from the server and dispatch them accordingly making draw/update calls as needed. My problem is that I cant figure out how to properly thread the connection method to have that run (and then block?) separate from the Send/Receive loop (or what I'd like to be a continuous loop).
Like I said I'm no expert, I'm just doing this for fun so I may be all over the place. Anyway here are the essentials code-wise:
Networking.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
namespace DrawTest
{
class Networking
{
public void StartClient(Messenger m)
{
// Data buffer for incoming data.
StreamWriter _con = new StreamWriter("data.txt");
// Connect to a remote device.
try {
// Establish the remote endpoint for the socket.
// This example uses port 11000 on the local computer.
IPHostEntry ipHostInfo = Dns.Resolve("127.0.0.1");
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress,3000);
// Create a TCP/IP socket.
Socket sender = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
// Connect the socket to the remote endpoint. Catch any errors.
try {
sender.Connect(remoteEP);
_con.WriteLine("Socket connected to {0}",
sender.RemoteEndPoint.ToString());
while (m.isAlive)
{
m.SocketStream(sender, _con);
}
if (Messenger.mPacket() == "close_socket")
{
_con.WriteLine("Connection closed by client.");
sender.Shutdown(SocketShutdown.Both);
sender.Close();
}
} catch (ArgumentNullException ane) {
_con.WriteLine("ArgumentNullException : {0}",ane.ToString());
} catch (SocketException se) {
_con.WriteLine("SocketException : {0}",se.ToString());
} catch (Exception e) {
_con.WriteLine("Unexpected exception : {0}", e.ToString());
}
_con.Flush();
}
catch (Exception e) {
_con.WriteLine(e.ToString());
_con.Flush();
}
}
}
}
Messenger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace DrawTest
{
public class Messenger
{
byte[] bytes = new byte[1024];
byte[] incBuffer = new byte[1024];
public bool isAlive = true;
Vector2 position = new Vector2(0.0f, 0.0f);
public Vector2 getPos()
{
return position;
}
public void setPos(Vector2 pos)
{
position = pos;
}
public void SocketStream(Socket s, StreamWriter logfile)
{
byte[] msg = null;
int bytesSent = 0;
int bytesRec = 0;
msg = BitConverter.GetBytes(position.X);
// Encode the data string into a byte array.
bytesSent = s.Send(msg);
// Receive the response from the remote device.
bytesRec = s.Receive(incBuffer);
//logfile.WriteLine(Messenger.mDecoder(incBuffer, bytesRec));
}
public string mDecoder(byte[] msg, int size)
{
string DecodedMessage;
byte[] bytes = new byte[1024];
DecodedMessage = Encoding.ASCII.GetString(msg, 0, size);
if (DecodedMessage == "close_socket")
{
isAlive = false;
return DecodedMessage;
}
return DecodedMessage;
}
public static string mPacket()
{
return null;
}
}
}
Think that should do it. The other code is relatively self-explanatory (abstract player/sprite classes and the typical XNA Game.cs)
Thanks in advance for any help!
You may do something along the lines of:
public void SendData(Socket s)
{
byte[] msg = null;
int bytesSent = 0;
msg = BitConverter.GetBytes(position.X);
// Encode the data string into a byte array.
bytesSent = s.Send(msg);
}
void ReceiveData(Socket s)
{
int bytesExpected = 1024; // somehow specify the number of bytes expected
int totalBytesRec = 0; // adds up all the bytes received
int bytesRec = -1; // zero means that you're done receiving
while(bytesRec != 0 && totalBytesRec < bytesExpected )
{
// Receive the response from the remote device.
bytesRec = s.Receive(incBuffer);
totalBytesRec += bytesRec;
}
}
Back in your StartClient class, you should start your receive thread first then send the data:
// Start your receive thread first
Thread t = new Thread(()=>{ReceiveData(sender);});
t.IsBackground = true;
t.Start();
// Then send the data
SendData(sender);
// Wait for the thread to terminate (if you need to)
t.Join(30000);
// Once you close the socket, then it will throw an exception
// in the receive thread (which you should catch) and you can
// exit the thread, thus terminating the thread.
This is roughly how you would start a thread that performs the receive.
Update (based on comments)
I would recommend that you take a look at some of the Patterns for Multithreaded Network Server in C#.
The server side should start a new thread for every client connection accepted and a "connection handler" should take over and manage the sending/receiving of data from there on:
while(serverRunning)
{
Socket clientSocket = serverSocket.Accept();
// You can write your own connection handler class that automatically
// starts a new ReceiveData thread when it gets a client connection
ConnectionHandler chandler = new ConnectionHandler(clientSocket);
// Have an on-client-disconnected event which you can subscribe to
// and remove the handler from your list when the client is disconnected
chandler.OnClinetDisconnectedEvent += new OnClientDisconnectedDelegate(OnClientDisconnected);
mHandlerList.Add(chandler);
}
// When you're terminating the program, then just go through
// the list of active ConnectionHandlers and call some method
// which tells them to close their connections with the clients
// and terminates the thread.
To be even more precise, you are likely to have the very similar behavior with the client's and the server's ReceiveData method: i.e. synchronously send a message back whenever they receive some message. Here is a more realistic example that might help you conceptualize it better:
void ReceiveData(Socket s)
{
int bytesExpected = 1024; // somehow specify the number of bytes expected
int totalBytesRec = 0; // adds up all the bytes received
int bytesRec = -1; // zero means that you're done receiving
while(bytesRec != 0 && totalBytesRec < bytesExpected )
{
// Receive the response from the remote device.
bytesRec = s.Receive(incBuffer);
totalBytesRec += bytesRec;
if(needToReply)
{
// Send another message
SendData(s);
}
}
}
This is a long running thread, of course, so you would generally like to have it run for as long as the player is connected to the internet. The comment about closing the connection and terminating the thread is specifically for the situation where you need to have a graceful exit (i.e. the player quits the game or the rare case that server needs to be shut down).