当前位置: 首页 > 知识库问答 >
问题:

如何从头实现EventSource(SSE)服务器端?

佟涵畅
2023-03-14

我正在尝试实现一个简单的、最基本的微型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)并允许客户端向我发送请求,并且只有在该服务器可以发送更新(头+数据)之后。这,有点,有点起作用了:

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

在我在网上看到的php和Node.js示例中,在我看来,它们似乎不知何故地连续发送数据,而不需要等待client。也许他们使用了某种缓冲区或内存映射什么的?

共有1个答案

於宾白
2023-03-14

所以,很明显,我做的一切都是正确的,除了关于如何正确发送更新的极小的没有文档记录的细节(至少我没有找到任何关于它的细节)。

首先将标题更改为:

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-lengty:

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

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

 类似资料:
  • 我目前正在实现soap服务并且我需要从另一个soap服务(服务A)调用一个soap服务(服务B)。从服务A中,如何将输出SOAP请求的HTTP头而不是SOAP头设置到服务B。 目前我正在使用JaxWsDynamicClientFactory,

  • 问题内容: 我一直在开发一个nodejs服务器,以便为我正在使用HTML5开发的新网站提供服务器端事件。 当我通过telnet到服务器时,它可以正常工作,向我发送所需的HTTP响应标头,然后再发送一系列事件,这些事件我目前每2或3秒生成一次,以证明其有效。 我尝试了最新版本的FireFox,Chrome和Opera,它们创建了EventSource对象并连接到nodejs服务器,但没有一个浏览器会

  • 在之前的几篇教程中,我们讲的是如何查询和Mutation操作,这些都是在客户端那边所进行的,那么服务器这边是如何处理这些请求的呢?这就是这篇教程所要说的东西了. 准备工作 克隆库: git clone https://github.com/zhouyuexie/learn-graphql 安装依赖: cd learn-graphql && npm install cd learn-graphql

  • 问题内容: 双方的WebSockets和服务器发送的事件能够将数据推送到浏览器。在我看来,它们似乎是竞争技术。它们之间有什么区别?您何时会选择一个? 问题答案: Websocket和SSE(服务器发送事件)都能够将数据推送到浏览器,但是它们不是竞争技术。 Websockets连接既可以将数据发送到浏览器,也可以从浏览器接收数据。可以使用websockets的应用程序的一个很好的例子是聊天应用程序。

  • 我想要实现的是: user1向服务器发送消息(一个简单的POST请求) 服务器部分是(events.php) 我在(true)时尝试了无限循环以避免3秒的轮询。我也尝试过没有那个无限循环。只有发送消息的用户收到事件。 我理解这种做法并不好。但是,将SSE用于聊天应用程序的最佳实践是什么? 如何向所有用户发送事件?

  • 我在Apache CXF中实现了web服务。有没有办法使用AddressingProperties将SOAP头设置为请求(服务器端)? 这对我有用: 但我想使用org。阿帕奇。cxf。ws。寻址。AddressingProperties-类似这样的内容: 我如何将其传递给请求?我无法通过MessageContext进行设置