DESKTOP-B25GA9E\W35
2 years ago
39 changed files with 933 additions and 0 deletions
@ -0,0 +1,8 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 8ed219fcc5699874f896054b77910c9e |
||||||
|
folderAsset: yes |
||||||
|
DefaultImporter: |
||||||
|
externalObjects: {} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,8 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 799120a631a78f74da78ed6ca397818a |
||||||
|
folderAsset: yes |
||||||
|
DefaultImporter: |
||||||
|
externalObjects: {} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,8 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 1af376cc70619ba4ba8877d7442e8e55 |
||||||
|
folderAsset: yes |
||||||
|
DefaultImporter: |
||||||
|
externalObjects: {} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -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 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 5cae6cd5a80444e79b49b1b23e819bb0 |
||||||
|
MonoImporter: |
||||||
|
externalObjects: {} |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,8 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 4ebafb77ac26644d5add696267d4e150 |
||||||
|
folderAsset: yes |
||||||
|
DefaultImporter: |
||||||
|
externalObjects: {} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -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 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 2cda990e2423bbf4892e6590ba056729 |
||||||
|
DefaultImporter: |
||||||
|
externalObjects: {} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,8 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 90c4b056dd07146b4b6f2a9d235c0be9 |
||||||
|
folderAsset: yes |
||||||
|
DefaultImporter: |
||||||
|
externalObjects: {} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -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 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: cb7ccee6494e64db7bd20c7e8cfd0a78 |
||||||
|
MonoImporter: |
||||||
|
externalObjects: {} |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -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 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: d7f4a1fb063a4455d96289977a51d644 |
||||||
|
MonoImporter: |
||||||
|
externalObjects: {} |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -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 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 4ac217db4dad5436d9927ea08182cd9b |
||||||
|
MonoImporter: |
||||||
|
externalObjects: {} |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
Loading…
Reference in new issue