簡體   English   中英

如何從頭開始實現 EventSource (SSE) 服務器端?

[英]How to implement EventSource (SSE) server side from scratch?

我正在嘗試實現簡單、簡單的迷你 web 服務器,它的主要和唯一任務是將簡單的 html/js 頁面發送到客戶端,然后對其進行實時更新。 實現傳輸層非常簡單,但我在服務器端實現 EventSource 時遇到了令人驚訝的困難......最初我嘗試了這種簡單的方法:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#define PORT       80

using namespace std;

string head =
"HTTP/1.1 200 OK\n\
Content-Type: text/html\n\
Content-Length: ";
string update_head =
"HTTP/1.1 200 OK\n\
Content-Type: text/event-stream\n\
Cache-Control: no-cache\n\
Content-Length: ";

string update = "retry: 10000\ndata: SERVER SAYS: ";

string response =
"<!DOCTYPE html>\
<html>\n\
<head>\n\
</head>\n\
<body>\n\
<div id=\"serverData\">Here is where the server sent data will appear</div>\n\
<script>\n\
if(typeof(EventSource)!==\"undefined\") {\n\
    var eSource = new EventSource(\"/\");\n\
    eSource.onmessage = function(event) {\n\
        document.getElementById(\"serverData\").innerHTML = event.data;\n\
    };\n\
}\n\
else {\n\
    document.getElementById(\"serverData\").innerHTML=\"Whoops! Your browser doesn't receive server-sent events.\";\n\
}\n\
</script>\n\
</body>\n\
</html>";

int serverMain()
{
    int listen_sock, new_sock;
    struct sockaddr_in addr;
    int addr_len = sizeof(addr);
    
    listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock == 0)
    {
        perror("Error creating socket");
        return 1;
    }
    
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT);
    
    memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
    
    int ret = bind(listen_sock, (struct sockaddr*)&addr, addr_len);
    if(ret < 0)
    {
        perror("Error binding socket");
        return 2;
    }
    
    ret = listen(listen_sock, 10);
    if(ret < 0)
    {
        perror("Error setting up server as listеner");
        return 3;
    }
    
    while(1)
    {
        char buff[2048] = {0};
        
        printf("Waiting for clients...\n\n");
        new_sock = accept(listen_sock, (struct sockaddr*)&addr, (socklen_t*)&addr_len);
        if(new_sock < 0)
        {
            perror("Error accepting client connection into new socket");
            return 4;
        }
        
        long bytes_read = read(new_sock, buff, 2048);
        printf("------------------Client-Request------------------\n%s\
        \n------------------Client-Request------------------\n", buff);
        
        string reply = head + to_string(response.size()) + "\n\n" + response;
        write(new_sock, reply.c_str(), reply.size());
        printf("Server response sent.\n\n");        
        
        bytes_read = read(new_sock, buff, 2048);
        printf("------------------Client-Request------------------\n%s\
        \n------------------Client-Request------------------\n", buff);         
        
        for(int i = 0; i < 60; ++i)
        {               
            sleep(1);
            string msg = update + to_string(i) + "\n\ndata: some other stufff morestuff "
            + to_string(i) + "\n\n";
            string upd = update_head + to_string(msg.size()) + "\n\n" + msg;
            write(new_sock, upd.c_str(), upd.size());
            printf("Server UPDATE %d sent.\n", i);
        }
        
        close(new_sock);
    }
    
    return 0;
}

TLDR:基本上,我只是每秒都在推送包裝在 header 中的“更新”。 結果一點都不好:

錯誤嘗試編號 1

瀏覽器實際上只收到了第一個更新,所有后續更新都被忽略了。 更糟糕的是——在瀏覽器在 10 秒后發送了另一個 EventStream 數據請求后(查看retry: 10000\n I sent with each Massage),服務器崩潰且沒有錯誤消息(我仍然不知道是什么原因)。

在此之后,我嘗試了另一種方法:

for(int i = 0; i < 60; ++i)
{
    bytes_read = read(new_sock, buff, 2048);
    printf("------------------Client-Request------------------\n%s\
    \n------------------Client-Request------------------\n", buff);
            
    string msg = update + to_string(i) + "\n\ndata: some other stufff morestuff "
    + to_string(i) + "\n\n";
    string upd = update_head + to_string(msg.size()) + "\n\n" + msg;
    write(new_sock, upd.c_str(), upd.size());
    printf("Server UPDATE %d sent.\n", i);
}

我從服務器更新循環中刪除了sleep(1)並允許客戶端向我發送請求,並且只有在該服務器可以發送更新(標頭 + 數據)之后。 這,有點,有點工作:

錯誤嘗試編號 2

在某種程度上,是的,瀏覽器確實收到了所有更新並在 html 頁面中正確顯示。 但是有些事情仍然存在……我需要 1 秒的時間間隔。 當然,我可以設置retry: 1000\n並且瀏覽器會每秒發送一次請求,一切都會“完美”地工作。 但實際上並非如此。 因為決定何時推送更新的不是服務器,而是執行更新的客戶端。 與每秒單擊“刷新頁面”按鈕沒有太大區別......

在我在網上看到的 php 和 node.js 示例中,在我看來,它們以某種方式連續發送數據而無需等待客戶端。 也許他們使用某種緩沖區或 memory 映射或其他東西?

因此,顯然,除了關於如何正確發送更新的微小未記錄(至少我沒有發現任何關於它的任何內容)的細節之外,我正在朝着正確的方向做所有事情。

首先將 header 更改為:

string update_head =
"HTTP/1.1 200 OK\n\
Content-Type: text/event-stream\n\
Cache-Control: no-cache\n\n";

不需要內容長度,現在,在發送實際的 HTML 頁面后,客戶端將發送text/event-stream請求。 您需要閱讀它並用裸露的 header 回復(重要,沒有數據或其他任何東西! )。

write(new_sock, update_head.c_str(), update_head.size());
printf("Server HEAD UPDATE sent.\n");

只有在此之后,您才能開始發送實際更新而無需任何 header 或Content-Length

for(int i = 0; i < 60; ++i)
{           
    sleep(1);
    string msg = update + to_string(i) + "\n\ndata: some other stufff morestuff "
    + to_string(i) + "\n\n";
    write(new_sock, msg.c_str(), msg.size());
    printf("Server UPDATE %d sent.\n", i);
}

這會導致瀏覽器正確解釋事件 stream:

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM