The client is a standard web-browser with the JavaScript websocket implementation, this is my code:
//File: Test.js
//This file contains all the javascript functions required by the client.
//History:
//2024/11/20 Created by Simon Platten
const cintCheckWebSocketInterval = 1000;
const cintReconnectTime = 5;
const cintRetryInitInterval = 2000;
const cintServiceNoResponses = 5;
const cintServiceRequestInterval = 1000;
const cintServiceRequestAltInterval = 10000;
const cstrServerIP = "{SRVR_ADR}";
const cstrServerSocketIP = "{SRVR_SCKT_ADR}";
const cstrRequestPrefix = "/?";
const cstrRequestType = "GET";
const cstrSendMsg = "msg=*US";
const cstrServiceInfoTagRequests = "requests";
const cstrServiceInfoTagResponses = "responses";
const cobjWebSocketErrors = {1000:"Normal Closure"
,1001:"Going Away"
,1002:"Protocol Error"
,1003:"Unsupported Data"
,1004:"---Reserved---"
,1005:"No Status Rcvd"
,1006:"Abnormal Closure"
,1007:"Invalid frame payload data"
,1008:"Policy Violation"
,1009:"Message Too Big"
,1010:"Mandatory Ext."
,1011:"Internal Server Error"
,1015:"TLS handshake"};
var blnSocketClosed = false, intNotConnected, intServiceInterval = 0
, objServiceInfo = {}, tmrCheckWebSocket, tmrInit, tmrService
, webSocket = null;
//Initialisation function
function TestInit() {
if (cstrServerSocketIP[0] == '{') {
//Server hasn't replace this macro yet....install a timer to retry this
tmrInit = setInterval(TestInit, cintRetryInitInterval);
return;
}
if (tmrInit != null && typeof tmrInit !== "undefined") {
clearInterval(tmrInit);
tmrInit = null;
}
if (typeof tmrService !== "undefined") {
return;
}
console.log("Attempting connect to: " + cstrServerSocketIP);
//Create web-socket and set it up
CreateWebSocket();
//Initialise service info object
objServiceInfo[cstrServiceInfoTagRequests] = 0;
objServiceInfo[cstrServiceInfoTagResponses] = 0;
}
function Connected() {
return (typeof webSocket == "object"
&& typeof webSocket.readyState == "number"
&& webSocket.readyState == WebSocket.OPEN);
}
function CreateWebSocket() {
try {
intNotConnected = 0;
console.log(cstrServerSocketIP);
webSocket = new WebSocket(cstrServerSocketIP);
webSocket.binaryType = "arraybuffer";
if (tmrCheckWebSocket != undefined) {
clearInterval(tmrCheckWebSocket);
}
//Setup timeout timer to timeout if webSocket does not connect
tmrCheckWebSocket = setInterval(function() {
var strMsg = "Checking (" + cstrServerSocketIP + "), ";
if (typeof webSocket == "object"
&& typeof webSocket.readyState == "number") {
if (webSocket.readyState == WebSocket.CONNECTING) {
strMsg += "connecting";
} else if (webSocket.readyState == WebSocket.OPEN) {
strMsg += "open (connected)";
} else if (webSocket.readyState == WebSocket.CLOSING) {
strMsg += "closing";
} else if (webSocket.readyState == WebSocket.CLOSED) {
strMsg += "closed";
}
if (WebSocket.readyState != WebSocket.OPEN) {
intNotConnected++;
} else {
intNotConnected = 0;
}
} else {
strMsg += "not a valid connection!";
}
DebugMsg(strMsg);
if (webSocket.readyState != WebSocket.OPEN
&& intNotConnected >= cintReconnectTime) {
CreateWebSocket();
}
}, cintCheckWebSocketInterval);
webSocket.onclose = (event) => {
DebugMsg("onclose");
if (typeof event == "object") {
var strReason = "";
if (typeof event.code == "number") {
strReason += "code[" + event.code + "]";
var strError = cobjWebSocketErrors[event.code];
if (typeof strError == "string") {
strReason += ":" + strError;
}
}
if (typeof event.reason == "string" && event.reason.length > 0) {
if (strReason.length > 0) {
strReason += ", ";
}
strReason += "Reason:\"" + event.reason + "\"";
}
DebugMsg(strReason);
}
//Try again...
CreateWebSocket();
};
webSocket.onerror = (event) => {
DebugMsg("onerror"
+ ((typeof event == "object"
&& typeof event.data != "undefined") ? ":" + String(event.data) : ""));
};
webSocket.onmessage = (event) => {
DebugMsg("onmessage");
webSocket.close();
};
webSocket.onopen = () => {
DebugMsg("onopen");
sendMsg();
};
//Set-up timer to send requests for data updates
InstallServiceTimer(cintServiceRequestInterval);
} catch (e) {
DebugMsg(e);
}
}
function DebugMsg(msg) {
var msgdump = document.getElementById("msgdump");
if (msgdump == null || typeof msgdump === "undefined") {
return;
}
var strContent;
if (typeof msg == "string" && msg.length > 0) {
strContent = msg;
}
if (typeof msg == "object" && typeof msg.message == "string") {
strContent = msg.message;
}
if (typeof strContent == "string" && strContent.length > 0) {
strContent = (new Date()).toUTCString() + ", " + strContent;
msgdump.innerHTML = strContent + "\r\n" + msgdump.innerHTML;
}
}
function InstallServiceTimer(intInterval) {
if (intServiceInterval != intInterval) {
tmrService = setInterval(ServiceRequests, intInterval);
if (tmrService != null && typeof tmrService == "number") {
intServiceInterval = intInterval;
}
}
if (!(tmrService == null || typeof tmrService === "undefined")) {
DebugMsg("Set-up, interval: " + intInterval + "ms");
}
}
function sendMsg() {
if (Connected() != true) {
return;
}
webSocket.send("Hello World");
}
//Function called when service request timer expires, no arguments
function ServiceRequests() {
if (Connected() != true) {
return;
}
objServiceInfo[cstrServiceInfoTagRequests]++;
var intServiceReqRespDiff = objServiceInfo[cstrServiceInfoTagRequests]
- objServiceInfo[cstrServiceInfoTagResponses];
if (intServiceReqRespDiff >= cintServiceNoResponses
&& intServiceInterval != cintServiceRequestAltInterval) {
//Stop the existing timer
clearInterval(tmrService);
//Reset request and response counters
objServiceInfo[cstrServiceInfoTagRequests] =
objServiceInfo[cstrServiceInfoTagResponses] = 0;
//Create a new timer with the slower interval
InstallServiceTimer(cintServiceRequestAltInterval);
return;
}
DebugMsg("Sending: \"" + cstrSendMsg + "\" to " + cstrServerSocketIP);
webSocket.send(cstrSendMsg);
}
I have developed a C# application which has an HTTP web-server implementation and also a web-socket server. The web-socket server is based on the TcpClient and TcpListener classes and listens for traffic on port 8080:
try {
mWebSocketServer = new TcpListener(ipWebSocketAddr, mcintWebSocketPort);
mWebSocketServer.Start();
mWebSocketThread = new Thread(() => WebSocketServer());
mWebSocketThread.Start();
} catch (Exception ex) {
FrmMain.UpdateTaskbar(ex.ToString());
}
WebSocketHandshake and WebSocketServer:
/// <summary>
/// Function <c>WebSocketHandshake</c>
/// </summary>
/// <param name="arybytRX"></param>
/// <param name="stream"></param>
private void WebSocketHandshake(byte[] arybytRX, NetworkStream stream) {
int intRxLength = arybytRX.Length, intTxLength = 1;
byte[] arybytPacket = null;
for (int intPass = 1; intPass <= 2; intPass++) {
if (intPass == 2) {
arybytPacket = new byte[intTxLength + intRxLength];
arybytPacket[0] = mcintWebSocketDataID0;
}
if (intRxLength <= mcintWebSocketLength1) {
if (intPass == 1) {
intTxLength++;
} else {
arybytPacket[1] = (byte)intRxLength;
}
} else if (intRxLength >= 0x7e && intRxLength < 0xffff) {
if (intPass == 1) {
intTxLength += 3;
} else {
arybytPacket[1] = mcintWebSocketLength2;
arybytPacket[2] = (byte)((intRxLength >> 0x08) & 0xff);//Hi-byte
arybytPacket[3] = (byte)(intRxLength & 0xff); //Lo-byte
}
} else {
if (intPass == 1) {
intTxLength += 9;
} else {
arybytPacket[1] = mcintWebSocketLength3;
arybytPacket[2] = (byte)((intRxLength >> 0x38) & 0xff);//56
arybytPacket[3] = (byte)((intRxLength >> 0x30) & 0xff);//48
arybytPacket[4] = (byte)((intRxLength >> 0x28) & 0xff);//40
arybytPacket[5] = (byte)((intRxLength >> 0x20) & 0xff);//32
arybytPacket[6] = (byte)((intRxLength >> 0x18) & 0xff);//24
arybytPacket[7] = (byte)((intRxLength >> 0x10) & 0xff);//16
arybytPacket[8] = (byte)((intRxLength >> 0x08) & 0xff);//8
arybytPacket[9] = (byte)(intRxLength & 0xff); //0
}
}
if (intPass == 2) {
for (int i = 0; i < intRxLength; i++) {
arybytPacket[intTxLength++] = arybytRX[i];
}
}
}
if (arybytPacket[0] != mcintWebSocketDataID0) {
Console.WriteLine(FrmMain.strTimeStampMsg("Cannot interpret data, mal-formed packet!"));
return;
}
int intDataLength, intDataOffset;
if (arybytPacket[1] == mcintWebSocketLength2) {
//Data length is between 126 and 65535 bytes
intDataLength = (int)arybytPacket[2] << 0x08; //Hi-byte
intDataLength += (int)arybytPacket[3]; //Lo-byte
intDataOffset = 4;
} else if (arybytPacket[1] == mcintWebSocketLength3) {
//Data length is > 65535 bytes
intDataLength = (int)arybytPacket[2] << 0x38;
intDataLength += (int)arybytPacket[3] << 0x30;
intDataLength += (int)arybytPacket[4] << 0x28;
intDataLength += (int)arybytPacket[5] << 0x20;
intDataLength += (int)arybytPacket[6] << 0x18;
intDataLength += (int)arybytPacket[7] << 0x10;
intDataLength += (int)arybytPacket[8] << 0x08;
intDataLength += (int)arybytPacket[9];
intDataOffset = 10;
} else {
//Data length must be <= 125 bytes
intDataLength = (int)arybytPacket[1];
intDataOffset = 2;
}
string strRX = Encoding.ASCII.GetString(arybytPacket, intDataOffset
, intTxLength - intDataOffset);
string[] arystrRX = strRX.Split('\n');
Console.WriteLine(string.Format("Received: [{0}]", strRX));//Just log to console whats received
string strWebSocketAccept = null;
bool blnWebsocket = false;
foreach (string strLine in arystrRX) {
int intColon = strLine.IndexOf(":");
if (intColon == -1) {
//Shouldn't get here!
continue;
}
string strValue = strLine.Substring(intColon + 1).Trim();
if (strValue.Length == 0) {
continue;
}
string strParam = strLine.Substring(0, intColon).Trim();
if (strParam.CompareTo(mcstrHdrUpgrade) == 0) {
if (strValue.CompareTo(mcstrWebsocket) == 0) {
blnWebsocket = true;
}
} else if (strParam.CompareTo(mcstrHdrWebSocketKey) == 0) {
string strKey = strValue + mcstrWebSocketMagicString;
byte[] arybytKey = Encoding.UTF8.GetBytes(strKey);
string strSHA1 = Encoding.ASCII.GetString(arybytKey, 0, arybytKey.Length);
byte[] arybytAccept = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(strSHA1));
strWebSocketAccept = Convert.ToBase64String(arybytAccept);
break;
} else if (strParam.CompareTo(mcstrHdrWebSocketVersion) == 0) {
mintWebSocketVersion = 0;
int.TryParse(strValue, out mintWebSocketVersion);
if (mintWebSocketVersion < mcintMinWebSocketVersion) {
throw new Exception(string.Format("WebSocket ({0}) version not supported!"
, mintWebSocketVersion));
}
}
}
if (blnWebsocket != true) {
return;
}
Console.WriteLine(FrmMain.strTimeStampMsg("Received from web-client:"));
Console.WriteLine(strRX);
if (strWebSocketAccept != null) {
string strResponse = "HTTP/1.1 101 Switching Protocols"
+ CSTR.CRLF + "Upgrade: " + mcstrWebsocket
+ CSTR.CRLF + "Connection: Upgrade"
+ CSTR.CRLF + mcstrHdrWebSocketAccept + ": "
+ strWebSocketAccept
+ CSTR.CRLF + CSTR.CRLF;
Console.WriteLine("Response:");
Console.WriteLine(strResponse);
byte[] arybytResponse = Encoding.UTF8.GetBytes(strResponse);
stream.Write(arybytResponse, 0, arybytResponse.Length);
FrmMain.sobjThis.UpdateClientStats(eResponse: eCounterState.INCREMENT);
}
}
/// <summary>
/// Function <c>WebSocketServer</c>
/// </summary>
public void WebSocketServer() {
Byte[] arybytData = new byte[mcintWebsocketReceiveBufferSize];
while (mblnRunTillStopped == true) {
try {
TcpClient tcpClient = mWebSocketServer.AcceptTcpClient();
if (tcpClient.Connected != true) {
continue;
}
tcpClient.ReceiveTimeout = mcintReceiveTimeout;
IPEndPoint ipEndPoint = tcpClient.Client.RemoteEndPoint as IPEndPoint;
IPAddress ipClient = ipEndPoint.Address;
FrmMain.sobjThis.AddSubscriber(ipClient);
Console.WriteLine(FrmMain.strTimeStampMsg("WebSocketServer(): client connected"));
NetworkStream stream = tcpClient.GetStream();
while (true) {
Thread.Sleep(mcintWebSocketServerThreadSleep);
if (mblnRunTillStopped != true) {
break;
}
int intRxLength = stream.Read(arybytData, 0, tcpClient.Available);
if (intRxLength == 0) {
break;
}
string strRX = Encoding.ASCII.GetString(arybytData);
FrmMain.sobjThis.UpdateClientStats(eRequest: eCounterState.INCREMENT);
if (Regex.IsMatch(strRX, "^GET") == true) {
WebSocketHandshake(arybytData, stream);
//First byte should be '129'
} else if (arybytData[0] == 0x81) {
//Is data masked?
bool blnFinished = ((arybytData[0] & 0x01) == 0x01);
bool blnMsgRecvd = ((arybytData[0] & 0x80) == 0x80);
byte bytMask = (byte)(arybytData[1] & 0x01);
ulong ulngMsgLength = (ulong)(arybytData[1] & 0x7f)
,ulngOffset = mcintWebSocketDataStartIdx;
if (ulngMsgLength == 0x7e) {
//Bytes are reverseved, websocket will print them in big-endian
ulngMsgLength = BitConverter.ToUInt16(new byte[] {
arybytData[3], arybytData[2]}, 0);
ulngOffset += 2;
} else if (ulngMsgLength == 0x7f) {
ulngMsgLength = BitConverter.ToUInt64(new byte[] {
arybytData[9], arybytData[8], arybytData[7]
,arybytData[6], arybytData[5], arybytData[4]
,arybytData[3], arybytData[2] }, 0);
ulngOffset += 8;
}
if (ulngMsgLength > 0 && bytMask > 0) {
byte[] arybytDecoded = new byte[ulngMsgLength];
byte[] arybytMasks = new byte[4] {arybytData[ulngOffset++]
,arybytData[ulngOffset++]
,arybytData[ulngOffset++]
,arybytData[ulngOffset++]};
for (ulong i=0; i<ulngMsgLength; i++) {
arybytDecoded[i] = (byte)(arybytData[ulngOffset + i] ^ arybytMasks[i % 4]);
}
string strDecoded = Encoding.UTF8.GetString(arybytDecoded);
string[] arystrDecoded = strDecoded.Split('=');
if (arystrDecoded.Length == 2) {
if (arystrDecoded[0].CompareTo(mcstrParamMsg) == 0) {
MessageParamHandler(ipClient, arystrDecoded[1]);
}
if (stream.CanWrite == true) {
byte[] arybytAck = new byte[] { (byte)'a'
, (byte)'c'
, (byte)'k' };
stream.Write(arybytAck, 0, arybytAck.Length);
stream.Flush();
}
FrmMain.sobjThis.UpdateClientStats(eResponse: eCounterState.INCREMENT);
Console.WriteLine(string.Format("Sending ack to client {0}", ipClient));
}
}
} else {
Console.WriteLine("Bad response");
}
}
} catch (Exception ex) {
FrmMain.UpdateTaskbar(ex.Message);
}
}
}
The above works fine in that the handshaking is good, but I do not receive any messages on either the client or the server, I’ve spent hours searching for a solution and found nothing except lots of examples calling send
as I do, but I don’t see anything received.
Can anyone help?