Идея:
Написать подобную систему постройки своего сервера как реализовано в PhotonServer на асинхронных сокетах.
Чего я уже добился:
Почти все уже реализовано (как для упрощенного, но рабочего, макета), а именно:
1)Отправка структурированного пакета наполненого пользовательскими данными произвольного типа.
2) Получение данных на стороне сервера, формирование и отправка ответа.
3) Получение ответа на клиентах.
Сама проблема:
Для упрощения я сейчас пишу авторизацию используя свою новоиспеченную сетевую библиотечку которую я назвал банально SocketServer.
И вот появились основания добавить некоторое окно, которое будет подобием MessageBox из Windows Forms, оно должно выводить сообщения об ошибке если сервер ответил что авторизация не удалась и вдобавок это окно что бы можно было вызвать из любой сцены.
Вот код класса MessageBox который я накатал пока как временный вариант:
Синтаксис:
Используется csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class MessageBox : MonoBehaviour {
public static CanvasGroup errorFrame;
public static Text caption;
public static Text message;
public static Button submit;
public CanvasGroup ErrorFrame;
public Text CaptionField;
public Text MessageField;
public Button SubmitButton;
void Start()
{
errorFrame = ErrorFrame;
caption = CaptionField;
message = MessageField;
submit = SubmitButton;
submit.onClick.AddListener(() => SubmitButtonHandler());
}
public static void Show(string _caption, string _message)
{
caption.text = _caption;
message.text = _message;
EnableCG(errorFrame, true);
}
public void SubmitButtonHandler()
{
EnableCG(errorFrame, false);
}
static void EnableCG(CanvasGroup cg, bool visible)
{
cg.alpha = visible ? 1 : 0;
cg.blocksRaycasts = visible;
cg.interactable = visible;
}
}
using UnityEngine.UI;
using System.Collections;
public class MessageBox : MonoBehaviour {
public static CanvasGroup errorFrame;
public static Text caption;
public static Text message;
public static Button submit;
public CanvasGroup ErrorFrame;
public Text CaptionField;
public Text MessageField;
public Button SubmitButton;
void Start()
{
errorFrame = ErrorFrame;
caption = CaptionField;
message = MessageField;
submit = SubmitButton;
submit.onClick.AddListener(() => SubmitButtonHandler());
}
public static void Show(string _caption, string _message)
{
caption.text = _caption;
message.text = _message;
EnableCG(errorFrame, true);
}
public void SubmitButtonHandler()
{
EnableCG(errorFrame, false);
}
static void EnableCG(CanvasGroup cg, bool visible)
{
cg.alpha = visible ? 1 : 0;
cg.blocksRaycasts = visible;
cg.interactable = visible;
}
}
Кратко о скрипте, я создал некое визуальное оформление окна в канвасе, закинул скрипт на родительский елемент этого окна в канвасе и закинул в паблик переменные нужные данные. Дальше вы по скрипту можете увидеть что я единажды заношу все данные из простых публичных переменных в статические, что бы иметь возможность использовать их в статической функции Show отвечающей собственно за показ окна. Может реализация не суперская, чесно признаться я слабоват в статичности в языках программирования. Но проблема скорее всего не в этом скрипте, так как убирание всего статика из скрипта и вызов функции Show через объект класса выдал тот же результат.
А вот скрипт SocketServer на клиентской стороне (аналог PhotonServer):
Синтаксис:
Используется csharp
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using CommonTemplate;
public class SocketNetwork : MonoBehaviour {
public string ServerAddress = "178.74.209.179";
public int ServerPort = 3333;
private Socket clientSocket;
private byte[] buffer = new byte[2048];
private BinaryFormatter binFormatter;
private static SocketNetwork _instance;
public static SocketNetwork Instance
{
get { return _instance; }
}
private SocketNetwork PhotonPeer { get; set; }
void Awake()
{
if (Instance != null)
DestroyObject(gameObject);
DontDestroyOnLoad(gameObject);
Application.runInBackground = true;
_instance = this;
}
private string debugMessage = "";
void OnGUI()
{
GUI.skin.box.fontSize = 16;
GUI.Box(new Rect(Screen.width / 2 - 200, 0, 400, 100), debugMessage);
GUI.skin.box.fontSize = 12;
}
void Start()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.BeginConnect(new IPEndPoint(IPAddress.Parse(ServerAddress), ServerPort), new AsyncCallback(ConnectCallback), null);
binFormatter = new BinaryFormatter();
}
//Callbacks
private void ConnectCallback(IAsyncResult ar)
{
try
{
clientSocket.EndConnect(ar);
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
Debug.Log("Client was connected to the server!");
}
catch (Exception ex)
{
debugMessage += ex.Message;
Debug.LogError(ex.Message);
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
clientSocket.EndReceive(ar);
OperationResponse response = ServerUtills.DeserializeResponse(buffer);
switch (response.opCode)
{
case OperationCode.OperationLogin:
LoginHandler(response);
break;
}
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
}
catch (SocketException socketEx)
{
debugMessage += socketEx.Message;
Debug.LogError(socketEx.Message);
}
catch (Exception ex)
{
debugMessage += ex.Message;
Debug.LogError(ex.Message);
}
}
//Operations
public void LoginOperation(string Login, string Pass)
{
OperationData operationData = new OperationData((byte)OperationCode.OperationLogin);
operationData.Parameters = new Dictionary<byte, object>
{
{ (byte)ParameterCode.CharacterLogin, Login },
{ (byte)ParameterCode.CharacterPasswd, Pass }
};
SendPacket(operationData);
}
//Handlers
private void LoginHandler(OperationResponse response)
{
if(response.errCode != ErrorCode.Ok)
{
Debug.Log("FAILURE!");
MessageBox.Show("FAILURE!", "Something is wrong!");
return;
}
Debug.Log("SUCCESS! Name: " + response.Parameters[(byte)ParameterCode.CharacterName] + " ID: " +
response.Parameters[(byte)ParameterCode.CharacterId]);
MessageBox.Show("SUCCESS!", "SUCCESS! Name: " + response.Parameters[(byte)ParameterCode.CharacterName] + " ID: " +
response.Parameters[(byte)ParameterCode.CharacterId]);
}
//Tools
private void SendPacket(OperationData operationData)
{
byte[] data = ServerUtills.SerializePacket(operationData);
debugMessage = "SendingDataLength: " + data.Length;
clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), clientSocket);
}
private void SendCallback(IAsyncResult ar)
{
clientSocket.EndSend(ar);
}
}
using System.Net.Sockets;
using System.Net;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using CommonTemplate;
public class SocketNetwork : MonoBehaviour {
public string ServerAddress = "178.74.209.179";
public int ServerPort = 3333;
private Socket clientSocket;
private byte[] buffer = new byte[2048];
private BinaryFormatter binFormatter;
private static SocketNetwork _instance;
public static SocketNetwork Instance
{
get { return _instance; }
}
private SocketNetwork PhotonPeer { get; set; }
void Awake()
{
if (Instance != null)
DestroyObject(gameObject);
DontDestroyOnLoad(gameObject);
Application.runInBackground = true;
_instance = this;
}
private string debugMessage = "";
void OnGUI()
{
GUI.skin.box.fontSize = 16;
GUI.Box(new Rect(Screen.width / 2 - 200, 0, 400, 100), debugMessage);
GUI.skin.box.fontSize = 12;
}
void Start()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.BeginConnect(new IPEndPoint(IPAddress.Parse(ServerAddress), ServerPort), new AsyncCallback(ConnectCallback), null);
binFormatter = new BinaryFormatter();
}
//Callbacks
private void ConnectCallback(IAsyncResult ar)
{
try
{
clientSocket.EndConnect(ar);
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
Debug.Log("Client was connected to the server!");
}
catch (Exception ex)
{
debugMessage += ex.Message;
Debug.LogError(ex.Message);
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
clientSocket.EndReceive(ar);
OperationResponse response = ServerUtills.DeserializeResponse(buffer);
switch (response.opCode)
{
case OperationCode.OperationLogin:
LoginHandler(response);
break;
}
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
}
catch (SocketException socketEx)
{
debugMessage += socketEx.Message;
Debug.LogError(socketEx.Message);
}
catch (Exception ex)
{
debugMessage += ex.Message;
Debug.LogError(ex.Message);
}
}
//Operations
public void LoginOperation(string Login, string Pass)
{
OperationData operationData = new OperationData((byte)OperationCode.OperationLogin);
operationData.Parameters = new Dictionary<byte, object>
{
{ (byte)ParameterCode.CharacterLogin, Login },
{ (byte)ParameterCode.CharacterPasswd, Pass }
};
SendPacket(operationData);
}
//Handlers
private void LoginHandler(OperationResponse response)
{
if(response.errCode != ErrorCode.Ok)
{
Debug.Log("FAILURE!");
MessageBox.Show("FAILURE!", "Something is wrong!");
return;
}
Debug.Log("SUCCESS! Name: " + response.Parameters[(byte)ParameterCode.CharacterName] + " ID: " +
response.Parameters[(byte)ParameterCode.CharacterId]);
MessageBox.Show("SUCCESS!", "SUCCESS! Name: " + response.Parameters[(byte)ParameterCode.CharacterName] + " ID: " +
response.Parameters[(byte)ParameterCode.CharacterId]);
}
//Tools
private void SendPacket(OperationData operationData)
{
byte[] data = ServerUtills.SerializePacket(operationData);
debugMessage = "SendingDataLength: " + data.Length;
clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), clientSocket);
}
private void SendCallback(IAsyncResult ar)
{
clientSocket.EndSend(ar);
}
}
И в функции LoginHandler при попытке обратиться к MessageBox я получаю ошибку:
При этом если я комментирую строку с вызовом MessageBox (остается еще обычный Debug.Log) ошибки нет, и все выводит в консоль.
Еще, когда учился работать с асинхронными сокетами я видел что-то подобное, и это решали через MethodInvoker но он вроде как только для WindowsForms.
Собственно ошибка говорит о том, что оперировать с елементами окна (Text, Image, CanvasGroup) как с елементами формы в Windows Forms нужно строго с главного потока, но я что то не припоминаю что бы я создавал несколько потоков на стороне клиента . Хотя в теме с многопоточностью я тоже пока новичек.
Собственно довольно большая тема получилась по размерам. Я надеюсь кому-то будет все же не лень это все читать