0

我有一个可视化 C# 应用程序,它通过 WebSocket 连接到使用 Heroku 部署的远程 Node.js 服务器。

服务器使用 npm WebSocket 模块“ws”来创建一个 WebSocket-Server。

C# 客户端应用程序使用此 GitHub 存储库中的 WebSocketSharp 库:https ://github.com/sta/websocket-sharp创建连接到服务器的 WebSocket-Client。

这是必要的节点服务器 server.js 代码:

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);

这是节点服务器 ApiHandler.js 代码

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;

这是必要的 C# 客户端应用程序代码

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.");} 
 }

所以它的工作原理是,当客户端更新服务器上的信息时,它会将更新的信息发送到服务器,然后服务器将更新的信息发送给除发送者之外的所有客户端。

当客户端从服务器接收到更新的信息时,它们会更新其本地副本以匹配从服务器接收到的数据。

问题是在客户端通过 WebSocket 连接到服务器大约一分钟后,客户端抛出 WebSocketException 并显示消息“无法从流中读取帧的标头”。

快速的谷歌搜索将我带到这个 GitHub 问题https://github.com/sta/websocket-sharp/issues/202。从上述链接:

“我们通过在 Ext.cs 中修改 ReadBytesAsync(this Stream stream, int length, Action completed, Action error) 设法完全修复它。我们注意到即使没有连接,'NetworkStream.EndRead' 也可以返回零 (0) 字节关闭。仔细阅读“EndRead”的文档只说当连接关闭时返回零字节。其他方式并不总是正确的,接收 0 字节并不总是意味着连接似乎正在关闭。这很常见即使连接没有关闭,也会返回零字节。在 Chrome 中偶尔会发生一次,而在 Firefox 中似乎更常见。因此,将:替换 if (nread == 0 || nread == length) 为: if(nread == length) 修复了问题。它确保当一个人在等待时两个帧字节一个真的得到两个字节。“。

但是在检查 WebSocket-Sharp 库中的代码时,上面引用的修复程序已经应用于库(该帖子来自 2016 年,因此它可能已被路径)。

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);
      }
    }
}

另一个奇怪的事情是,当我在本地运行服务器并连接到它时,这个问题永远不会发生。只有当我连接到部署在 Heroku 上的远程副本时才会发生这种情况。

4

0 回答 0