Как отправлять и получать TCP-сообщения во время потокового видео? (Сеть Unity и Socket)

Мне удалось создать клиентское приложение TCP, которое передает видео в серверное приложение с помощью Unity и Sockets с большой помощью Programmer от этот ответ: https://stackoverflow.com/a/42727918/8713780

Теперь мне нужно во время потоковой передачи видео отправить простое текстовое сообщение с сервера клиенту, которое запускает клиент для немедленной отправки ответного сообщения обратно на сервер.

Первая попытка почти всегда успешна, но при второй попытке ответное сообщение на сервер приводит к зависанию видео, само сообщение повреждено, и я получаю это исключение: «System.OverflowException: Number overflow. at ‹0x00000> "

Это серверный скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System;
using UnityEngine.UI;
using System.IO;

public class MySocketsServer : MonoBehaviour {

    TcpListener listner;
    TcpClient client;
    NetworkStream stream;
    StreamWriter streamWriter;
    StreamReader streamReader;

    int port = 8010;
    int SEND_RECEIVE_COUNT = 15;

    bool getVideo = false;
    bool waitingForClient = false;
    bool serverConnected = false;
    bool clientConnected = false;

    private List<TcpClient> clients = new List<TcpClient>();

    WaitForEndOfFrame waitEndOfFrame = new WaitForEndOfFrame();
    WaitForFixedUpdate waitFixedUpdate = new WaitForFixedUpdate ();

    Texture2D tex;

    public RawImage serverRawImage; 

    public GameObject startServerUiBtn;
    public GameObject stopServerUiBtn;
    public GameObject startStopTrackingUiBtn;

    public Text startStopTrackingTxt;





    void Start(){
        Application.runInBackground = true;

        tex = new Texture2D(2, 2, TextureFormat.RGB24, false);
    }




    public void StartServer () {
        listner = new TcpListener(IPAddress.Any, port);
        client = new TcpClient ();

        listner.Start();

        serverConnected = true;

        Debug.Log ("Server conected! - IP = " + Network.player.ipAddress);
        UIDebugLog.VisableLog ("Server conected! - IP = " + Network.player.ipAddress);

        StartCoroutine (WaitForClient());

        startServerUiBtn.SetActive (false);

    }



    public void StopServer(){
        stopServerUiBtn.SetActive (false);
        startStopTrackingUiBtn.SetActive (false);
        serverConnected = false;
        getVideo = false;

        streamWriter.Close ();
        stream.Close ();
        client.Close ();

        foreach (TcpClient c in clients) {
            c.Close ();
        }

        if (listner != null)
        {
            listner.Stop();
        }

        startServerUiBtn.SetActive (true);

        serverRawImage.gameObject.SetActive (false);
    }



    private void OnApplicationQuit()
    {
        StopServer ();
    }





    IEnumerator WaitForClient()
    {

        clientConnected = false;
        waitingForClient = true;
        stopServerUiBtn.SetActive (true);

        // Wait for client to connect in another Thread 
        Loom.RunAsync(() =>
            {
                while (waitingForClient)
                {
                    // Wait for client connection
                    client = listner.AcceptTcpClient();
                    // We are connected
                    clients.Add(client);
                    clientConnected = true;
                }
            });

        //Wait until client has connected
        while (!clientConnected)
        {
            yield return null;
        }

        stream = client.GetStream ();

        streamWriter = new StreamWriter(stream);
        streamReader = new StreamReader(stream);

        streamWriter.AutoFlush = true;

        startStopTrackingUiBtn.SetActive (true);

        serverRawImage.gameObject.SetActive (true);

        Debug.Log("Client is Connected!");
        UIDebugLog.VisableLog("Client is Connected!");

        waitingForClient = false;

        getVideo = true;
        imageReceiver ();

    }


    int imageSize;
    void imageReceiver()
    {
        Loom.RunAsync(() =>
            {
                while (getVideo)
                {
                    //Read Image Count
                    imageSize = readImageByteSize(SEND_RECEIVE_COUNT);

                    //Read Image Bytes and Display it
                    readFrameByteArray(imageSize);
                }
            });
    }



    byte[] imageBytesCount;

    private int readImageByteSize(int size)
    {
        bool disconnected = false;

        try{

            imageBytesCount = new byte[size];
            var total = 0;
            do
            {
                var read = stream.Read(imageBytesCount, total, size - total);

                if (read == 0)
                {
                    disconnected = true;
                    break;
                }
                total += read;
            } while (total != size);

        }catch(Exception e){
            Debug.Log ("readImageByteSize() error: " + e);
            disconnected = true;
        }

        if (disconnected)
        {
            return -1;
        }
        else
        {
            return frameByteArrayToByteLength(imageBytesCount);
        }

    }


