DESKTOP-B25GA9E\W35
2 years ago
39 changed files with 933 additions and 0 deletions
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 8ed219fcc5699874f896054b77910c9e |
||||
folderAsset: yes |
||||
DefaultImporter: |
||||
externalObjects: {} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 799120a631a78f74da78ed6ca397818a |
||||
folderAsset: yes |
||||
DefaultImporter: |
||||
externalObjects: {} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 1af376cc70619ba4ba8877d7442e8e55 |
||||
folderAsset: yes |
||||
DefaultImporter: |
||||
externalObjects: {} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using UnityEngine; |
||||
|
||||
using WebSocketServer; |
||||
|
||||
public class MyWebSocketServer : WebSocketServer.WebSocketServer |
||||
{ |
||||
override public void OnOpen(WebSocketConnection connection) { |
||||
// Here, (string)connection.id gives you a unique ID to identify the client. |
||||
Debug.Log(connection.id); |
||||
} |
||||
|
||||
override public void OnMessage(WebSocketMessage message) { |
||||
// (WebSocketConnection)message.connection gives you the connection that send the message. |
||||
// (string)message.id gives you a unique ID for the message. |
||||
// (string)message.data gives you the message content. |
||||
Debug.Log(message.connection.id); |
||||
Debug.Log(message.id); |
||||
Debug.Log(message.data); |
||||
} |
||||
|
||||
|
||||
override public void OnClose(WebSocketConnection connection) { |
||||
// Here is the same as OnOpen |
||||
Debug.Log(connection.id); |
||||
} |
||||
|
||||
public void onConnectionOpened (WebSocketConnection connection) { |
||||
Debug.Log("Connection opened: " + connection.id); |
||||
} |
||||
|
||||
public void onMessageReceived (WebSocketMessage message) { |
||||
Debug.Log("Received new message: " + message.data); |
||||
} |
||||
|
||||
public void onConnectionClosed (WebSocketConnection connection) { |
||||
Debug.Log("Connection closed: " + connection.id); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 5cae6cd5a80444e79b49b1b23e819bb0 |
||||
MonoImporter: |
||||
externalObjects: {} |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 4ebafb77ac26644d5add696267d4e150 |
||||
folderAsset: yes |
||||
DefaultImporter: |
||||
externalObjects: {} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,293 @@
@@ -0,0 +1,293 @@
|
||||
%YAML 1.1 |
||||
%TAG !u! tag:unity3d.com,2011: |
||||
--- !u!29 &1 |
||||
OcclusionCullingSettings: |
||||
m_ObjectHideFlags: 0 |
||||
serializedVersion: 2 |
||||
m_OcclusionBakeSettings: |
||||
smallestOccluder: 5 |
||||
smallestHole: 0.25 |
||||
backfaceThreshold: 100 |
||||
m_SceneGUID: 00000000000000000000000000000000 |
||||
m_OcclusionCullingData: {fileID: 0} |
||||
--- !u!104 &2 |
||||
RenderSettings: |
||||
m_ObjectHideFlags: 0 |
||||
serializedVersion: 9 |
||||
m_Fog: 0 |
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} |
||||
m_FogMode: 3 |
||||
m_FogDensity: 0.01 |
||||
m_LinearFogStart: 0 |
||||
m_LinearFogEnd: 300 |
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} |
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} |
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} |
||||
m_AmbientIntensity: 1 |
||||
m_AmbientMode: 3 |
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} |
||||
m_SkyboxMaterial: {fileID: 0} |
||||
m_HaloStrength: 0.5 |
||||
m_FlareStrength: 1 |
||||
m_FlareFadeSpeed: 3 |
||||
m_HaloTexture: {fileID: 0} |
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} |
||||
m_DefaultReflectionMode: 0 |
||||
m_DefaultReflectionResolution: 128 |
||||
m_ReflectionBounces: 1 |
||||
m_ReflectionIntensity: 1 |
||||
m_CustomReflection: {fileID: 0} |
||||
m_Sun: {fileID: 0} |
||||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} |
||||
m_UseRadianceAmbientProbe: 0 |
||||
--- !u!157 &3 |
||||
LightmapSettings: |
||||
m_ObjectHideFlags: 0 |
||||
serializedVersion: 11 |
||||
m_GIWorkflowMode: 1 |
||||
m_GISettings: |
||||
serializedVersion: 2 |
||||
m_BounceScale: 1 |
||||
m_IndirectOutputScale: 1 |
||||
m_AlbedoBoost: 1 |
||||
m_EnvironmentLightingMode: 0 |
||||
m_EnableBakedLightmaps: 0 |
||||
m_EnableRealtimeLightmaps: 0 |
||||
m_LightmapEditorSettings: |
||||
serializedVersion: 12 |
||||
m_Resolution: 2 |
||||
m_BakeResolution: 40 |
||||
m_AtlasSize: 1024 |
||||
m_AO: 0 |
||||
m_AOMaxDistance: 1 |
||||
m_CompAOExponent: 1 |
||||
m_CompAOExponentDirect: 0 |
||||
m_ExtractAmbientOcclusion: 0 |
||||
m_Padding: 2 |
||||
m_LightmapParameters: {fileID: 0} |
||||
m_LightmapsBakeMode: 1 |
||||
m_TextureCompression: 1 |
||||
m_FinalGather: 0 |
||||
m_FinalGatherFiltering: 1 |
||||
m_FinalGatherRayCount: 256 |
||||
m_ReflectionCompression: 2 |
||||
m_MixedBakeMode: 2 |
||||
m_BakeBackend: 0 |
||||
m_PVRSampling: 1 |
||||
m_PVRDirectSampleCount: 32 |
||||
m_PVRSampleCount: 500 |
||||
m_PVRBounces: 2 |
||||
m_PVREnvironmentSampleCount: 500 |
||||
m_PVREnvironmentReferencePointCount: 2048 |
||||
m_PVRFilteringMode: 2 |
||||
m_PVRDenoiserTypeDirect: 0 |
||||
m_PVRDenoiserTypeIndirect: 0 |
||||
m_PVRDenoiserTypeAO: 0 |
||||
m_PVRFilterTypeDirect: 0 |
||||
m_PVRFilterTypeIndirect: 0 |
||||
m_PVRFilterTypeAO: 0 |
||||
m_PVREnvironmentMIS: 0 |
||||
m_PVRCulling: 1 |
||||
m_PVRFilteringGaussRadiusDirect: 1 |
||||
m_PVRFilteringGaussRadiusIndirect: 5 |
||||
m_PVRFilteringGaussRadiusAO: 2 |
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5 |
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2 |
||||
m_PVRFilteringAtrousPositionSigmaAO: 1 |
||||
m_ExportTrainingData: 0 |
||||
m_TrainingDataDestination: TrainingData |
||||
m_LightProbeSampleCountMultiplier: 4 |
||||
m_LightingDataAsset: {fileID: 0} |
||||
m_UseShadowmask: 1 |
||||
--- !u!196 &4 |
||||
NavMeshSettings: |
||||
serializedVersion: 2 |
||||
m_ObjectHideFlags: 0 |
||||
m_BuildSettings: |
||||
serializedVersion: 2 |
||||
agentTypeID: 0 |
||||
agentRadius: 0.5 |
||||
agentHeight: 2 |
||||
agentSlope: 45 |
||||
agentClimb: 0.4 |
||||
ledgeDropHeight: 0 |
||||
maxJumpAcrossDistance: 0 |
||||
minRegionArea: 2 |
||||
manualCellSize: 0 |
||||
cellSize: 0.16666667 |
||||
manualTileSize: 0 |
||||
tileSize: 256 |
||||
accuratePlacement: 0 |
||||
debug: |
||||
m_Flags: 0 |
||||
m_NavMeshData: {fileID: 0} |
||||
--- !u!1 &519420028 |
||||
GameObject: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
serializedVersion: 6 |
||||
m_Component: |
||||
- component: {fileID: 519420032} |
||||
- component: {fileID: 519420031} |
||||
- component: {fileID: 519420029} |
||||
m_Layer: 0 |
||||
m_Name: Main Camera |
||||
m_TagString: MainCamera |
||||
m_Icon: {fileID: 0} |
||||
m_NavMeshLayer: 0 |
||||
m_StaticEditorFlags: 0 |
||||
m_IsActive: 1 |
||||
--- !u!81 &519420029 |
||||
AudioListener: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
m_GameObject: {fileID: 519420028} |
||||
m_Enabled: 1 |
||||
--- !u!20 &519420031 |
||||
Camera: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
m_GameObject: {fileID: 519420028} |
||||
m_Enabled: 1 |
||||
serializedVersion: 2 |
||||
m_ClearFlags: 2 |
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} |
||||
m_projectionMatrixMode: 1 |
||||
m_GateFitMode: 2 |
||||
m_FOVAxisMode: 0 |
||||
m_SensorSize: {x: 36, y: 24} |
||||
m_LensShift: {x: 0, y: 0} |
||||
m_FocalLength: 50 |
||||
m_NormalizedViewPortRect: |
||||
serializedVersion: 2 |
||||
x: 0 |
||||
y: 0 |
||||
width: 1 |
||||
height: 1 |
||||
near clip plane: 0.3 |
||||
far clip plane: 1000 |
||||
field of view: 60 |
||||
orthographic: 1 |
||||
orthographic size: 5 |
||||
m_Depth: -1 |
||||
m_CullingMask: |
||||
serializedVersion: 2 |
||||
m_Bits: 4294967295 |
||||
m_RenderingPath: -1 |
||||
m_TargetTexture: {fileID: 0} |
||||
m_TargetDisplay: 0 |
||||
m_TargetEye: 0 |
||||
m_HDR: 1 |
||||
m_AllowMSAA: 0 |
||||
m_AllowDynamicResolution: 0 |
||||
m_ForceIntoRT: 0 |
||||
m_OcclusionCulling: 0 |
||||
m_StereoConvergence: 10 |
||||
m_StereoSeparation: 0.022 |
||||
--- !u!4 &519420032 |
||||
Transform: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
m_GameObject: {fileID: 519420028} |
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
||||
m_LocalPosition: {x: 0, y: 0, z: -10} |
||||
m_LocalScale: {x: 1, y: 1, z: 1} |
||||
m_Children: [] |
||||
m_Father: {fileID: 0} |
||||
m_RootOrder: 0 |
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
||||
--- !u!1 &944133941 |
||||
GameObject: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
serializedVersion: 6 |
||||
m_Component: |
||||
- component: {fileID: 944133943} |
||||
- component: {fileID: 944133942} |
||||
m_Layer: 0 |
||||
m_Name: WebSocket Server |
||||
m_TagString: Untagged |
||||
m_Icon: {fileID: 0} |
||||
m_NavMeshLayer: 0 |
||||
m_StaticEditorFlags: 0 |
||||
m_IsActive: 1 |
||||
--- !u!114 &944133942 |
||||
MonoBehaviour: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
m_GameObject: {fileID: 944133941} |
||||
m_Enabled: 1 |
||||
m_EditorHideFlags: 0 |
||||
m_Script: {fileID: 11500000, guid: 5cae6cd5a80444e79b49b1b23e819bb0, type: 3} |
||||
m_Name: |
||||
m_EditorClassIdentifier: |
||||
address: 192.168.1.41 |
||||
port: 20000 |
||||
onOpen: |
||||
m_PersistentCalls: |
||||
m_Calls: |
||||
- m_Target: {fileID: 944133942} |
||||
m_MethodName: onConnectionOpened |
||||
m_Mode: 0 |
||||
m_Arguments: |
||||
m_ObjectArgument: {fileID: 0} |
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine |
||||
m_IntArgument: 0 |
||||
m_FloatArgument: 0 |
||||
m_StringArgument: |
||||
m_BoolArgument: 0 |
||||
m_CallState: 2 |
||||
onMessage: |
||||
m_PersistentCalls: |
||||
m_Calls: |
||||
- m_Target: {fileID: 944133942} |
||||
m_MethodName: onMessageReceived |
||||
m_Mode: 0 |
||||
m_Arguments: |
||||
m_ObjectArgument: {fileID: 0} |
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine |
||||
m_IntArgument: 0 |
||||
m_FloatArgument: 0 |
||||
m_StringArgument: |
||||
m_BoolArgument: 0 |
||||
m_CallState: 2 |
||||
onClose: |
||||
m_PersistentCalls: |
||||
m_Calls: |
||||
- m_Target: {fileID: 944133942} |
||||
m_MethodName: onConnectionClosed |
||||
m_Mode: 0 |
||||
m_Arguments: |
||||
m_ObjectArgument: {fileID: 0} |
||||
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine |
||||
m_IntArgument: 0 |
||||
m_FloatArgument: 0 |
||||
m_StringArgument: |
||||
m_BoolArgument: 0 |
||||
m_CallState: 2 |
||||
--- !u!4 &944133943 |
||||
Transform: |
||||
m_ObjectHideFlags: 0 |
||||
m_CorrespondingSourceObject: {fileID: 0} |
||||
m_PrefabInstance: {fileID: 0} |
||||
m_PrefabAsset: {fileID: 0} |
||||
m_GameObject: {fileID: 944133941} |
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
||||
m_LocalPosition: {x: 0, y: 0, z: 0} |
||||
m_LocalScale: {x: 1, y: 1, z: 1} |
||||
m_Children: [] |
||||
m_Father: {fileID: 0} |
||||
m_RootOrder: 1 |
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 2cda990e2423bbf4892e6590ba056729 |
||||
DefaultImporter: |
||||
externalObjects: {} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 90c4b056dd07146b4b6f2a9d235c0be9 |
||||
folderAsset: yes |
||||
DefaultImporter: |
||||
externalObjects: {} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
using UnityEngine; |
||||
|
||||
using System; |
||||
using System.Net; |
||||
using System.Net.Sockets; |
||||
using System.Collections.Concurrent; |
||||
// For parsing the client websocket requests |
||||
using System.Text; |
||||
using System.Text.RegularExpressions; |
||||
// For creating a thread |
||||
using System.Threading; |
||||
|
||||
|
||||
namespace WebSocketServer { |
||||
|
||||
public enum WebSocketEventType { |
||||
Open, |
||||
Close, |
||||
Message |
||||
} |
||||
|
||||
public struct WebSocketMessage { |
||||
public WebSocketMessage(WebSocketConnection connection, string data) { |
||||
this.id = Guid.NewGuid().ToString(); |
||||
this.connection = connection; |
||||
this.data = data; |
||||
} |
||||
|
||||
public string id { get; } |
||||
public WebSocketConnection connection { get; } |
||||
public string data { get; } |
||||
} |
||||
|
||||
public struct WebSocketEvent { |
||||
public WebSocketEvent(WebSocketConnection connection, WebSocketEventType type, string data) { |
||||
this.id = Guid.NewGuid().ToString(); |
||||
this.connection = connection; |
||||
this.type = type; |
||||
this.data = data; |
||||
} |
||||
|
||||
public string id { get; } |
||||
public WebSocketEventType type { get; } |
||||
public WebSocketConnection connection { get; } |
||||
public string data { get; } |
||||
} |
||||
|
||||
public class WebSocketConnection { |
||||
|
||||
public string id; |
||||
private TcpClient client; |
||||
private NetworkStream stream; |
||||
private WebSocketServer server; |
||||
private Thread connectionHandler; |
||||
|
||||
public WebSocketConnection(TcpClient client, WebSocketServer server) { |
||||
this.id = Guid.NewGuid().ToString(); |
||||
this.client = client; |
||||
this.stream = client.GetStream(); |
||||
this.server = server; |
||||
} |
||||
|
||||
public bool Establish() { |
||||
// Wait for enough bytes to be available |
||||
while (!stream.DataAvailable); |
||||
while(client.Available < 3); |
||||
// Translate bytes of request to a RequestHeader object |
||||
Byte[] bytes = new Byte[client.Available]; |
||||
stream.Read(bytes, 0, bytes.Length); |
||||
RequestHeader request = new RequestHeader(Encoding.UTF8.GetString(bytes)); |
||||
|
||||
// Check if the request complies with WebSocket protocol. |
||||
if (WebSocketProtocol.CheckConnectionHandshake(request)) { |
||||
// If so, initiate the connection by sending a reply according to protocol. |
||||
Byte[] response = WebSocketProtocol.CreateHandshakeReply(request); |
||||
stream.Write(response, 0, response.Length); |
||||
|
||||
Debug.Log("WebSocket client connected."); |
||||
|
||||
// Start message handling |
||||
connectionHandler = new Thread(new ThreadStart(HandleConnection)); |
||||
connectionHandler.IsBackground = true; |
||||
connectionHandler.Start(); |
||||
|
||||
// Call the server callback. |
||||
WebSocketEvent wsEvent = new WebSocketEvent(this, WebSocketEventType.Open, null); |
||||
server.events.Enqueue(wsEvent); |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private void HandleConnection () { |
||||
while (true) { |
||||
WebSocketDataFrame dataframe = ReadDataFrame(); |
||||
|
||||
if (dataframe.fin) { |
||||
if ((WebSocketOpCode)dataframe.opcode == WebSocketOpCode.Text) { |
||||
// Let the server know of the message. |
||||
string data = WebSocketProtocol.DecodeText(dataframe); |
||||
WebSocketEvent wsEvent = new WebSocketEvent(this, WebSocketEventType.Message, data); |
||||
server.events.Enqueue(wsEvent); |
||||
} else if ((WebSocketOpCode)dataframe.opcode == WebSocketOpCode.Close) { |
||||
// Handle closing the connection. |
||||
Debug.Log("Client closed the connection."); |
||||
// Close the connection. |
||||
stream.Close(); |
||||
client.Close(); |
||||
// Call server callback. |
||||
WebSocketEvent wsEvent = new WebSocketEvent(this, WebSocketEventType.Close, null); |
||||
server.events.Enqueue(wsEvent); |
||||
// Jump out of the loop. |
||||
break; |
||||
} |
||||
} else { |
||||
Debug.Log("Framentation encoutered."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
private WebSocketDataFrame ReadDataFrame() { |
||||
const int DataframeHead = 2; // Length of dataframe head |
||||
const int ShortPayloadLength = 2; // Length of a short payload length field |
||||
const int LongPayloadLength = 8; // Length of a long payload length field |
||||
const int Mask = 4; // Length of the payload mask |
||||
|
||||
// Wait for a dataframe head to be available, then read the data. |
||||
while (!stream.DataAvailable && client.Available < DataframeHead); |
||||
Byte[] bytes = new Byte[DataframeHead]; |
||||
stream.Read(bytes, 0, DataframeHead); |
||||
|
||||
// Decode the message head, including FIN, OpCode, and initial byte of the payload length. |
||||
WebSocketDataFrame dataframe = WebSocketProtocol.CreateDataFrame(); |
||||
WebSocketProtocol.ParseDataFrameHead(bytes, ref dataframe); |
||||
|
||||
// Depending on the dataframe length, read & decode the next bytes for payload length |
||||
if (dataframe.length == 126) { |
||||
while (client.Available < ShortPayloadLength); // Wait until data is available |
||||
Array.Resize(ref bytes, bytes.Length + ShortPayloadLength); |
||||
stream.Read(bytes, bytes.Length - ShortPayloadLength, ShortPayloadLength); // Read the next two bytes for length |
||||
} else if (dataframe.length == 127) { |
||||
while (client.Available < LongPayloadLength); // Wait until data is available |
||||
Array.Resize(ref bytes, bytes.Length + LongPayloadLength); |
||||
stream.Read(bytes, bytes.Length - LongPayloadLength, LongPayloadLength); // Read the next two bytes for length |
||||
} |
||||
WebSocketProtocol.ParseDataFrameLength(bytes, ref dataframe); // Parse the length |
||||
|
||||
if (dataframe.mask) { |
||||
while (client.Available < Mask); // Wait until data is available |
||||
Array.Resize(ref bytes, bytes.Length + Mask); |
||||
stream.Read(bytes, bytes.Length - Mask, Mask); // Read the next four bytes for mask |
||||
} |
||||
|
||||
while (client.Available < dataframe.length); // Wait until data is available |
||||
Array.Resize(ref bytes, bytes.Length + dataframe.length); |
||||
stream.Read(bytes, bytes.Length - dataframe.length, dataframe.length); // Read the payload |
||||
dataframe.data = bytes; |
||||
|
||||
return dataframe; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2 |
||||
guid: cb7ccee6494e64db7bd20c7e8cfd0a78 |
||||
MonoImporter: |
||||
externalObjects: {} |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,188 @@
@@ -0,0 +1,188 @@
|
||||
using UnityEngine; |
||||
// For String |
||||
using System; |
||||
// For dictionary |
||||
using System.Collections.Generic; |
||||
// For parsing the client websocket requests |
||||
using System.Text; |
||||
using System.Text.RegularExpressions; |
||||
|
||||
namespace WebSocketServer { |
||||
|
||||
struct WebSocketDataFrame { |
||||
public WebSocketDataFrame(bool fin, bool mask, int opcode, int length, int offset, Byte[] data) { |
||||
this.fin = fin; |
||||
this.mask = mask; |
||||
this.opcode = opcode; |
||||
this.length = length; |
||||
this.offset = offset; |
||||
this.data = data; |
||||
} |
||||
|
||||
public bool fin { get; set; } |
||||
public bool mask { get; set; } |
||||
public int opcode { get; set; } |
||||
public int length { get; set; } |
||||
public int offset { get; set; } |
||||
public byte[] data { get; set; } |
||||
} |
||||
|
||||
class RequestHeader { |
||||
|
||||
static Regex head = new Regex("^(GET|POST|PUT|DELETE|OPTIONS) (.+) HTTP/([0-9.]+)", RegexOptions.Compiled); |
||||
static Regex body = new Regex("([A-Za-z0-9-]+): ?([^\n^\r]+)", RegexOptions.Compiled); |
||||
|
||||
public string method = ""; |
||||
public string uri = ""; |
||||
public string version = ""; |
||||
public Dictionary<string, string> headers; |
||||
|
||||
public RequestHeader(string data) { |
||||
headers = new Dictionary<string, string>(); |
||||
|
||||
MatchCollection matches = head.Matches(data); |
||||
foreach (Match match in matches) { |
||||
method = match.Groups[1].Value.Trim(); |
||||
uri = match.Groups[2].Value.Trim(); |
||||
version = match.Groups[3].Value.Trim(); |
||||
} |
||||
|
||||
matches = body.Matches(data); |
||||
foreach (Match match in matches) { |
||||
headers.Add(match.Groups[1].Value.Trim(), match.Groups[2].Value.Trim()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
class WebSocketProtocol { |
||||
|
||||
public static bool CheckConnectionHandshake(RequestHeader request) { |
||||
// The method must be GET. |
||||
if (!String.Equals(request.method, "GET")) { |
||||
Debug.Log("Request does not begin with GET."); |
||||
return false; |
||||
} |
||||
// TODO: Version must be greater than "1.1". |
||||
// Must have a Host. |
||||
if (!request.headers.ContainsKey("Host")) { |
||||
Debug.Log("Request does not have a Host."); |
||||
return false; |
||||
} |
||||
// Must have a Upgrade: websocket |
||||
if (!request.headers.ContainsKey("Upgrade") || !String.Equals(request.headers["Upgrade"], "websocket")) { |
||||
Debug.Log("Request does not have Upgrade: websocket."); |
||||
return false; |
||||
} |
||||
|
||||
// Must have a Connection: Upgrade |
||||
if (!request.headers.ContainsKey("Connection") || request.headers["Connection"].IndexOf("Upgrade") == -1) { |
||||
Debug.Log("Request does not have Connection: Upgrade."); |
||||
return false; |
||||
} |
||||
|
||||
// Must have a Sec-WebSocket-Key |
||||
if (!request.headers.ContainsKey("Sec-WebSocket-Key")) { |
||||
Debug.Log("Request does not have Sec-WebSocket-Key"); |
||||
return false; |
||||
} |
||||
|
||||
// Must have a Sec-WebSocket-Key |
||||
if (!request.headers.ContainsKey("Sec-WebSocket-Key")) { |
||||
Debug.Log("Request does not have Sec-WebSocket-Key"); |
||||
return false; |
||||
} |
||||
|
||||
// Must have a Sec-WebSocket-Version: 13 |
||||
if (!request.headers.ContainsKey("Sec-WebSocket-Version") || !String.Equals(request.headers["Sec-WebSocket-Version"], "13")) { |
||||
Debug.Log("Request does not have Sec-WebSocket-Version: 13"); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public static Byte[] CreateHandshakeReply(RequestHeader request) { |
||||
const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker |
||||
|
||||
Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol |
||||
+ "Connection: Upgrade" + eol |
||||
+ "Upgrade: websocket" + eol |
||||
+ "Sec-WebSocket-Accept: " + Convert.ToBase64String( |
||||
System.Security.Cryptography.SHA1.Create().ComputeHash( |
||||
Encoding.UTF8.GetBytes( |
||||
request.headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
||||
) |
||||
) |
||||
) + eol |
||||
+ eol); |
||||
|
||||
return response; |
||||
} |
||||
|
||||
public static WebSocketDataFrame CreateDataFrame() { |
||||
return new WebSocketDataFrame(false, false, 0, 0, 0, null); |
||||
} |
||||
|
||||
public static void ParseDataFrameHead(byte[] bytes, ref WebSocketDataFrame dataframe) { |
||||
bool fin = (bytes[0] & 0b10000000) != 0, |
||||
mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set" |
||||
|
||||
int opcode = bytes[0] & 0b00001111; |
||||
int msglen = bytes[1] & 0b01111111, |
||||
offset = 2; |
||||
|
||||
dataframe.fin = fin; |
||||
dataframe.mask = mask; |
||||
dataframe.opcode = opcode; |
||||
dataframe.length = msglen; |
||||
dataframe.offset = offset; |
||||
dataframe.data = bytes; |
||||
} |
||||
|
||||
public static void ParseDataFrameLength(byte[] bytes, ref WebSocketDataFrame dataframe) { |
||||
if (dataframe.length == 126) { |
||||
// was ToUInt16(bytes, offset) but the result is incorrect |
||||
dataframe.length = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0); |
||||
dataframe.offset = 4; |
||||
} else if (dataframe.length == 127) { |
||||
dataframe.length = (int) BitConverter.ToUInt64(new byte[] { |
||||
bytes[9], bytes[8], bytes[7], bytes[6], |
||||
bytes[5], bytes[4], bytes[3], bytes[2] |
||||
}, 0); |
||||
dataframe.offset = 10; |
||||
} |
||||
} |
||||
|
||||
public static string DecodeText(WebSocketDataFrame dataframe) { |
||||
if (dataframe.length > 0 && dataframe.mask) { |
||||
byte[] decoded = new byte[dataframe.length]; |
||||
byte[] masks = new byte[4] { |
||||
dataframe.data[dataframe.offset], |
||||
dataframe.data[dataframe.offset + 1], |
||||
dataframe.data[dataframe.offset + 2], |
||||
dataframe.data[dataframe.offset + 3] |
||||
}; |
||||
int payloadOffset = dataframe.offset + 4; |
||||
|
||||
for (int i = 0; i < dataframe.length; ++i) { |
||||
decoded[i] = (byte)(dataframe.data[payloadOffset + i] ^ masks[i % 4]); |
||||
} |
||||
|
||||
string text = Encoding.UTF8.GetString(decoded); |
||||
return text; |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
} |
||||
|
||||
enum WebSocketOpCode { |
||||
Continuation = 0x0, |
||||
Text = 0x1, |
||||
Binary = 0x2, |
||||
Close = 0x8, |
||||
Ping = 0x9, |
||||
Pong = 0xA |
||||
} |
||||
|
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2 |
||||
guid: d7f4a1fb063a4455d96289977a51d644 |
||||
MonoImporter: |
||||
externalObjects: {} |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
@ -0,0 +1,154 @@
@@ -0,0 +1,154 @@
|
||||
using System; |
||||
// Networking libs |
||||
using System.Net; |
||||
using System.Net.Sockets; |
||||
// For creating a thread |
||||
using System.Threading; |
||||
// For List & ConcurrentQueue |
||||
using System.Collections.Generic; |
||||
using System.Collections.Concurrent; |
||||
// Unity & Unity events |
||||
using UnityEngine; |
||||
using UnityEngine.Events; |
||||
|
||||
namespace WebSocketServer { |
||||
[System.Serializable] |
||||
public class WebSocketOpenEvent : UnityEvent<WebSocketConnection> {} |
||||
|
||||
[System.Serializable] |
||||
public class WebSocketMessageEvent : UnityEvent<WebSocketMessage> {} |
||||
|
||||
[System.Serializable] |
||||
public class WebSocketCloseEvent : UnityEvent<WebSocketConnection> {} |
||||
|
||||
public class WebSocketServer : MonoBehaviour |
||||
{ |
||||
// The tcpListenerThread listens for incoming WebSocket connections, then assigns the client to handler threads; |
||||
private TcpListener tcpListener; |
||||
private Thread tcpListenerThread; |
||||
private List<Thread> workerThreads; |
||||
private TcpClient connectedTcpClient; |
||||
|
||||
public ConcurrentQueue<WebSocketEvent> events; |
||||
|
||||
public string address; |
||||
public int port; |
||||
public WebSocketOpenEvent onOpen; |
||||
public WebSocketMessageEvent onMessage; |
||||
public WebSocketCloseEvent onClose; |
||||
|
||||
void Awake() { |
||||
if (onMessage == null) onMessage = new WebSocketMessageEvent(); |
||||
} |
||||
|
||||
void Start() { |
||||
events = new ConcurrentQueue<WebSocketEvent>(); |
||||
workerThreads = new List<Thread>(); |
||||
|
||||
tcpListenerThread = new Thread (new ThreadStart(ListenForTcpConnection)); |
||||
tcpListenerThread.IsBackground = true; |
||||
tcpListenerThread.Start(); |
||||
} |
||||
|
||||
void Update() { |
||||
WebSocketEvent wsEvent; |
||||
while (events.TryDequeue(out wsEvent)) { |
||||
if (wsEvent.type == WebSocketEventType.Open) { |
||||
onOpen.Invoke(wsEvent.connection); |
||||
this.OnOpen(wsEvent.connection); |
||||
} else if (wsEvent.type == WebSocketEventType.Close) { |
||||
onClose.Invoke(wsEvent.connection); |
||||
this.OnClose(wsEvent.connection); |
||||
} else if (wsEvent.type == WebSocketEventType.Message) { |
||||
WebSocketMessage message = new WebSocketMessage(wsEvent.connection, wsEvent.data); |
||||
onMessage.Invoke(message); |
||||
this.OnMessage(message); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void ListenForTcpConnection () { |
||||
try { |
||||
// Create listener on <address>:<port>. |
||||
tcpListener = new TcpListener(IPAddress.Parse(address), port); |
||||
tcpListener.Start(); |
||||
Debug.Log("WebSocket server is listening for incoming connections."); |
||||
while (true) { |
||||
// Accept a new client, then open a stream for reading and writing. |
||||
connectedTcpClient = tcpListener.AcceptTcpClient(); |
||||
// Create a new connection |
||||
WebSocketConnection connection = new WebSocketConnection(connectedTcpClient, this); |
||||
// Establish connection |
||||
connection.Establish(); |
||||
// // Start a new thread to handle the connection. |
||||
// Thread worker = new Thread (new ParameterizedThreadStart(HandleConnection)); |
||||
// worker.IsBackground = true; |
||||
// worker.Start(connection); |
||||
// // Add it to the thread list. TODO: delete thread when disconnecting. |
||||
// workerThreads.Add(worker); |
||||
} |
||||
} |
||||
catch (SocketException socketException) { |
||||
Debug.Log("SocketException " + socketException.ToString()); |
||||
} |
||||
} |
||||
|
||||
// private void HandleConnection (object parameter) { |
||||
// WebSocketConnection connection = (WebSocketConnection)parameter; |
||||
// while (true) { |
||||
// string message = ReceiveMessage(connection.client, connection.stream); |
||||
// connection.queue.Enqueue(message); |
||||
// } |
||||
// } |
||||
|
||||
// private string ReceiveMessage(TcpClient client, NetworkStream stream) { |
||||
// // Wait for data to be available, then read the data. |
||||
// while (!stream.DataAvailable); |
||||
// Byte[] bytes = new Byte[client.Available]; |
||||
// stream.Read(bytes, 0, bytes.Length); |
||||
|
||||
// return WebSocketProtocol.DecodeMessage(bytes); |
||||
// } |
||||
|
||||
public virtual void OnOpen(WebSocketConnection connection) {} |
||||
|
||||
public virtual void OnMessage(WebSocketMessage message) {} |
||||
|
||||
public virtual void OnClose(WebSocketConnection connection) {} |
||||
|
||||
public virtual void OnError(WebSocketConnection connection) {} |
||||
|
||||
|
||||
public void OnDestroy() |
||||
{ |
||||
if (null != tcpListener) { |
||||
tcpListenerThread.Abort(); |
||||
tcpListener.Stop(); |
||||
} |
||||
} |
||||
|
||||
|
||||
// private void SendMessage() { |
||||
// if (connectedTcpClient == null) { |
||||
// return; |
||||
// } |
||||
|
||||
// try { |
||||
// // Get a stream object for writing. |
||||
// NetworkStream stream = connectedTcpClient.GetStream(); |
||||
// if (stream.CanWrite) { |
||||
// string serverMessage = "This is a message from your server."; |
||||
// // Convert string message to byte array. |
||||
// byte[] serverMessageAsByteArray = Encoding.ASCII.GetBytes(serverMessage); |
||||
// // Write byte array to socketConnection stream. |
||||
// stream.Write(serverMessageAsByteArray, 0, serverMessageAsByteArray.Length); |
||||
// Debug.Log("Server sent his message - should be received by client"); |
||||
// } |
||||
// } |
||||
// catch (SocketException socketException) { |
||||
// Debug.Log("Socket exception: " + socketException); |
||||
// } |
||||
// } |
||||
} |
||||
} |
||||
|
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2 |
||||
guid: 4ac217db4dad5436d9927ea08182cd9b |
||||
MonoImporter: |
||||
externalObjects: {} |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
Loading…
Reference in new issue