Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

C# WebSockets-Sharp WebSocketSharpException: "The header of frame cannot be read from the stream"

Ask Question

I have a visual C# application that connects via WebSocket to my remote Node.js server that is deployed using Heroku.

The server uses the npm WebSocket module "ws" to create a WebSocket-Server.

The C# client application uses the WebSocketSharp Library from this GitHub repository: https://github.com/sta/websocket-sharp to create a WebSocket-Client that connects to the server.

Here is the necessary Node Server server.js code:

require('dotenv').config()
var express = require('express');
const API = require('./api_handler').api;
const PORT = process.env.PORT || 5000; 
const HOSTNAME = process.env.HOST || '127.0.0.1';
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.post('/', function(req, res){
    api.Auth(req.body,res);
app.get('/', function(req, res){
    res.send(':)');
const httpServer = app.listen(PORT, function () {
    console.log('Server running at http://' + HOSTNAME + ':' + PORT + '/');
const api = new API(httpServer);

Here is the Node Server ApiHandler.js code

class API_Handler{
  constructor(httpServer){
        const Database = require('./database').db;
        const { Server : wsServer} = require('ws');
        this.db = new Database();
        this.wss = new wsServer({ server: httpServer });
        this.actions = {write : 'write', read : 'read', authenticate : 'authenticate'};
        this.operations = {insert : 'insert', update : 'update', delete : 'delete', login : 'login', logout : 'logout'};
        this.template = {action : 'action', operation : 'operation',categories : 'categories',category : 'category',IDs : 'ids',fields : 'fields',data : 'data',userid : 'userid',password : 'password',Token : 'Token',Logged : 'logged'}; 
        this.status = {ok : 'ok', error : 'error'};     
        this.CLIENT_TOKENS = [];
        this.wsClients = new Object();
        this.ClientIDs = new Object();
        this.wss.on('connection', (client,req) => { 
            var cookie = this.CookieParseJSON(req.headers.cookie)
            if(this.CLIENT_TOKENS.includes(cookie.Token)){          
                this.CLIENT_TOKENS.splice(this.CLIENT_TOKENS.indexOf(cookie.Token), 1); 
                this.wsClients[cookie.Token] = client;
                client.on('message', (msg) => { 
                  this.handle(JSON.parse(msg),client); 
                client.on('close', () => {
                  console.log('Client disconnected');
                console.log('Client connected');
                this.InitializeClient(client);
            else{
                console.log('Unauthorized Client connected');
                client.close();
    async Auth(req,res){
        if(this.template.password in req){
            var tmp = JSON.parse(JSON.stringify(req));
            tmp.password = (Array.from({length:req.password.length}).map(x=>'*')).join('');
            console.log(tmp);
        }else{console.log(req);}
        var result;     
        if(this.template.action in req){
            if (req.action === this.actions.authenticate){
                if(this.template.operation in req){
                    if(req.operation === this.operations.login){                        
                        if(this.template.userid in req && this.template.password in req ){  
                            result = await this.executeLogin(req);
                        }else{result = this.missingAuthCredentialsResult();}
                    }else{result = this.invalidAuthOperationResult();}
                }   else{result = this.noAuthOperationResult();}
            }else if(req.operation === this.operations.read || req.operation === this.operations.write || req.operation === this.operations.logout){
                result = this.UnAuthedActionResult();
            }   else{result = this.invalidActionResult();}
        }else{result = this.noActionResult();}
        res.json(result);
  async handle(req,client){
        console.log(req);
        var result;
        if(this.template.Token in req){
            if(req.Token in this.wsClients){
                if(this.template.action in req){
                    if(req.action === this.actions.authenticate){
                        if(this.template.operation in req){
                            if(req.operation === this.operations.logout){               
                                await this.executeLogout(req);
                                client.close();return;
                            }else{result = this.invalidAuthOperationResult();}
                        }   else{result = this.noAuthOperationResult();}
                    }if (req.action === this.actions.read){
                        if(this.template.categories in req){    
                            if(this.db.validateCategories(req.categories)){
                                result = await this.executeRead(req);
                                client.send(JSON.stringify(result));return;
                            }else{result = this.invalidCategoriesResult();}
                        }else{result = this.noCategoriesResult()}
                    }else if (req.action === this.actions.write){       
                        if(this.template.category in req){
                            if(this.db.validateCategory(req.category) && this.db.isWritableCategory(req.category)){
                                if(this.template.operation in req){
                                    if(req.operation === this.operations.insert){
                                        if(this.template.data in req){
                                            await this.executeInsert(req);
                                            return;
                                        }else{result = this.noDataResult()}
                                    }else if(req.operation === this.operations.update){
                                        if(this.db.isUpdatableCategory(req.category)){
                                            if(this.template.IDs in req){
                                                if(this.template.fields in req && Array.isArray(req.fields) && req.fields.length > 0){
                                                    if(this.template.data in req){
                                                        await this.executeUpdate(req);
                                                        return;
                                                    }else{result = this.noDataResult()}
                                                }else{result = this.noFieldsResult()}
                                            }else{result = this.noIDsResult()}
                                        }else{result = this.invalidCategoryResult();}
                                    }else if(req.operation === this.operations.delete){
                                        if(this.template.IDs in req){
                                            await this.executeDelete(req);
                                            return;
                                        }else{result = this.noIDsResult()}
                                    }else{result = this.invalidOperationResult();}
                                }else{result = this.noOperationResult();}
                            }else{result = this.invalidCategoryResult();}
                        }else{result = this.noCategoryResult();}
                    }else{result = this.invalidActionResult();}
                }else{result = this.noActionResult();}
            }else{result = this.invalidTokenResult();}
        }else{result = this.noTokenResult();}
        client.send(JSON.stringify(result));
        client.close();
    async executeLogin(req){ 
        if(await this.db.authenticate(req.userid,req.password)){    //successfully logged in
            console.log("Auth Passed");
            var res = new Object();
            var token = this.hex();
            res[this.template.Token] = token;
            res[this.template.Logged] = true;
            this.CLIENT_TOKENS.push(token);
            this.ClientIDs[token] = req.userid;
            return new Promise((resolve,reject) =>{resolve ({ status : this.status.ok, message : this.messages.success.loggedIn, result: res});});  
        }else{
            console.log("Auth Failed");
            var res = new Object();
            res[this.template.Logged] = false;
            return new Promise((resolve,reject) =>{resolve ({ status : this.status.ok, message : this.messages.error.loggedIn, result: res});});    
    async executeLogout(req){ 
        this.wsClients[req.Token].close();
        delete this.wsClients[req.Token];
        delete this.ClientIDs[req.Token];
    async executeRead(req){ 
        req.categories = this.removeDuplicates(req.categories);
        var res = new Object();
        var promises = [];
        for(var i = 0; i < req.categories.length; i++){ promises[i] = this.db.select(req.categories[i]); }
        await Promise.all(promises).then( (results) => {
            for(var i = 0; i < results.length; i++){
                res[req.categories[i]] = (results[i].command === 'SELECT')?{count: results[i].rowCount, values: results[i].rows} : this.messages.error.selectCategory;
        return new Promise((resolve,reject) =>{ resolve ({ status : this.status.ok, message : this.messages.success.read, result: res});});     
    async executeInsert(req){
        for(var i = 0; i < req.data.length; i++){
            var dbResponse = await this.db.insert(req.category,req.data[i],this.ClientIDs[req.Token]);          
        this.UpdateClientData(req,(req.category === this.db.tables.Transactions || req.category === this.db.tables.ItemTypes));
    async executeUpdate(req){
        for(var i = 0; i < req.ids.length; i++){
            var dbResponse = await this.db.update(req.category,req.ids[i],req.fields,req.data[i],this.ClientIDs[req.Token]);
        this.UpdateClientData(req);
    async executeDelete(req){
        req.ids = this.removeDuplicates(req.ids);
        for(var i = 0; i < req.ids.length; i++){var dbResponse = await this.db.delete(req.category,req.ids[i],this.ClientIDs[req.Token]);}
        this.UpdateClientData(req);
    async InitializeClient(client){
        var read = await this.ReadInit();
        client.send(JSON.stringify(read));
    async ReadInit(){
        return this.executeRead({categories : this.db.tableNames});     
    async UpdateClientData(req, updateSender = false){
        var cats = [req.category];
        if(req.category === this.db.tables.ItemListings){cats.push(this.db.tables.Transactions);}
        var read = await this.ReadAll(cats);
        var readString = JSON.stringify(read);
        this.wss.clients.forEach((client) => {
            if(!updateSender || client !== this.wsClients[req.Token]){
                client.send(readString);
                console.log("REFRESH: Client updated, columns: " + cats);
            }else{
                console.log("REFRESH: Sender-Client was skipped");
    async ReadAll(cats){
        return this.executeRead({categories : cats});   
    removeDuplicates(array){return [...new Set(array)]; }
    hex(){
        return this.randHex(16);
    randHex(len) {
        var maxlen = 8;
        var min =   Math.pow(16,Math.min(len,maxlen)-1);
        var max = Math.pow(16,Math.min(len,maxlen)) - 1;
        var n   = Math.floor( Math.random() * (max-min+1) ) + min;
        var r   = n.toString(16);
        while ( r.length < len ) { r = r + this.randHex( len - maxlen ); }
        return r;
    CookieParseJSON(cookieStr){
        var sep = cookieStr.indexOf('=');
        var key = cookieStr.substr(0,sep);
        var value = cookieStr.substr(sep+1,cookieStr.length - sep -1);
        var obj = new Object();
        obj[key] = value;
        return obj;
module.exports.api = API_Handler;

Here is the necessary C# Client Application Code

public void init_Socket() {
    wsSocket = new WebSocket(Socket_URL);
    wsSocket.OnOpen += (sender, e) => {
        Console.WriteLine("Connected to server at "+Socket_URL);
    wsSocket.OnClose += (sender, e) => {
        setToken(NO_TOKEN);
        Console.WriteLine("Disconnected from server! - " + e.Code.ToString());
    wsSocket.OnMessage += (sender, e) => {
        if (wsSocket.ReadyState == WebSocketState.Open && e.IsText) {
            Console.WriteLine("Server Message: " + e.Data);
            ReadHandler handler = new ReadHandler(null);
            handler.handle(e.Data);
public void setToken(string token) {
    if(wsSocket != null) {
        wsSocket.SetCookie(new Cookie(Props.Token, token));
public void SocketConnect() {
    wsSocket.Connect();
private void SendSocketMessage(APIRequest req, APIResponseHandler handler) {
    Console.WriteLine("SOCKET MESSAGE: " + req.ToString());
    if (wsSocket.IsAlive && wsSocket.ReadyState == WebSocketState.Open) {
        wsSocket.SendAsync(req.ToString(), new Action<bool>((bool completed) => {
            Console.WriteLine("SENDING completed!");
    } else{ Console.WriteLine("Socket must be alive in order to send a message.");} 

So how it works is that when a client updates information on the server it sends the updated information to the server and the server then sends this updated information to all its clients except the sender.

When the Clients receive updated information from the server they update their local copies to match the data received from the server.

The problem is that after the client has been connected to the server via WebSocket for about a minute, the client throws a WebSocketException with the message "The header of frame cannot be read from the stream".

A quick google search leads me to this GitHub issue https://github.com/sta/websocket-sharp/issues/202. From the above-mentioned link:

We managed to fully fix it by modifying ReadBytesAsync(this Stream stream, int length, Action completed, Action error) in Ext.cs. We noticed that 'NetworkStream.EndRead' can return zero (0) bytes even when connection is not being closed. Close reading of documentation of 'EndRead' only says that zero bytes is returned when connection is closed. Other way around is not always true, receiving 0 bytes does not always mean connection is being closed it seems. It is quite common to get zero bytes returned even when the connection is not being closed. Happens once in a while with Chrome, and seems to happen more often with Firefox. Therefore replacing: if (nread == 0 || nread == length) with: if(nread == length) fixes the problem. It makes sure that when one is waiting for example for the two frame bytes one really gets two bytes back.

But on inspecting the code in the WebSocket-Sharp library the above-quoted fix has already been applied to the library(the post was from 2016 so it was probably pathed).

The code from the library in 2020:

public static class Ext{
 //Other functions and properties of Ext goes here
 internal static void ReadBytesAsync (this Stream stream,int length,Action<byte[]>
                      completed,Action<Exception> error){
   var buff = new byte[length];
   var offset = 0;
   var retry = 0;
   AsyncCallback callback = null;
     callback =
       ar => {
         try {
            var nread = stream.EndRead (ar);
            if (nread <= 0) {
                if (retry < _retry) {
                retry++;
                stream.BeginRead (buff, offset, length, callback, null);
                return;
            if (completed != null)
                completed(buff.SubArray(0, offset));
                return;
            if (nread == length) {
                if (completed != null)
                  completed (buff);
              return;
            retry = 0;
            offset += nread;
            length -= nread;
            stream.BeginRead (buff, offset, length, callback, null);
          catch (Exception ex) {
            if (error != null) {
                error(ex);
      try {
        stream.BeginRead (buff, offset, length, callback, null);
      catch (Exception ex) {
        if (error != null)
          error (ex);

Another strange thing is that when I run the server locally and connect to it this problem never occurs. It only happens when I connect to the remote copy that is deployed on Heroku.

what is the socket url you are trying to connect from C# and which protocol is it using? maybe you are using ws protocol while heroku uses wss – C.Gochev Jan 11, 2020 at 16:26 @C.Gochev When connecting locally I use the ws protocol to connect to the local server. But when I connect remotely I use the wss protocol. The url is [inventorymanagementsa.herokuapp.com/] – Phillip Schulze Jan 15, 2020 at 8:46 @AndrewsBAnthony Unfortunately, I have long since abandoned this project without finding a solution to this problem. – Phillip Schulze Jan 18, 2021 at 9:56

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.