Reputation: 2629
After fighting all morning, I got the handshake to work but now I'm stuck actually sending and receiving messages. I've been searching for answers to no avail so thought I'd finally step up here and ask :/
My client so far is very simple:
function testWebSocket() {
if (!window.WebSocket) {
alert('WebSockets are NOT supported by your browser.');
return;
}
try {
var ws = new WebSocket('ws://localhost:8181/websession');
ws.onopen = function () {
alert('Handshake successfully established. Ready for data...');
};
ws.onmessage = function (e) {
alert('Got WebSockets message: ' + e.data);
}
ws.onclose = function () {
alert('Connection closed.');
};
}
catch (e) {
alert(e);
}
}
yes, I've done a lot of code borrowing for this...I'm just trying to get a proof of concept working with a simple "chat application"
my server is composed mostly of two classes, the SocketServer.cs and the SocketClient.cs
they follow:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
namespace WebSocketServer.Entities
{
class SocketServer
{
public static Form1 parentForm;
TcpListener socketServer;
public static List<SocketClient> ClientList = new List<SocketClient>();
public SocketServer(Form1 pForm)
{
parentForm = pForm;
parentForm.ApplyText("Socket Class Initiated\r\n");
socketServer = new TcpListener(IPAddress.Any, 8181);
// tell the console that it's started
parentForm.ApplyText("Socket Server Started\r\n");
// create continuous loops to listen for new connections
// start the listener
socketServer.Start();
while (true)
{
// check for any incoming pending connections
// create new socket client for new connection
TcpClient socketConnection = socketServer.AcceptTcpClient();
DateTime now = DateTime.Now;
//write message to console to indicate new connection
parentForm.ApplyText("New Client Connected - " + now.ToString("MM/dd/yyyy h:mm:ss tt") + "\r\n");
// create new client object for this connection
SocketClient socketClient = new SocketClient(socketConnection, parentForm);
}
}
public static void CloseClient(SocketClient whichClient)
{
ClientList.Remove(whichClient);
whichClient.Client.Close();
// dispose of the client object
whichClient.Dispose();
whichClient = null;
parentForm.ApplyText("Client Disconnected\r\n");
}
public static void SendTextToClient(SocketClient sc, string text)
{
StreamWriter writer = new StreamWriter(sc.Client.GetStream());
// check if client is still connected, then send the text string
try
{
if (sc.Client.Connected)
{
writer.WriteLine(text);
writer.Flush();
writer = null;
}
}
catch
{
CloseClient(sc);
}
}
public static void SendBroadcast(string text)
{
StreamWriter writer;
// loop through the array and send text to all clients
foreach (SocketClient client in ClientList)
{
if (client.Client.Connected)
{
try
{
writer = new StreamWriter(client.Client.GetStream());
writer.WriteLine(text);
writer.Flush();
writer = null;
}
catch
{
CloseClient(client);
}
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Security.Cryptography;
namespace WebSocketServer.Entities
{
class SocketClient
{
public TcpClient Client;
StreamReader reader;
StreamWriter writer;
Form1 parentForm;
public SocketClient(TcpClient client, Form1 pForm)
{
parentForm = pForm;
Client = client;
Thread clientThread = new Thread(new ThreadStart(StartClient));
clientThread.Start();
}
private void StartClient()
{
SocketServer.ClientList.Add(this);
// create a reader for this client
reader = new StreamReader(Client.GetStream());
// create a writer for this client
writer = new StreamWriter(Client.GetStream());
var headers = new Dictionary<string, string>();
string line = "";
while ((line = reader.ReadLine()) != string.Empty)
{
if (!string.IsNullOrEmpty(line))
{
var tokens = line.Split(new char[] { ':' }, 2);
if (!string.IsNullOrWhiteSpace(line) && tokens.Length > 1)
{
headers[tokens[0]] = tokens[1].Trim();
}
}
}
String secWebSocketAccept = ComputeWebSocketHandshakeSecurityHash09(headers["Sec-WebSocket-Key"]);
// send handshake to this client only
writer.WriteLine("HTTP/1.1 101 Web Socket Protocol Handshake");
writer.WriteLine("Upgrade: WebSocket");
writer.WriteLine("Connection: Upgrade");
writer.WriteLine("WebSocket-Origin: http://localhost:63422/");
writer.WriteLine("WebSocket-Location: ws://localhost:8181/websession");
writer.WriteLine("Sec-WebSocket-Accept: " + secWebSocketAccept);
writer.WriteLine("");
writer.Flush();
SocketServer.SendBroadcast("New Client Connected");
Thread clientRun = new Thread(new ThreadStart(RunClient));
clientRun.Start();
}
public static String ComputeWebSocketHandshakeSecurityHash09(String secWebSocketKey)
{
const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
String secWebSocketAccept = String.Empty;
// 1. Combine the request Sec-WebSocket-Key with magic key.
String ret = secWebSocketKey + MagicKEY;
// 2. Compute the SHA1 hash
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));
// 3. Base64 encode the hash
secWebSocketAccept = Convert.ToBase64String(sha1Hash);
return secWebSocketAccept;
}
private void RunClient()
{
try
{
string line = "";
while (true)
{
line = reader.ReadLine();
if (!string.IsNullOrEmpty(line))
{
parentForm.ApplyText(line + "\r\n");
SocketServer.SendBroadcast(line);
}
}
}
catch
{
parentForm.ApplyText("Client Disconnected\r\n");
SocketServer.CloseClient(this);
}
}
public void Dispose()
{
System.GC.SuppressFinalize(this);
}
}
}
I can connect with multiple instances in Chrome and my server shows all of the clients connecting and I'm seeing the alert that the handshake was successful. But when I try sending text from the client (code not shown above, but it's a pretty straightforward ws.send(text) kinda thing), it comes across to the server as garbled text. When I try and do a writer.WriteLine("whatever") from the server to the client, the onmessage event never fires. I've done a LOT of looking around after I finally got the handshake sorted out, and can't find any good examples of how to resolve this.
should I not be using a StreamWriter? Am I missing something else in my handshake (a protocol maybe).
TIA for looking and helping.
Edit:
The below code works but I don't know how to modify it to allow for dynamic sized text lengths. I'm ok with just being able to send text that is 127 or less at this point, but I can't seem to get a handle on how to even get past 4.
public static void SendBroadcast(string text)
{
StreamWriter writer;
// loop through the array and send text to all clients
foreach (SocketClient client in ClientList)
{
if (client.Client.Connected)
{
try
{
NetworkStream l_Stream = client.Client.GetStream();
List<byte> lb = new List<byte>();
lb.Add(0x81);
lb.Add(0x04);
lb.AddRange(Encoding.UTF8.GetBytes("test"));
l_Stream.Write(lb.ToArray(), 0, 6);
}
catch
{
CloseClient(client);
}
}
}
}
I've tried modifying lb.Add(0x04) to lb.Add(0x07) and sending "testing" no dice. I'm also confused as to what the l_Stream.Write() parameters are. I know it's the byte array, offset, and size but size of what?
Upvotes: 4
Views: 12911
Reputation: 133
I had the same problem, i found the solution:
lb = new List<byte>();
lb.Add(0x81);
size = message.Length;//get the message's size
lb.Add((byte)size); //get the size in bytes
lb.AddRange(Encoding.UTF8.GetBytes(message));
stream.Write(lb.ToArray(), 0, size+2); //I do size+2 because we have 2 bytes plus 0x81 and (byte)size
With this solution you can send a larger message but only < 127 characters
note: excuse me but my english is not so good. ^^
Upvotes: 2
Reputation: 42165
Messages aren't sent as plain text in recent versions of the spec. See the data framing section for details.
This wiki post is also very helpful.
I've also written a C++ server; the WsProtocol80
class shows how to read/write data.
EDIT: In your sample sending code, the 0x04 byte specifies a 4-byte message. You can set different values and send messages up to 125 bytes in this way. When you change the message length, you'd also have to update the last parameter to l_Stream.Write (which specifies the number of bytes to write). Changing it to lb.Count in all cases would seem better.
If you're still finding bitwise operations confusing and later want to send longer messages or read messages from the client, the link above to a wiki post includes pseudocode which should be helpful.
Upvotes: 4