【问题标题】:How to implement EventSource (SSE) server side from scratch?如何从头开始实现 EventSource (SSE) 服务器端?
【发布时间】:2021-06-16 22:11:57
【问题描述】:

我正在尝试实现简单、简单的迷你 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:基本上,我只是每秒钟推送一次包含在标题中的“更新”。结果一点都不好:

浏览器仅实际接收到第一个更新,所有后续更新都将被忽略。更糟糕的是——在浏览器在 10 秒后再次发送 EventStream 数据请求后(查看 retry: 10000\n 我发送的每次按摩),服务器崩溃,没有错误消息(我仍然不知道是什么原因)。

在这之后我尝试了另一种方法:

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),并允许客户端向我发送请求,并且只有在该服务器可以发送更新(标头+数据)之后。这,有点,有点工作:

在某种程度上,是的,浏览器确实收到了所有更新并在 html 页面中正确显示。但是有些事情仍然存在……我需要 1 秒的时间间隔。当然,我可以设置retry: 1000\n,浏览器会每秒发送一次请求,一切都会“完美”地工作。但实际上并非如此。因为决定何时推送更新的不是服务器,而是执行更新的客户端。和每秒点击“刷新页面”按钮没有太大区别……

在网上看到的php和node.js示例中,在我看来,它们以某种方式连续发送数据而无需等待客户端。也许他们使用某种缓冲区或内存映射或其他东西?

【问题讨论】:

    标签: c++ http tcp eventsource


    【解决方案1】:

    所以,显然,除了关于如何正确发送更新的微小未记录(至少我没有发现任何关于它的任何内容)的细节之外,我所做的一切都是正确的。

    首先将标题更改为:

    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 的请求。您需要阅读它并以裸标题回复(重要,没有数据或其他任何内容!)。

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

    只有在此之后,您才能开始发送没有任何标题或 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);
    }
    

    这会导致浏览器正确解释事件流:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-20
      • 2016-12-03
      • 1970-01-01
      • 2023-02-17
      相关资源
      最近更新 更多