如何为流创建 TCP 消息帧 [英] How to create TCP message framing for the stream

查看:25
本文介绍了如何为流创建 TCP 消息帧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的客户端连接到服务器的方式:

Here is how my Client connects to the server:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Net.Sockets;
using System.IO;
using System;
using System.Text.RegularExpressions;
using UnityEngine.SceneManagement;
using Newtonsoft.Json;
using System.Linq;

public class ClientWorldServer : MonoBehaviour {

    public bool socketReady;
    public static TcpClient socket;
    public static NetworkStream stream;
    public static StreamWriter writer;
    public static StreamReader reader;

    public void ConnectToWorldServer()
    {
        if (socketReady)
        {
            return;
        }
        //Default host and port values;
        string host = "127.0.0.1";
        int port = 8080;

        try
        {

            socket = new TcpClient(host, port);
            stream = socket.GetStream();
            writer = new StreamWriter(stream);
            reader = new StreamReader(stream);
            socketReady = true;
        }
        catch (Exception e)
        {
            Debug.Log("Socket error : " + e.Message);
        }

    }
}

这是我如何使用我的 Send 函数将数据发送到服务器:

Here is how i send data to the server using my Send function:

public void Send(string header, Dictionary<string, string> data)
{

    if (stream.CanRead)
    {
        socketReady = true;
    }

    if (!socketReady)
    {
        return;
    }
    JsonData SendData = new JsonData();
    SendData.header = "1x" + header;
    foreach (var item in data)
    {
        SendData.data.Add(item.Key.ToString(), item.Value.ToString());
    }
    SendData.connectionId = connectionId;

    string json = JsonConvert.SerializeObject(SendData);
    var howManyBytes = json.Length * sizeof(Char);
    writer.WriteLine(json);
    writer.Flush();

    Debug.Log("Client World:" + json);
}

如您所见,我将数据像字符串一样发送到流,而不是字节数组.据我所知,我应该将数据作为字节数组发送到消息的大小之前并跟随消息.在服务器端,我不知道如何读取这些数据.

As you can see i'm sending the data to the Stream like a string not like a byte array. As far as i know i should send the data as byte array prepending the size of the message and following the message. On the server side i have no clue how i can read that data.

这是我现在阅读它的方式(它现在有效,但如果我尝试一次发送更多消息,它将不起作用):

Here is how i read it now(it works for now, but it will not work if i try to send more messages at once):

class WorldServer
{
    public List<ServerClient> clients = new List<ServerClient>();
    public List<ServerClient> disconnectList;


    public List<CharactersOnline> charactersOnline = new List<CharactersOnline>();

    public int port = 8080;
    private TcpListener server;
    private bool serverStarted;

    private int connectionIncrementor;

    private string mysqlConnectionString = @"server=xxx;userid=xxx;password=xxx;database=xx";

    private MySqlConnection mysqlConn = null;
    private MySqlDataReader mysqlReader;

    static void Main(string[] args)
    {
        WorldServer serverInstance = new WorldServer();

        Console.WriteLine("Starting World Server...");

        try
        {
            serverInstance.mysqlConn = new MySqlConnection(serverInstance.mysqlConnectionString);
            serverInstance.mysqlConn.Open();
            Console.WriteLine("Connected to MySQL version: " + serverInstance.mysqlConn.ServerVersion + "\n");
        }
        catch (Exception e)
        {
            Console.WriteLine("MySQL Error: " + e.ToString());
        }
        finally
        {
            if (serverInstance.mysqlConn != null)
            {
                serverInstance.mysqlConn.Close();
            }
        }

        serverInstance.clients = new List<ServerClient>();
        serverInstance.disconnectList = new List<ServerClient>();

        try
        {
            serverInstance.server = new TcpListener(IPAddress.Any, serverInstance.port);
            serverInstance.server.Start();

            serverInstance.StartListening();
            serverInstance.serverStarted = true;


            Console.WriteLine("Server has been started on port: " + serverInstance.port);
        }
        catch (Exception e)
        {
            Console.WriteLine("Socket error: " + e.Message);
        }

        new Thread(() =>
        {
            Thread.CurrentThread.IsBackground = true;
            /* run your code here */
            while (true)
            {
                string input = Console.ReadLine();
                string[] commands = input.Split(':');
                if (commands[0] == "show online players")
                {
                    Console.WriteLine("Showing connections\n");
                    foreach (CharactersOnline c in serverInstance.charactersOnline)
                    {
                        Console.WriteLine("Character name: " + c.characterName + "Character ID: " + c.characterId + "Connection id: " + c.connectionId + "\n");
                    }
                }
                continue;
            }

        }).Start();

        while (true)
        {
            serverInstance.Update();
        }
    }

