Tun richtig & schnell-TCP-Netzwerke in c#
Ich versuche eine Lösung implementieren, wo Sie auf der service-Zeitpläne einige Arbeit, um eine Anzahl von Arbeitern. Die Planung selbst soll geschehen über ein einfaches tcp-basiertes Protokoll, weil der Arbeitnehmer kann entweder auf der gleichen oder auf einer anderen Maschine.
Nach einigen Recherchen fand ich diese post. Nach dem Lesen fand ich heraus, dass es mindestens 3 verschiedene mögliche Lösungen, jeder mit seinen vor-und disatvantages.
Habe ich beschlossen zu gehen für die Begin-End-Lösung, und schrieb ein kleines test-Programm zu spielen, um mit es.
Mein client ist nur ein einfaches Programm, dass sendet einige Daten an den server und wird dann beendet.
Dies ist der Client-Code:
class Client
{
static void Main(string[] args)
{
var ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
var s = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
s.Connect(ep);
Console.WriteLine("client Startet, socket connected");
s.Send(Encoding.ASCII.GetBytes("1234"));
s.Send(Encoding.ASCII.GetBytes("ABCDEFGH"));
s.Send(Encoding.ASCII.GetBytes("A1B2C3D4E5F6"));
Console.ReadKey();
s.Close();
}
}
Mein Server ist fast so einfach, nach dem Beispiel:
class Program
{
static void Main(string[] args)
{
var server = new BeginEndTcpServer(8, 1, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345));
//var server = new ThreadedTcpServer(8, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345));
//server.ClientConnected += new EventHandler<ClientConnectedEventArgs>(server_ClientConnected);
server.DataReceived += new EventHandler<DataReceivedEventArgs>(server_DataReceived);
server.Start();
Console.WriteLine("Server Started");
Console.ReadKey();
}
static void server_DataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Receveived Data: " + Encoding.ASCII.GetString(e.Data));
}
}
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace TcpServerTest
{
public sealed class BeginEndTcpServer
{
private class Connection
{
public Guid id;
public byte[] buffer;
public Socket socket;
}
private readonly Dictionary<Guid, Connection> _sockets;
private Socket _serverSocket;
private readonly int _bufferSize;
private readonly int _backlog;
private readonly IPEndPoint serverEndPoint;
public BeginEndTcpServer(int bufferSize, int backlog, IPEndPoint endpoint)
{
_sockets = new Dictionary<Guid, Connection>();
serverEndPoint = endpoint;
_bufferSize = bufferSize;
_backlog = backlog;
}
public bool Start()
{
//System.Net.IPHostEntry localhost = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
try
{
_serverSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
catch (System.Net.Sockets.SocketException e)
{
throw new ApplicationException("Could not create socket, check to make sure not duplicating port", e);
}
try
{
_serverSocket.Bind(serverEndPoint);
_serverSocket.Listen(_backlog);
}
catch (Exception e)
{
throw new ApplicationException("Error occured while binding socket, check inner exception", e);
}
try
{
//warning, only call this once, this is a bug in .net 2.0 that breaks if
//you're running multiple asynch accepts, this bug may be fixed, but
//it was a major pain in the ass previously, so make sure there is only one
//BeginAccept running
_serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), _serverSocket);
}
catch (Exception e)
{
throw new ApplicationException("Error occured starting listeners, check inner exception", e);
}
return true;
}
public void Stop()
{
_serverSocket.Close();
lock (_sockets)
foreach (var s in _sockets)
s.Value.socket.Close();
}
private void AcceptCallback(IAsyncResult result)
{
Connection conn = new Connection();
try
{
//Finish accepting the connection
System.Net.Sockets.Socket s = (System.Net.Sockets.Socket)result.AsyncState;
conn = new Connection();
conn.id = Guid.NewGuid();
conn.socket = s.EndAccept(result);
conn.buffer = new byte[_bufferSize];
lock (_sockets)
_sockets.Add(conn.id, conn);
OnClientConnected(conn.id);
//Queue recieving of data from the connection
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
//Queue the accept of the next incomming connection
_serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), _serverSocket);
}
catch (SocketException)
{
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
_sockets.Remove(conn.id);
}
//Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
_serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), _serverSocket);
}
catch (Exception)
{
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
_sockets.Remove(conn.id);
}
//Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
_serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), _serverSocket);
}
}
private void ReceiveCallback(IAsyncResult result)
{
//get our connection from the callback
Connection conn = (Connection)result.AsyncState;
//catch any errors, we'd better not have any
try
{
//Grab our buffer and count the number of bytes receives
int bytesRead = conn.socket.EndReceive(result);
//make sure we've read something, if we haven't it supposadly means that the client disconnected
if (bytesRead > 0)
{
//put whatever you want to do when you receive data here
conn.socket.Receive(conn.buffer);
OnDataReceived(conn.id, (byte[])conn.buffer.Clone());
//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
}
else
{
//Callback run but no data, close the connection
//supposadly means a disconnect
//and we still have to close the socket, even though we throw the event later
conn.socket.Close();
lock (_sockets)
_sockets.Remove(conn.id);
}
}
catch (SocketException)
{
//Something went terribly wrong
//which shouldn't have happened
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
_sockets.Remove(conn.id);
}
}
}
public bool Send(byte[] message, Guid connectionId)
{
Connection conn = null;
lock (_sockets)
if (_sockets.ContainsKey(connectionId))
conn = _sockets[connectionId];
if (conn != null && conn.socket.Connected)
{
lock (conn.socket)
{
//we use a blocking mode send, no async on the outgoing
//since this is primarily a multithreaded application, shouldn't cause problems to send in blocking mode
conn.socket.Send(message, message.Length, SocketFlags.None);
}
}
else
return false;
return true;
}
public event EventHandler<ClientConnectedEventArgs> ClientConnected;
private void OnClientConnected(Guid id)
{
if (ClientConnected != null)
ClientConnected(this, new ClientConnectedEventArgs(id));
}
public event EventHandler<DataReceivedEventArgs> DataReceived;
private void OnDataReceived(Guid id, byte[] data)
{
if (DataReceived != null)
DataReceived(this, new DataReceivedEventArgs(id, data));
}
public event EventHandler<ConnectionErrorEventArgs> ConnectionError;
}
public class ClientConnectedEventArgs : EventArgs
{
private readonly Guid _ConnectionId;
public Guid ConnectionId { get { return _ConnectionId; } }
public ClientConnectedEventArgs(Guid id)
{
_ConnectionId = id;
}
}
public class DataReceivedEventArgs : EventArgs
{
private readonly Guid _ConnectionId;
public Guid ConnectionId { get { return _ConnectionId; } }
private readonly byte[] _Data;
public byte[] Data { get { return _Data; } }
public DataReceivedEventArgs(Guid id, byte[] data)
{
_ConnectionId = id;
_Data = data;
}
}
public class ConnectionErrorEventArgs : EventArgs
{
private readonly Guid _ConnectionId;
public Guid ConnectionId { get { return _ConnectionId; } }
private readonly Exception _Error;
public Exception Error { get { return _Error; } }
public ConnectionErrorEventArgs(Guid id, Exception ex)
{
_ConnectionId = id;
_Error = ex;
}
}
}
Mein problem ist: Der Server erhält nur einen Trank, der die Daten (in dem Beispiel erhält nur 'EFGHA1B2'). Auch wenn ich nur 4byte Daten, die der server nicht erhalten, bis die Verbindung geschlossen wird.
Das, was ich bin fehlt oder falsch??
Die andere Sache ist, im moment bin ich wirklich verwirrt von den verschiedenen Möglichkeiten, ist die Art und Weise mache ich das eine gute Lösung? Oder sollte ich etwas anderes versuchen?
Jede Hilfe wäre sehr geschätzt werden!
- Warum stellen Sie die buffer size 8 bytes, statt der Einstellung es auf die Größe der Menge an text, die Sie brauchen, um zu senden.
- Die Größe des Puffers ist, dass niedrig, weil es einige test-code, In meiner Produktion Anwendung habe ich möglicherweise befassen sich mit Datenmengen, die größer sind als meine verfügbaren Pufferspeicher. Eh, das überschreiben der Puffer sollte nicht passieren, in jedem Fall, also muss ich etwas falsch gemacht haben hier. Frage ist: was?
- Es würde wahrscheinlich einfacher sein, um Ihren Worker-Anwendungen/ - Dienste hosten eines WCF-Diensts statt Programmieren mit sockets direkt. Sie können konfigurieren, dass ein WCF-Dienst für die Kommunikation über TCP, aber die eigentlichen socket-Kommunikation wird abstrahiert von Ihnen, so dass Sie nur wirklich Gedanken über die Definition der Operationen, die Arbeiter aussetzen und die Struktur der übergebenen Daten für jeden Betrieb.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich würde vermuten, dass der sockel ist nur den Aufruf Ihrer callback-Methode, wenn die Anzahl der empfangenen bytes entspricht der Länge, die Sie angeben, im Aufruf von BeginReceive oder der socket ist geschlossen.
Ich denke, die Frage, die Sie sich stellen müssen, ist, was die Art der Daten, die Sie senden werden über diese Verbindung. Sind die Daten wirklich ein ununterbrochener Strom von bytes, wie der Inhalt einer Datei? Wenn ja, dann vielleicht könnte man vernünftigerweise erwarten, dass den client zum senden der bytes an, und schließen Sie dann die Verbindung sofort. Wie du gesagt hast, wenn der client die Verbindung schließt, dann wird Ihre callback-Methode aufgerufen wird, für die verbleibenden bytes.
Wenn die Verbindung offen bleiben und verwendet werden, zum senden von einzelnen Nachrichten, dann müssen Sie möglicherweise zu entwickeln, ein einfaches Protokoll für die Kommunikation. Wird dem client das senden mehrerer beliebig lange Zeichenfolgen, und wird der server behandeln müssen Sie jede saite einzeln? Wenn dem so ist, dann könnte ein Ansatz sein, um Präfix jeder string mit einem integer-Wert (sagen wir mal 4 Byte) gibt die Länge der Zeichenfolge, die Sie senden werden. Dann ist dein server konnte ersten Aufruf von BeginReceive Angabe einer Länge von 4 bytes sind, verwenden Sie diese 4 bytes zu bestimmen, die Länge der eingehenden Zeichenfolge, dann rufen Sie Erhalten die Angabe der Länge der eingehenden Zeichenfolge. Wenn die Länge der eingehenden Zeichenfolge überschreitet die Größe der Puffer, dann müssen Sie behandeln diesen Fall als gut.
Ich habe eine kleine Beispiel-server-und-client-Anwendung, implementiert diesen Ansatz. Ich habe die Puffer hart codiert als 2048 bytes, aber es funktioniert immer noch, wenn Sie ein Puffer so klein wie 4 bytes.
In meinem Ansatz, wenn die eingehenden Daten überschreitet die Größe meiner vorhandenen Puffer, den ich dann erstellen Sie eine separate byte-array zu speichern, dass die ankommenden Daten. Das ist vielleicht nicht die beste Art damit umzugehen, die situation, aber ich denke, wie du mit dieser situation hängt davon ab, was man eigentlich mit den Daten macht.
Server
Client
Das problem ist, dass sofort nach Erhalt der ersten paar bytes, die Sie überschreiben Sie mit dem nächsten Stapel:
EndReceive
setzen empfangenen Daten in den Puffer, so gibt es keine Notwendigkeit zu nennenReceive
danach. Der Grund, warum man nur die mittleren 8 Byte ist, dass nach Erhalt des letzten batch-code-Blöcke inReceive
, warten auf weitere Daten. Wenn der client die Verbindung schließt, wirdReceive
zurück0
, ändert sich nicht der Puffer und der callback aufgerufen wird, mit was auch immerEndReceive
erhalten hatte.