文件服务器
使用yalantinglibs
,几行代码
开发静态文件服务器
最近的workshop
上的一个任务
,就是实现一个文件服务器
,只要设置下载目录
之后,就可下载
目录里面的文件.
看看用yalantinglibs
怎么实现一个静态文件服务器
的吧.
coro_http::coro_http_server server(1, 9001);
server.set_static_res_dir("download", "");
server.sync_start();
在浏览器
输入http://127.0.0.1:9001/download/index.html
就能打开index.html
页了,如果下载目录
还有图片,则可通过curl
或wget
下载:
curl -O http://127.0.0.1:9001/download/test.jpg
wget http://127.0.0.1:9001/download/test.jpg
set_static_res_dir
函数有两个
参数,第一个
参数是设置
虚目录,第二个
参数设置
实际文件目录
,如果设置
为空,则当前目录
就是下载文件目录
.
如果想控制
每次往客户
发送的数据流大小
,则可通过server.set_transfer_chunked_size(chunk_size)
来设置.
小文件下载优化
如果需要高并发
下载海量小文件
,每次都打开文件
读文件流
,发送
完关闭
文件,会导致性能瓶颈
,此时可启用缓存
,在内存中缓存
小文件,之后请求文件
就省掉了打开和关闭文件
的步骤
了.
缓存文件
也是非常简单
的,一个函数
搞定.
coro_http::coro_http_server server(1, 9001);
server.set_static_res_dir("download", "");
server.set_max_size_of_cache_files(100*1204);
//缓存
server.sync_start();
set_max_size_of_cache_files
函数设置后,会把下载目录
里面所有文件大小
小于100k
的文件都缓存
到内存
,后续客户
下载文件,就直接从内存
发数据,不会走文件流
,多个连接请求
同一文件也是零拷贝
,效率很高.
因为是静态文件服务器
,如果目录添加
了新的文件
,需要重启一下服务器
.
用yalantinglibs
的coro_http::coro_http_server
实现的静态文件
服务器,不仅可做一个文件服务器
,也可做一个静态
的网站页,还是很方便
的,不妨试试.
原文
聊天室
基于websocket
的实现,服务端
使用yalantinglibs.coro_http_server
,前端页
使用html+js
实现.
一个聊天室
有以下基本功能
:
1,登录/登出
2,更新用户列表
3,发送消息
4,登录,登出和更新
用户列表
登录
时,需要填一个用户名
,填好
用户名之后,js websocket client
发起连接
,当连接
成功之后,把用户名
发送到服务端
,服务端
要把登录
用户名广播
给聊天室的其它成员
,其它成员
收到新加入
用户消息后,要更新用户列表
,把新加入
用户添加到用户列表
中.
登出
时,同样要更新列表
,把登出
的人从列表
中删掉.
发送消息功能
聊天室用户发送一条消息,需要通过服务端
广播给所有成员
.
先看看前端登录
部分的代码:
var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
var ws = new WebSocket("ws://127.0.0.1:9001/");
ws.onopen = function () {var data = "系统消息:创建连接成功";let user_info = { 'type': 'login', 'content': uname };sendMsg(user_info);
};function sendMsg(msg) {var data = JSON.stringify(msg);ws.send(data);
}
前端和服务端
创建websocket
连接之后,把消息类型和用户名
序化成一个json
串,再发送
到服务端.
服务端
如何处理登录消息
呢?
//定义`json`结构
//登录/登出的`json`结构
struct login_info_t {std::string_view type;std::string_view content;std::vector<std::string> user_list;
};
REFLECTION(login_info_t, type, content, user_list);
using logout_info_t = login_info_t;
//发送给前端的`json`结构
struct user_info_t {std::string_view type;std::string_view from;std::string_view content;
};
REFLECTION(user_info_t, type, from, content);
//收到前端消息的`json`结构
struct message_t {std::string type;std::string_view content;
};
REFLECTION(message_t, type, content);
int main() {coro_http::coro_http_server server(1, 9001);server.set_static_res_dir("", "");std::mutex mtx;std::unordered_map<intptr_t, std::string> conn_map;server.set_http_handler<cinatra::GET>("/",[&](coro_http_request &req,coro_http_response &resp) -> async_simple::coro::Lazy<void> {websocket_result result{};std::unordered_map<intptr_t, std::string> map;std::string resp_str;while (true) {result = co_await req.get_conn()->read_websocket();message_t msg{};struct_json::from_json(msg, result.data);if (msg.type == "login") {std::string user_name;{std::scoped_lock lock(mtx);if (msg.type == "login") {user_name = std::string{msg.content};conn_map.emplace((intptr_t)req.get_conn(), user_name);}map = conn_map;}if (!map.empty()) {std::vector<std::string> user_list;std::transform(map.begin(), map.end(), std::back_inserter(user_list), [](auto &kv) { return kv.second; });logout_info_t info{msg.type, user_name, std::move(user_list)};struct_json::to_json(info, resp_str);}}co_await broadcast(map, resp_str);}});server.sync_start();
}
服务端
读到websocket
消息后,先反序化json
串,得到消息类型和消息内容
,如果是login
消息则把链接指针和用户名
保存到表示聊天室
用户列表的map
中,后面更新用户列表和广播消息
都需要该map
.
更新map
之后,会把map
中所有的value
放到一个vector
中,得到一个用户名列表
,接着生成一个json
格式的消息,消息类型是login
,消息内容
是用户列表
.
最后通过broadcast
广播消息.
注意该map
需要加锁
,用户登录和登出
都需要更新,该map
.
登出
逻辑是类似的,只不过需要从map
里移除用户.
广播消息
async_simple::coro::Lazy<void> broadcast(auto &conn_map, std::string &resp_str) {for (auto &[conn_ptr, user_name] : conn_map) {auto conn = (coro_http_connection *)conn_ptr;auto ec = co_await conn->write_websocket(resp_str);if (ec) {std::cout << ec.message() << "\n";continue;}}resp_str.clear();
}
广播消息
很简单,遍历map
,发送消息
即可.这里也可用collectAll
并行发送.
处理用户发送的消息
if (msg.type == "user") {std::string from;{std::scoped_lock lock(mtx);from = conn_map.at((intptr_t)req.get_conn());map = conn_map;}user_info_t info{msg.type, from, msg.content};struct_json::to_json(info, resp_str);
}
co_await broadcast(map, resp_str);
把消息类型,谁发的消息和消息内容
等json
化然后广播
出去即可.
前端处理广播消息
ws.onmessage = function (e) {var msg = JSON.parse(e.data);var sender, user_name, name_list, change_type;switch (msg.type) {case 'user':sender = '<b style="color:green;">' + msg.from + '说: ' + '</b>';break;case 'login':case 'logout':user_name = msg.content;name_list = msg.user_list;change_type = msg.type;dealUser(user_name, change_type, name_list);return;}var data = sender + msg.content;listMsg(data);
};
function listMsg(data) {var msg_list = document.getElementById("msg_list");var msg = document.createElement("p");msg.innerHTML = data;msg_list.appendChild(msg);msg_list.scrollTop = msg_list.scrollHeight;
}
function dealUser(user_name, type, name_list) {var user_list = document.getElementById("user_list");var user_num = document.getElementById("user_num");while (user_list.hasChildNodes()) {user_list.removeChild(user_list.firstChild);}for (var index in name_list) {var user = document.createElement("p");user.innerHTML = name_list[index];user_list.appendChild(user);}user_num.innerHTML = name_list.length;user_list.scrollTop = user_list.scrollHeight;var change = type == 'login' '上线' : '下线';var data = '<b style="color:red;">系统消息</b>: ' + user_name + ' 已' + change;listMsg(data);
}
前端
收到广播消息
之后,在html
页中把发言者
的名字和消息内容
展示出来即可.
收到登录和登出
消息后更新用户列表
.
运行聊天室
先启动服务端
,然后在浏览器
中输入网址:http://127.0.0.1:9001/client.html
就能看到登录聊天室的页了.
当从浏览器
输入该网址
之后,会从服务端
下载client.html
页,而coro_http_server
已设置了静态文件目录
:
server.set_static_res_dir("", "");
可下载
当前目录下的任意静态文件
,而前端
需要下载的是client.html
文件,所以需要确保client.html
文件在当前路径下,否则会返回
一个404
的错误.
完整示例代码在yalantinglibs
里:
服务端:
前端html
yalantinglibs
使用了coro_http_server
和struct_json
,只需要数十行代码
,就实现了一个简易的聊天室服务端
.