    //Converts the byte array to the data size and returns the result
    int frameByteArrayToByteLength(byte[] frameBytesLength)
    {
        return BitConverter.ToInt32(frameBytesLength, 0);
    }


    byte[] imageBytes;

    private void readFrameByteArray(int size)
    {
        try{

            bool disconnected = false;

            imageBytes = new byte[size];
            var total = 0;
            do
            {
                var read = stream.Read(imageBytes, total, size - total);

                if (read == 0)
                {
                    disconnected = true;
                    break;
                }
                total += read;
            } while (total != size);

        bool readyToReadAgain = false;

        if (!disconnected)
        {
            //Display Image on the main Thread
            Loom.QueueOnMainThread(() =>
                {
                    displayReceivedImage(imageBytes);
                    readyToReadAgain = true;
                });
        }

        //Wait until old Image is displayed
        while (!readyToReadAgain)
        {
            System.Threading.Thread.Sleep(1);
        }

        }catch(Exception e){
            Debug.Log ("readFrameByteArray() error: " + e);
        }
    }


    void displayReceivedImage(byte[] receivedImageBytes)
    {
        tex.LoadImage(receivedImageBytes);
        serverRawImage.texture = tex;
    }





    bool waitingForMsgCallback = false;

    public void SendMsgToTheClient(){
        try{
            // Stop recieving video for a moment:
            getVideo = false;

            // Start listenung to the callback:
            waitingForMsgCallback = true;
            StartCoroutine (RecieveMsgCallback());

            // send the msg:
            streamWriter.WriteLine ("msg_from_the_server");

            Debug.Log("msg_from_the_server sended");
            UIDebugLog.VisableLog("msg_from_the_server sended");

        }catch(Exception e){
            Debug.Log("SendMsgToTheClient() error: " + e);
        }
    }


    IEnumerator RecieveMsgCallback(){
        while (waitingForMsgCallback) {
            try{
                if (stream.DataAvailable) {
                    // get the callback msg:
                    string callbackMsg = streamReader.ReadLine ();

                    Debug.Log ("Recieved msg Callback: " + callbackMsg);
                    UIDebugLog.VisableLog ("Recieved msg Callback: " + callbackMsg);

                    waitingForMsgCallback = false;

                    // Continue receiving video:
                    getVideo = true;
                    imageReceiver ();
                }
            }catch(Exception e){
                Debug.Log("RecieveStartTrackingCallback() error: " + e);
            }

            yield return waitFixedUpdate;
        }
    }


}

И клиентский скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Text;
using System.Net;
using UnityEngine.UI;
using System;
using System.IO;


public class MySocketsClient : MonoBehaviour {

    TcpClient client;
    NetworkStream stream;
    StreamWriter streamWriter;
    StreamReader streamReader;

    int port = 8010;
    int SEND_RECEIVE_COUNT = 15;

    string IP = "127.0.0.1";

    bool connected = false;
    bool sendingVideo = false;
    bool waitingForMsgFromServer = false;

    WebCamTexture webCam;

    public RawImage clientRawImage;

    Texture2D currentTexture;

    WaitForEndOfFrame waitEndOfFrame = new WaitForEndOfFrame();
    WaitForFixedUpdate waitFixedUpdate = new WaitForFixedUpdate();

    public GameObject StartClientUiBtn;
    public GameObject StopClientUIBtn;

    public InputField ipField;

    Rect captureRect;




    void Start () {
        Application.runInBackground = true;

        if (PlayerPrefs.GetString ("server_address") != "") {
            IP = PlayerPrefs.GetString ("server_address");
            ipField.text = IP;
        }
        captureRect = new Rect (0, 0, Screen.width, Screen.height);

        InitWebCam();

        currentTexture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
        frameBytesLength = new byte[SEND_RECEIVE_COUNT];
    }


    public void StartClientBtn(){
        if (IP == "") {
            Debug.Log ("Please enter an ip address");
            return;
        }
        StartCoroutine (StartClient());
        StartClientUiBtn.SetActive (false);
    }


    IEnumerator StartClient(){

        client = new TcpClient();
        StopClientUIBtn.SetActive (true);

        //Connect to server from another Thread
        Loom.RunAsync(() =>
            {
                Debug.Log("Connecting to server...");

                IPAddress ipaddress = IPAddress.Parse(IP);
                client.Connect(ipaddress, port);

                Debug.Log("Connected!");

                connected = true;
            });

        while (!connected) {
            yield return null;
        }

        stream = client.GetStream();

        streamWriter = new StreamWriter(stream);
        streamReader = new StreamReader(stream);

        streamWriter.AutoFlush = true;

        //Start sending video:
        sendingVideo = true;
        StartCoroutine(senderCOR());

        //Start listening to msg from the server:
        waitingForMsgFromServer = true;
        StartCoroutine(RecieveServerMsg());
    }


