Отправка и получение данных по сети с использованием TcpClient - PullRequest
34 голосов
/ 31 августа 2010

Мне нужно разработать сервис, который будет подключаться к 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");

Правильно ли это использование?Или есть лучший способ?

Ответы [ 5 ]

21 голосов
/ 28 апреля 2011

Будьте осторожны - это очень старое и громоздкое «решение».

Кстати, вы можете использовать технологию сериализации для отправки строк, чисел или любых объектов, которые поддерживают сериализацию (большинство данных .NET).классы и структуры хранения [Serializable]).Там вы должны сначала отправить поток Int32 длиной в четыре байта в поток, а затем отправить в него двоичные данные (System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).

С другой стороны илисоединение (на самом деле с обеих сторон) у вас определенно должен быть буфер byte [], который вы добавите и обрежете влево во время выполнения, когда поступят данные.

Что-то подобное я использую:

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(); }
    }
}

Это всего лишь пример, вы должны отредактировать его для своего использования.

21 голосов
/ 31 августа 2010

Во-первых, я рекомендую вам использовать WCF, .NET Remoting или какую-либо другую высокоуровневую коммуникационную абстракцию.Кривая обучения для «простых» сокетов почти такая же высокая, как у WCF, потому что при использовании TCP / IP существует множество неочевидных ловушек.

Если вы решите продолжить работу по пути TCP / IP, топросмотрите мои .NET TCP / IP FAQ , в частности разделы обрамление сообщений и спецификации протокола приложения .

Также используйте API асинхронных сокетов,Синхронные API не масштабируются и в некоторых ситуациях ошибки могут привести к взаимоблокировкам.Синхронные API создают довольно небольшой пример кода, но в реальном коде производственного качества используются асинхронные API.

12 голосов
/ 31 августа 2010

Мне повезло, что я использовал объект сокета напрямую (а не клиент TCP).Я создаю объект Server, который выглядит примерно так (я отредактировал некоторые вещи, такие как обработка исключений для краткости, но я надеюсь, что идея натолкнулась на это.) ...

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);
    }
}

Затем яиспользуйте отдельный класс Connection для управления отдельным соединением с удаленным хостом.Это выглядит примерно так ...

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);

    }
}

Вы можете использовать свой объект Connection для отправки данных, напрямую вызывая его Socket, например так ...

this.sock.Send(this.encoding.GetBytes("Hello to you, remote host."));

Как я уже сказал,Я попытался отредактировать здесь код для публикации, поэтому я прошу прощения, если в нем есть какие-либо ошибки.

3 голосов
/ 31 августа 2010

Прежде всего, TCP не гарантирует, что все, что вы отправите, будет получено с таким же чтением на другом конце. Это только гарантирует, что все отправленные вами байты будут доставлены в правильном порядке.

Следовательно, вам нужно будет продолжать наращивать буфер при чтении из потока. Вы также должны будете знать, насколько велико каждое сообщение.

Самое простое - использовать непечатаемый символ ASCII, чтобы отметить конец пакета и найти его в полученных данных.

0 голосов
/ 25 мая 2019

Я разработал библиотеку dotnet, которая может пригодиться. Я исправил проблему, когда никогда не получал все данные, если они превышали буфер, который многие посты обесценили. Все еще есть проблемы с решением, но работает неплохо https://github.com/NicholasLKSharp/DotNet-TCP-Communication

...