如何从头开始实现 EventSource (SSE) 服务器端?
How to implement EventSource (SSE) server side from scratch?
我正在尝试实现简单的 bare-bones 迷你 Web 服务器,它的主要任务也是唯一的任务是向客户端发送简单的 html/js 页面,然后对 real-time 进行更新它。实现传输层非常简单,但我在 server-side 实现 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 中的“更新”。结果一点都不好:
浏览器实际上只收到第一次更新,所有后续更新都被忽略。更糟糕的是——浏览器在 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)
并允许客户端向我发送请求,并且只有在该服务器可以发送更新(header + 数据)之后。这有点,有点奏效:
在某种程度上,是的,浏览器确实收到了所有更新并将其正确显示在 html 页面中。但仍有问题……我需要 1 秒的间隔。当然,我可以设置 retry: 1000\n
并且浏览器将每秒发送请求,一切都会“完美”地工作。但实际上并非如此。因为决定何时推送更新的不是服务器,而是客户端。与每秒点击“刷新页面”按钮没有太大区别...
我在网上看到的php和node.js例子中,我觉得他们不知何故连续发送数据而不等待客户端。也许他们使用某种缓冲区或内存映射之类的?
所以,显然,除了关于如何正确发送更新的微小的未记录(至少我没有找到任何相关信息)的细节之外,我所做的一切都是正确的。
先把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);
}
这导致浏览器正确解释事件流:
我正在尝试实现简单的 bare-bones 迷你 Web 服务器,它的主要任务也是唯一的任务是向客户端发送简单的 html/js 页面,然后对 real-time 进行更新它。实现传输层非常简单,但我在 server-side 实现 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 中的“更新”。结果一点都不好:
浏览器实际上只收到第一次更新,所有后续更新都被忽略。更糟糕的是——浏览器在 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)
并允许客户端向我发送请求,并且只有在该服务器可以发送更新(header + 数据)之后。这有点,有点奏效:
在某种程度上,是的,浏览器确实收到了所有更新并将其正确显示在 html 页面中。但仍有问题……我需要 1 秒的间隔。当然,我可以设置 retry: 1000\n
并且浏览器将每秒发送请求,一切都会“完美”地工作。但实际上并非如此。因为决定何时推送更新的不是服务器,而是客户端。与每秒点击“刷新页面”按钮没有太大区别...
我在网上看到的php和node.js例子中,我觉得他们不知何故连续发送数据而不等待客户端。也许他们使用某种缓冲区或内存映射之类的?
所以,显然,除了关于如何正确发送更新的微小的未记录(至少我没有找到任何相关信息)的细节之外,我所做的一切都是正确的。
先把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);
}
这导致浏览器正确解释事件流: