I'm trying to write a service that listens to a TCP Socket on a given port until an end of line is recived and then based on the "line" that was received executes a command.
I've followed a basic socket programming tutorial for c# and have come up with the following code to listen to a socket:
public void StartListening()
{
_log.Debug("Creating Maing TCP Listen Socket");
_mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, _port);
_log.Debug("Binding to local IP Address");
_mainSocket.Bind(ipLocal);
_log.DebugFormat("Listening to port {0}",_port);
_mainSocket.Listen(10);
_log.Debug("Creating Asynchronous callback for client connections");
_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
}
public void OnClientConnect(IAsyncResult asyn)
{
try
{
_log.Debug("OnClientConnect Creating worker socket");
Socket workerSocket = _mainSocket.EndAccept(asyn);
_log.Debug("Adding worker socket to list");
_workerSockets.Add(workerSocket);
_log.Debug("Waiting For Data");
WaitForData(workerSocket);
_log.DebugFormat("Clients Connected [{0}]", _workerSockets.Count);
_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
}
catch (ObjectDisposedException)
{
_log.Error("OnClientConnection: Socket has been closed\n");
}
catch (SocketException se)
{
_log.Error("Socket Exception", se);
}
}
public class SocketPacket
{
private System.Net.Sockets.Socket _currentSocket;
public System.Net.Sockets.Socket CurrentSocket
{
get { return _currentSocket; }
set { _currentSocket = value; }
}
private byte[] _dataBuffer = new byte[1];
public byte[] DataBuffer
{
get { return _dataBuffer; }
set { _dataBuffer = value; }
}
}
private void WaitForData(Socket workerSocket)
{
_log.Debug("Entering WaitForData");
try
{
lock (this)
{
if (_workerCallback == null)
{
_log.Debug("Initializing worker callback to OnDataRecieved开发者_运维知识库");
_workerCallback = new AsyncCallback(OnDataRecieved);
}
}
SocketPacket socketPacket = new SocketPacket();
socketPacket.CurrentSocket = workerSocket;
workerSocket.BeginReceive(socketPacket.DataBuffer, 0, socketPacket.DataBuffer.Length, SocketFlags.None, _workerCallback, socketPacket);
}
catch (SocketException se)
{
_log.Error("Socket Exception", se);
}
}
public void OnDataRecieved(IAsyncResult asyn)
{
SocketPacket socketData = (SocketPacket)asyn.AsyncState;
try
{
int iRx = socketData.CurrentSocket.EndReceive(asyn);
char[] chars = new char[iRx + 1];
_log.DebugFormat("Created Char array to hold incomming data. [{0}]",iRx+1);
System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
int charLength = decoder.GetChars(socketData.DataBuffer, 0, iRx, chars, 0);
_log.DebugFormat("Read [{0}] characters",charLength);
String data = new String(chars);
_log.DebugFormat("Read in String \"{0}\"",data);
WaitForData(socketData.CurrentSocket);
}
catch (ObjectDisposedException)
{
_log.Error("OnDataReceived: Socket has been closed. Removing Socket");
_workerSockets.Remove(socketData.CurrentSocket);
}
catch (SocketException se)
{
_log.Error("SocketException:",se);
_workerSockets.Remove(socketData.CurrentSocket);
}
}
This I thought was going to be a good basis for what I wanted to do, but the code I have appended the incoming characters to a text box one by one and didn't do anything with it. Which doesn't really work for what I want to do.
My main issue is the decoupling of the OnDataReceived method from the Wait for data method. which means I'm having issues building a string (I would use a string builder but I can accept multiple connections so that doesn't really work.
Ideally I'd like to look while listening to a socket until I see and end of line character and then call a method with the resulting string as a parameter.
What's the best way to go about doing this.
Try using asynch sockets. The code below will listening to a socket, and if the new line char through telnet is recieved it will echo it back out to the incomming socket. It seems like you would just need to redirect that input to your text box.
private string _hostName;
private const int _LISTENINGPORT = 23;
private Socket _incomingSocket;
byte[] _recievedData;
//todo: do we need 1024 byte? the asynch methods read the bytes as they come
//so when 1 byte typed == 1 byte read. Unless its new line then it is two.
private const int _DATASIZE = 1024;
public ConnectionServer()
{
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
_hostName = Dns.GetHostName();
_recievedData = new byte[_DATASIZE];
_incomingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint endPoint = new IPEndPoint(localAddr, _LISTENINGPORT);
_incomingSocket.Bind(endPoint);
_incomingSocket.Listen(10);
}
~ConnectionServer()
{
}
public void StartListening()
{
_incomingSocket.BeginAccept(new AsyncCallback(OnAccept), _incomingSocket);
}
private void OnAccept(IAsyncResult result)
{
UserConnection connectionInfo = new UserConnection();
Socket acceptedSocket = (Socket)result.AsyncState;
connectionInfo.userSocket = acceptedSocket.EndAccept(result);
connectionInfo.messageBuffer = new byte[_DATASIZE];
//Begin acynch communication with target socket
connectionInfo.userSocket.BeginReceive(connectionInfo.messageBuffer, 0, _DATASIZE, SocketFlags.None,
new AsyncCallback(OnReceiveMessage), connectionInfo);
//reset the listnening socket to start accepting
_incomingSocket.BeginAccept(new AsyncCallback(OnAccept), result.AsyncState);
}
private void OnReceiveMessage(IAsyncResult result)
{
UserConnection connectionInfo = (UserConnection)result.AsyncState;
int bytesRead = connectionInfo.userSocket.EndReceive(result);
if (connectionInfo.messageBuffer[0] != 13 && connectionInfo.messageBuffer[1] != 10)
//ascii for newline and line feed
//todo dress this up
{
if (string.IsNullOrEmpty(connectionInfo.message))
{
connectionInfo.message = ASCIIEncoding.ASCII.GetString(connectionInfo.messageBuffer);
}
else
{
connectionInfo.message += ASCIIEncoding.ASCII.GetString(connectionInfo.messageBuffer);
}
}
else
{
connectionInfo.userSocket.Send(ASCIIEncoding.ASCII.GetBytes(connectionInfo.message), SocketFlags.None);
connectionInfo.userSocket.Send(connectionInfo.messageBuffer, SocketFlags.None);
connectionInfo.message = string.Empty;
connectionInfo.messageBuffer = new byte[_DATASIZE];
}
{
public class UserConnection
{
public Socket userSocket { get; set; }
public Byte[] messageBuffer { get; set; }
public string message { get; set; }
}
}
You seem to have several issues:
You have an asynchronous method called WaitForData
. That's very confusing, as methods with the word Wait
in their names generally block the currently executing thread until something happens (or, optionally, a timeout expires). This does the exact opposite. Are you intending for this to be a synchronous or asynchronous operation?
There's also no need to instantiate the Decoder
object, nor do you need the char
array for (it seems) anything; just call System.Text.Encoding.UTF8.GetString(socketData.DataBuffer, 0, iRx)
.
You also don't appear to be doing anything with lines...which is why it doesn't do anything with lines.
Your approach with using a StringBuilder
is what I would do. I would add a StringBuilder
to the SocketData
class and call it Builder
. As you capture string data, do something like this:
string[] data = System.Text.Encoding.UTF8.GetString(
socketData.DataBuffer, 0, iRx).Split(Environment.NewLine);
socketData.Builder.Append(data[0]);
for(int i = 1; i < data.Length; i++)
{
// the socketData.Builder variable now contains a single line, so do
// something with it here, like raise an event
OnLineReceived(builder.ToString());
socketData.Builder = new StringBuilder(data[i]);
}
The one caveat here is that UTF8
is a multi-byte encoding, meaning that you could potentially grab a chunk of data that cuts off mid-character. It's generally a better idea to do this sort of preprocessing on the other side of the communication, then send the data in an appropriately length-prefixed format.
精彩评论