    public void StopClient(){
        StopClientUIBtn.SetActive (false);
        connected = false;
        sendingVideo = false;

        streamReader.Close ();
        streamWriter.Close ();
        stream.Close ();


        if (client != null)
        {
            client.Close();
        }

        if (webCam != null && webCam.isPlaying)
        {
            webCam.Stop();
        }

        StartClientUiBtn.SetActive (true);
    }


    void OnApplicationQuit()
    {
        StopClient ();
    }



    public void OnChangeAddress(){
        IP = ipField.text;
        PlayerPrefs.SetString ("server_address", IP);
    }




    void InitWebCam(){

        webCam = new WebCamTexture();

        webCam.requestedHeight = 10;
        webCam.requestedWidth = 10;

        clientRawImage.texture = webCam;

        webCam.Play();
    }





    bool readyToGetFrame = false;
    byte[] frameBytesLength;
    byte[] jpgBytes;

    IEnumerator senderCOR()
    {
        readyToGetFrame = true;

        while (sendingVideo)
        {
            //Wait for End of frame
            yield return waitEndOfFrame;

            currentTexture.ReadPixels (captureRect , 0, 0);

            currentTexture.Apply();

            jpgBytes = currentTexture.EncodeToJPG(12);

            //Fill total byte length to send. Result is stored in frameBytesLength
            byteLengthToFrameByteArray(jpgBytes.Length, frameBytesLength);

            readyToGetFrame = false;

            try{
            Loom.RunAsync(() =>
                {
                    //NetworkStream videoStream = client.GetStream();

                    //Send total byte count first
                    stream.Write(frameBytesLength, 0, frameBytesLength.Length);

                    //Send the image bytes
                    stream.Write(jpgBytes, 0, jpgBytes.Length);

                    //Sent. Set readyToGetFrame true
                    readyToGetFrame = true;
                });
            }catch(Exception e){
                Debug.Log ("senderCOR() error: " + e.Message);
                readyToGetFrame = true;
            }
            //Wait until we are ready to get new frame(Until we are done sending data)
            while (!readyToGetFrame)
            {
                yield return null;
            }
        }
    }


    //Converts the data size to byte array and put result to the fullBytes array
    void byteLengthToFrameByteArray(int byteLength, byte[] fullBytes)
    {
        //Clear old data
        Array.Clear(fullBytes, 0, fullBytes.Length);
        //Convert int to bytes
        byte[] bytesToSendCount = BitConverter.GetBytes(byteLength);
        //Copy result to fullBytes
        bytesToSendCount.CopyTo(fullBytes, 0);
    }




    IEnumerator RecieveServerMsg(){
        while (waitingForMsgFromServer) {
            try{
                if (stream.DataAvailable) {
                    // read the msg from the server:
                    string msg = streamReader.ReadLine ();
                    UIDebugLog.VisableLog ("ReadStartTrackingMsg: " + msg);

                    // send a msg callback to the server:
                    streamWriter.WriteLine ("Callback Message from the client");
                    UIDebugLog.VisableLog ("callback sended...");
                    }
            }catch(Exception e){
                Debug.Log ("ReadStartTrackingMsg() error: " + e.Message);
            }
            yield return waitFixedUpdate;
            yield return waitEndOfFrame;
        }
    }

}

Окончательное клиентское приложение должно работать на Android, а серверное приложение должно работать на ПК. Я проверил на обоих и получил те же результаты.

Любые советы будут высоко оценены

Спасибо!


person A. Dorn    schedule 04.10.2017    source источник
comment
Я не проверял весь ваш код, но мне кажется, что вы используете один сокет и для видео, и для обмена сообщениями. Вероятно, было бы намного проще реализовать второй сокет (командный канал) для обмена сообщениями или командами.   -  person C. Gonzalez    schedule 04.10.2017
comment
Привет спасибо! Извините за мое невежество, я новичок в сети... Под внедрением второго сокета вы подразумеваете создание еще одного TcpListener, который прослушивает другой порт, а также создает еще один TcpClient и NetworkStream? Или вы имеете в виду создание объекта Socket? Потому что до сих пор мне не нужно было создавать объект Socket.   -  person A. Dorn    schedule 06.10.2017
comment
Да, это может быть другая пара TcpClient/Lister или UDPClient с обеих сторон. С последним проще обращаться.   -  person C. Gonzalez    schedule 06.10.2017