Отправка и получение данных по сети с помощью TcpClient
мне нужно разработать службу, которая будет подключаться к TCP-серверу. Основными задачами являются чтение входящих сообщений, а также отправка команд на сервер за десять минут, например команды синхронизации. Например, я использовал объект TcpClient, как показано ниже:
...
TcpClient tcpClient = new TcpClient();
tcpClient.Connect("x.x.x.x", 9999);
networkStream = tcpClient.GetStream();
clientStreamReader = new StreamReader(networkStream);
clientStreamWriter = new StreamWriter(networkStream);
while(true)
{
clientStreamReader.Read()
}
кроме того, когда мне нужно написать что-то в любом методе, я использую:
clientStreamWriter.write("xxx");
правильно ли это использование? Или есть лучший способ?
4 ответов
кстати, вы можете использовать технологию сериализации для отправки строк, чисел или любых объектов, которые поддерживают сериализацию (большинство классов и структур хранения данных .NET являются [Сериализуемыми]). Там вы должны сначала отправить Int32-length в четыре байта потоку, а затем отправить binary-serialized (System.Во время выполнения.Сериализация.Форматеры.Двоичный.BinaryFormatter) данные в него.
С другой стороны или соединение (с обеих сторон на самом деле) вы определенно должны иметь байт[] буфер, который u будет добавлять и обрезать слева во время выполнения, когда данные поступают.
что-то вроде этого я использую:
namespace System.Net.Sockets
{
public class TcpConnection : IDisposable
{
public event EvHandler<TcpConnection, DataArrivedEventArgs> DataArrive = delegate { };
public event EvHandler<TcpConnection> Drop = delegate { };
private const int IntSize = 4;
private const int BufferSize = 8 * 1024;
private static readonly SynchronizationContext _syncContext = SynchronizationContext.Current;
private readonly TcpClient _tcpClient;
private readonly object _droppedRoot = new object();
private bool _dropped;
private byte[] _incomingData = new byte[0];
private Nullable<int> _objectDataLength;
public TcpClient TcpClient { get { return _tcpClient; } }
public bool Dropped { get { return _dropped; } }
private void DropConnection()
{
lock (_droppedRoot)
{
if (Dropped)
return;
_dropped = true;
}
_tcpClient.Close();
_syncContext.Post(delegate { Drop(this); }, null);
}
public void SendData(PCmds pCmd) { SendDataInternal(new object[] { pCmd }); }
public void SendData(PCmds pCmd, object[] datas)
{
datas.ThrowIfNull();
SendDataInternal(new object[] { pCmd }.Append(datas));
}
private void SendDataInternal(object data)
{
if (Dropped)
return;
byte[] bytedata;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
try { bf.Serialize(ms, data); }
catch { return; }
bytedata = ms.ToArray();
}
try
{
lock (_tcpClient)
{
TcpClient.Client.BeginSend(BitConverter.GetBytes(bytedata.Length), 0, IntSize, SocketFlags.None, EndSend, null);
TcpClient.Client.BeginSend(bytedata, 0, bytedata.Length, SocketFlags.None, EndSend, null);
}
}
catch { DropConnection(); }
}
private void EndSend(IAsyncResult ar)
{
try { TcpClient.Client.EndSend(ar); }
catch { }
}
public TcpConnection(TcpClient tcpClient)
{
_tcpClient = tcpClient;
StartReceive();
}
private void StartReceive()
{
byte[] buffer = new byte[BufferSize];
try
{
_tcpClient.Client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, DataReceived, buffer);
}
catch { DropConnection(); }
}
private void DataReceived(IAsyncResult ar)
{
if (Dropped)
return;
int dataRead;
try { dataRead = TcpClient.Client.EndReceive(ar); }
catch
{
DropConnection();
return;
}
if (dataRead == 0)
{
DropConnection();
return;
}
byte[] byteData = ar.AsyncState as byte[];
_incomingData = _incomingData.Append(byteData.Take(dataRead).ToArray());
bool exitWhile = false;
while (exitWhile)
{
exitWhile = true;
if (_objectDataLength.HasValue)
{
if (_incomingData.Length >= _objectDataLength.Value)
{
object data;
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream(_incomingData, 0, _objectDataLength.Value))
try { data = bf.Deserialize(ms); }
catch
{
SendData(PCmds.Disconnect);
DropConnection();
return;
}
_syncContext.Post(delegate(object T)
{
try { DataArrive(this, new DataArrivedEventArgs(T)); }
catch { DropConnection(); }
}, data);
_incomingData = _incomingData.TrimLeft(_objectDataLength.Value);
_objectDataLength = null;
exitWhile = false;
}
}
else
if (_incomingData.Length >= IntSize)
{
_objectDataLength = BitConverter.ToInt32(_incomingData.TakeLeft(IntSize), 0);
_incomingData = _incomingData.TrimLeft(IntSize);
exitWhile = false;
}
}
StartReceive();
}
public void Dispose() { DropConnection(); }
}
}
это только пример, вы должны отредактировать его для вашей пользы.
во-первых, я рекомендую вам использовать WCF, .Удаленное взаимодействие. net, или некоторые другие более высокого уровня общения абстракции. Кривая обучения для" простых " сокетов почти так же высока, как WCF, потому что есть так много неочевидных подводных камней при использовании TCP/IP напрямую.
Если вы решили продолжить путь TCP/IP, то просмотрите мой .NET TCP/IP FAQ, особенно разделы на сообщение кадрирования и протокол приложение спецификации.
кроме того, используйте асинхронные API сокетов. Синхронные API-интерфейсы не масштабируются и в некоторых ситуациях ошибок могут вызвать взаимоблокировки. Синхронные API-интерфейсы создают довольно маленький пример кода, но в реальном коде качества производства используются асинхронные API.
мне повезло использовать объект сокета напрямую (а не TCP-клиент). Я создаю объект сервера, который выглядит примерно так (я отредактировал некоторые вещи, такие как обработка исключений для краткости, но я надеюсь, что идея попадется.)...
public class Server()
{
private Socket sock;
// You'll probably want to initialize the port and address in the
// constructor, or via accessors, but to start your server listening
// on port 8080 and on any IP address available on the machine...
private int port = 8080;
private IPAddress addr = IPAddress.Any;
// This is the method that starts the server listening.
public void Start()
{
// Create the new socket on which we'll be listening.
this.sock = new Socket(
addr.AddressFamily,
SocketType.Stream,
ProtocolType.Tcp);
// Bind the socket to the address and port.
sock.Bind(new IPEndPoint(this.addr, this.port));
// Start listening.
this.sock.Listen(this.backlog);
// Set up the callback to be notified when somebody requests
// a new connection.
this.sock.BeginAccept(this.OnConnectRequest, sock);
}
// This is the method that is called when the socket recives a request
// for a new connection.
private void OnConnectRequest(IAsyncResult result)
{
// Get the socket (which should be this listener's socket) from
// the argument.
Socket sock = (Socket)result.AsyncState;
// Create a new client connection, using the primary socket to
// spawn a new socket.
Connection newConn = new Connection(sock.EndAccept(result));
// Tell the listener socket to start listening again.
sock.BeginAccept(this.OnConnectRequest, sock);
}
}
затем я использую отдельный класс соединения для управления отдельным соединением с удаленным хостом. Что-то вроде этого...
public class Connection()
{
private Socket sock;
// Pick whatever encoding works best for you. Just make sure the remote
// host is using the same encoding.
private Encoding encoding = Encoding.UTF8;
public Connection(Socket s)
{
this.sock = s;
// Start listening for incoming data. (If you want a multi-
// threaded service, you can start this method up in a separate
// thread.)
this.BeginReceive();
}
// Call this method to set this connection's socket up to receive data.
private void BeginReceive()
{
this.sock.BeginReceive(
this.dataRcvBuf, 0,
this.dataRcvBuf.Length,
SocketFlags.None,
new AsyncCallback(this.OnBytesReceived),
this);
}
// This is the method that is called whenever the socket receives
// incoming bytes.
protected void OnBytesReceived(IAsyncResult result)
{
// End the data receiving that the socket has done and get
// the number of bytes read.
int nBytesRec = this.sock.EndReceive(result);
// If no bytes were received, the connection is closed (at
// least as far as we're concerned).
if (nBytesRec <= 0)
{
this.sock.Close();
return;
}
// Convert the data we have to a string.
string strReceived = this.encoding.GetString(
this.dataRcvBuf, 0, nBytesRec);
// ...Now, do whatever works best with the string data.
// You could, for example, look at each character in the string
// one-at-a-time and check for characters like the "end of text"
// character ('\u0003') from a client indicating that they've finished
// sending the current message. It's totally up to you how you want
// the protocol to work.
// Whenever you decide the connection should be closed, call
// sock.Close() and don't call sock.BeginReceive() again. But as long
// as you want to keep processing incoming data...
// Set up again to get the next chunk of data.
this.sock.BeginReceive(
this.dataRcvBuf, 0,
this.dataRcvBuf.Length,
SocketFlags.None,
new AsyncCallback(this.OnBytesReceived),
this);
}
}
вы можете использовать объект подключения для отправки данных вызов его сокета напрямую, вот так...
this.sock.Send(this.encoding.GetBytes("Hello to you, remote host."));
как я уже сказал, я попытался отредактировать код здесь для публикации, поэтому я прошу прощения, если в нем есть какие-либо ошибки.
прежде всего, TCP не гарантирует, что все, что вы отправляете, будет получено с тем же чтением на другом конце. Это только гарантирует, что все байты, которые вы отправляете, прибудут и в правильном порядке.
поэтому вам нужно будет продолжать создавать буфер при чтении из потока. Вы также должны знать, насколько велико каждое сообщение.
самый простой когда-либо использовать непечатаемый символ ASCII, чтобы отметить конец пакета и искать его в полученные сведения.