title: C# WebSockets-Sharp WebSocketSharpException: "The header of frame cannot be read from the stream"
body: <p>I have a visual C# application that connects via WebSocket to my remote Node.js server that is deployed using Heroku. </p>
  
  <p>The server uses the npm WebSocket module "ws" to create a WebSocket-Server.</p>
  
  <p>The C# client application uses the WebSocketSharp Library from this GitHub repository: <a href="https://github.com/sta/websocket-sharp" rel="nofollow noreferrer">https://github.com/sta/websocket-sharp</a> to create a WebSocket-Client that connects to the server.</p>
  
  <p>Here is the necessary Node Server server.js code:</p>
  
  <pre><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);
  
  </code></pre>
  
  <p>Here is the Node Server ApiHandler.js code</p>
  
  <pre><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) =&gt; { 
              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) =&gt; { 
                    this.handle(JSON.parse(msg),client); 
                  });
                  client.on('close', () =&gt; {
                    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=&gt;'*')).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 &amp;&amp; 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) &amp;&amp; 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 &amp;&amp; Array.isArray(req.fields) &amp;&amp; req.fields.length &gt; 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) =&gt;{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) =&gt;{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 &lt; req.categories.length; i++){ promises[i] = this.db.select(req.categories[i]); }
  
          await Promise.all(promises).then( (results) =&gt; {
              for(var i = 0; i &lt; 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) =&gt;{ resolve ({ status : this.status.ok, message : this.messages.success.read, result: res});});     
      }
      async executeInsert(req){
          for(var i = 0; i &lt; 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 &lt; 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 &lt; 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) =&gt; {
              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 &lt; 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;
  
  </code></pre>
  
  <p>Here is the necessary C# Client Application Code</p>
  
  <pre><code>public void init_Socket() {
      wsSocket = new WebSocket(Socket_URL);
      wsSocket.OnOpen += (sender, e) =&gt; {
          Console.WriteLine("Connected to server at "+Socket_URL);
      };
      wsSocket.OnClose += (sender, e) =&gt; {
          setToken(NO_TOKEN);
          Console.WriteLine("Disconnected from server! - " + e.Code.ToString());
      };
      wsSocket.OnMessage += (sender, e) =&gt; {
          if (wsSocket.ReadyState == WebSocketState.Open &amp;&amp; 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 &amp;&amp; wsSocket.ReadyState == WebSocketState.Open) {
          wsSocket.SendAsync(req.ToString(), new Action&lt;bool&gt;((bool completed) =&gt; {
              Console.WriteLine("SENDING completed!");
          })
      );
      } else{ Console.WriteLine("Socket must be alive in order to send a message.");} 
   }
  
  </code></pre>
  
  <p>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.</p>
  
  <p>When the Clients receive updated information from the server they update their local copies to match the data received from the server.</p>
  
  <p>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".</p>
  
  <p>A quick google search leads me to this GitHub issue <a href="https://github.com/sta/websocket-sharp/issues/202" rel="nofollow noreferrer">https://github.com/sta/websocket-sharp/issues/202</a>.
  From the above-mentioned link:</p>
  
  <p>"
  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:
  <code>if (nread == 0 || nread == length)</code>
  with:
  <code>if(nread == length)</code>
  fixes the problem. It makes sure that when one is waiting for example for the two frame bytes one really gets two bytes back.
  ".</p>
  
  <p>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).</p>
  
  <p>The code from the library in 2020:</p>
  
  <pre><code>public static class Ext{
   //Other functions and properties of Ext goes here
  
   internal static void ReadBytesAsync (this Stream stream,int length,Action&lt;byte[]&gt;
                        completed,Action&lt;Exception&gt; error){
     var buff = new byte[length];
     var offset = 0;
     var retry = 0;
  
     AsyncCallback callback = null;
       callback =
         ar =&gt; {
           try {
              var nread = stream.EndRead (ar);
              if (nread &lt;= 0) {
                  if (retry &lt; _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);
        }
      }
  }
  </code></pre>
  
  <p>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.</p>