    private void Update()
    {
        //Console.WriteLine("Call");
        if (!serverStarted)
        {
            return;
        }

        foreach (ServerClient c in clients.ToList())
        {
            // Is the client still connected?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                disconnectList.Add(c);
                Console.WriteLine(c.connectionId + " has disconnected.");
                CharacterLogout(c.connectionId);
                continue;
                //Console.WriteLine("Check for connection?\n");
            }
            else
            {



                // Check for message from Client.
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    StreamReader reader = new StreamReader(s, true);
                    string data = reader.ReadLine();

                    if (data != null)
                    {
                        OnIncomingData(c, data);
                    }

                }
                //continue;
            }
        }

        for (int i = 0; i < disconnectList.Count - 1; i++)
        {
            clients.Remove(disconnectList[i]);
            disconnectList.RemoveAt(i);
        }


    }

   private void OnIncomingData(ServerClient c, string data)
    {
        Console.WriteLine(data);
        dynamic json = JsonConvert.DeserializeObject(data);

        string header = json.header;
        //Console.WriteLine("Conn ID:" + json.connectionId);

        string connId = json.connectionId;
        int.TryParse(connId, out int connectionId);

        string prefix = header.Substring(0, 2);

        if (prefix != "1x")
        {
            Console.WriteLine("Unknown packet: " + data + "\n");
        }
        else
        {
            string HeaderPacket = header.Substring(2);
            switch (HeaderPacket)
            {
                default:
                    Console.WriteLine("Unknown packet: " + data + "\n");
                    break;
                case "004":
                    int accountId = json.data["accountId"];
                    SelectAccountCharacters(accountId, connectionId);
                    break;
                case "005":
                    int characterId = json.data["characterId"];
                    getCharacterDetails(characterId, connectionId);
                    break;
                case "006":
                    int charId = json.data["characterId"];
                    SendDataForSpawningOnlinePlayers(charId, connectionId);
                    break;
                case "008":
                    Dictionary<string, string> dictObj = json.data.ToObject<Dictionary<string, string>>();
                    UpdateCharacterPosition(dictObj, connectionId);
                    break;
            }
        }

    private bool IsConnected(TcpClient c)
    {
        try
        {
            if (c != null && c.Client != null && c.Client.Connected)
            {
                if (c.Client.Poll(0, SelectMode.SelectRead))
                {
                    return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
                }

                return true;
            }
            else
            {
                return false;
            }
        }
        catch
        {
            return false;
        }
    }

    private void StartListening()
    {
        server.BeginAcceptTcpClient(OnConnection, server);
    }

    private void OnConnection(IAsyncResult ar)
    {
        connectionIncrementor++;
        TcpListener listener = (TcpListener)ar.AsyncState;
        clients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
        clients[clients.Count - 1].connectionId = connectionIncrementor;
        StartListening();


        //Send a message to everyone, say someone has connected!
        Dictionary<string, string> SendDataBroadcast = new Dictionary<string, string>();
        SendDataBroadcast.Add("connectionId", clients[clients.Count - 1].connectionId.ToString());

        Broadcast("001", SendDataBroadcast, clients, clients[clients.Count - 1].connectionId);
        Console.WriteLine(clients[clients.Count - 1].connectionId + " has connected.");
    }

这就是服务器向客户端发回数据的方式:

And this is how the server send back data to the client:

    private void Send(string header, Dictionary<string, string> data, int cnnId)
    {
        foreach (ServerClient c in clients.ToList())
        {

            if (c.connectionId == cnnId)
            {
                try
                {
                    //Console.WriteLine("Sending...");
                    StreamWriter writer = new StreamWriter(c.tcp.GetStream());
                    if (header == null)
                    {
                        header = "000";
                    }
                    JsonData SendData = new JsonData();
                    SendData.header = "0x" + header;
                    foreach (var item in data)
                    {
                        SendData.data.Add(item.Key.ToString(), item.Value.ToString());
                    }
                    SendData.connectionId = cnnId;

                    string JSonData = JsonConvert.SerializeObject(SendData);

                    writer.WriteLine(JSonData);
                    writer.Flush();
                    //Console.WriteLine("Trying to send data to connection id: " + cnnId + " data:" + sendData);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Write error : " + e.Message + " to client " + c.connectionId);
                }
            }
        }
    }

这是我的 ServerClient 类:

public class ServerClient
{
    public TcpClient tcp;
    public int accountId;
    public int connectionId;
    public ServerClient(TcpClient clientSocket)
    {
        tcp = clientSocket;
    }
}

你能告诉我我应该如何修改客户端上的 Send 函数以将数据作为字节数组发送,以便我可以创建TCP 消息帧"以及我应该如何更改我的以下部分在服务器上:

Can you please show me how i should modify my Send function on the client to send the data as byte array so i can create "TCP Message Framing" and how should i change my the following part on the server:

        foreach (ServerClient c in clients.ToList())
        {
            // Is the client still connected?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                disconnectList.Add(c);
                Console.WriteLine(c.connectionId + " has disconnected.");
                CharacterLogout(c.connectionId);
                continue;
                //Console.WriteLine("Check for connection?\n");
            }
            else
            {



                // Check for message from Client.
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    StreamReader reader = new StreamReader(s, true);
                    string data = reader.ReadLine();

                    if (data != null)
                    {
                        OnIncomingData(c, data);
                    }

                }
                //continue;
            }
        }

哪个负责接收服务器上的数据?

which is responsible for receving data on the server ?

是否可以仅更改客户端和服务器上的这些部分并使其继续工作,但这次使用 TCP 消息帧正确处理?

Is it possible to change only these parts from the Client and on the Server and make it continue to work but this time properly with TCP Message Framing ?

当然,客户端上的侦听器和服务器上的发送功能,一旦我理解了这个框架应该是什么样子,我就会重新制作.

Of course the listener on the client and the Send function of the server i'll remake once i understand how this framing should look like.

推荐答案

你的框架已经被 cr/lf 定义了——所以很多已经存在了;你需要做的是保持一个后台缓冲区 每个流 - 像 MemoryStream 这样的东西可能就足够了,这取决于你需要多大规模;那么本质上你要做的是:

Your frames are already defined by cr/lf - so that much already exists; what you need to do is to keep a back buffer per stream - something like a MemoryStream might be sufficient, depending on how big you need to scale; then essentially what you're looking to do is something like:

while (s.DataAvailable)
{
    // try to read a chunk of data from the inbound stream
    int bytesRead = s.Read(someBuffer, 0, someBuffer.Length);
    if(bytesRead > 0) {
         // append to our per-socket back-buffer
         perSocketStream.Position = perSocketStream.Length;
         perSocketStream.Write(someBuffer, 0, bytesRead);


         int frameSize; // detect any complete frame(s)
         while((frameSize = DetectFirstCRLF(perSocketStream)) >= 0) {
              // decode it as text
              var backBuffer = perSocketStream.GetBuffer(); 
              string message = encoding.GetString(
                    backBuffer, 0, frameSize);
              // remove the frame from the start by copying down and resizing
              Buffer.BlockCopy(backBuffer, frameSize, backBuffer, 0,
                  (int)(backBuffer.Length - frameSize));
              perSocketStream.SetLength(backBuffer.Length - frameSize);

              // process it
              ProcessMessage(message);
         }
    }
}

这篇关于如何为流创建 TCP 消息帧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