├── .gitignore ├── img └── ScreenShot.png ├── src ├── server.cpp ├── client.html ├── listener.hpp └── html_def.hpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /out/ -------------------------------------------------------------------------------- /img/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leafee98/CommonClipboard/master/img/ScreenShot.png -------------------------------------------------------------------------------- /src/server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "listener.hpp" 6 | 7 | int main(int argc, const char *argv[]) 8 | { 9 | if (argc != 4) 10 | { 11 | std::cerr << "usage: server
\n"; 12 | return EXIT_FAILURE; 13 | } 14 | 15 | const boost::asio::ip::address listen_addr = boost::asio::ip::make_address(argv[1]); 16 | const unsigned short ws_port = static_cast(std::atoi(argv[2])); 17 | const unsigned short http_port = static_cast(std::atoi(argv[3])); 18 | 19 | boost::asio::io_context ioc; 20 | boost::asio::ip::tcp::endpoint ws_endpoint(listen_addr, ws_port); 21 | boost::asio::ip::tcp::endpoint http_endpoint(listen_addr, http_port); 22 | 23 | ws_listener ws_srv(ioc, ws_endpoint); 24 | http_listener http_srv(ioc, http_endpoint); 25 | 26 | ws_srv.start(); 27 | http_srv.start(); 28 | 29 | std::cout << "server started.\n"; 30 | ioc.run(); 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common Clipboard 2 | 3 | 简单的公共剪切板, 在不同的设备上登陆网页后连接同一个设备就可以看到相同的文本, 可以方便地进行临时性的文本的跨设备传输. 4 | 5 | 主要使用场景为局域网下没得QQ, 电脑扫不了二维码, 使用Paste Ubuntu又需要输入好长一段分享链接, 但是有一段文本需要复制到电脑上的情况. 6 | 7 | ![前端截图](/img/ScreenShot.png) 8 | 9 | ## 更新日志 10 | 11 | - html部分代码已经支持保存上一次连接过的服务器到cookie, cookie有效期7天.(2020-02-25) 12 | - 由服务器主程序直接为网页提供服务(2020-06-11) 13 | 14 | ## 依赖 15 | 16 | - `boost` -- 使用了`asio`的网络部分, `beast`封装的websocket部分 17 | 18 | ## 构建 19 | 20 | 假设已经安装`boost`库, 在构建时只需要将唯一一个cpp源码文件编译即可, 注意制定特定的静态链接库来完成编译. 21 | 22 | ``` 23 | # 注意"-lthread"参数必需, 因为编译时默认不会链接此库 24 | g++ src/server.cpp -lpthread -o ./server.out 25 | ``` 26 | 27 | ## 使用 28 | 29 | ### 服务端后端 30 | 31 | 在构建完成后, 只需要运行程序并指定监听IP和websocket以及http端口即可. 32 | 33 | ``` 34 | ./server.out 0.0.0.0 1234 12345 35 | ``` 36 | 37 | ### 服务端前端(deprecated) 38 | 39 | 现前端html代码已经由主程序直接负责支持, 不再需要一个强制的http服务. 不过鉴于现有前端代码的来源情况, 仍提供一份单独前端代码供独立服务器进行使用. 40 | 41 | > 把唯一一个html文件扔到任意一个网页服务器上即可, 如果实在懒得部署, 直接在浏览器打开html文件也可以. 42 | > 43 | > 如果需要修改文本框内服务器的默认值的话, 在html文件的javaScript代码的开头部分根据注释进行修改. 44 | > 45 | > 在前端页面, 以"ws://{ip}:{port}/"格式输入地址后, 点击连接, 连接状态变为"connected"之后便可以使用"save"和"load"来保存公告板的内容或者拉取公告版的内容. 46 | 47 | ## 注意 48 | 49 | 服务端后端的保存是非持久性的, 如果重新启动后端, 保存的文本将会丢失. 50 | 51 | ## 项目起因 52 | 53 | 需要解决上面使用场景提到的问题, 顺便练习一下boost的使用. 54 | 55 | ## 许可证 56 | 57 | - 可任意使用, 不限制使用范围 58 | - 可选署名原作者, 不署名也不勉强 59 | - 原作者对于使用本项目的一切后果不负任何责任 60 | -------------------------------------------------------------------------------- /src/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | simple web common clipboard 7 | 8 | 9 | 10 | 11 |
12 |

Common clipboard

13 |
14 | 15 |
16 | Server Address: 17 | 18 | 19 |
20 | 21 |
22 | Server State: 23 | disconnected 24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 | 33 | 34 | 115 | -------------------------------------------------------------------------------- /src/listener.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "html_def.hpp" 4 | 5 | class listener 6 | { 7 | private: 8 | std::shared_ptr _acceptor; 9 | void handle_accept(boost::system::error_code ec, boost::asio::ip::tcp::socket sock); 10 | virtual void handle(boost::system::error_code ec, boost::asio::ip::tcp::socket &sock) = 0; 11 | 12 | public: 13 | listener(boost::asio::io_context &ioc, const boost::asio::ip::tcp::endpoint &endpoint); 14 | 15 | void start(); 16 | }; 17 | 18 | listener::listener(boost::asio::io_context &ioc, const boost::asio::ip::tcp::endpoint &endpoint) 19 | : _acceptor(new boost::asio::ip::tcp::acceptor(ioc, endpoint)) 20 | { 21 | } 22 | 23 | void listener::start() 24 | { 25 | this->_acceptor->async_accept(std::bind(&listener::handle_accept, this, 26 | std::placeholders::_1, 27 | std::placeholders::_2)); 28 | } 29 | 30 | void listener::handle_accept(boost::system::error_code ec, boost::asio::ip::tcp::socket sock) 31 | { 32 | this->_acceptor->async_accept(std::bind(&listener::handle_accept, this, 33 | std::placeholders::_1, 34 | std::placeholders::_2)); 35 | this->handle(ec, sock); 36 | } 37 | 38 | class ws_listener 39 | : public listener 40 | { 41 | private: 42 | boost::beast::flat_buffer content; 43 | 44 | void handle(boost::system::error_code ec, boost::asio::ip::tcp::socket &sock) override 45 | { 46 | boost::beast::websocket::stream ws(std::move(sock)); 47 | boost::beast::flat_buffer instruction; 48 | 49 | ws.accept(); 50 | 51 | std::cout << "ws_srv: new connection" << std::endl; 52 | 53 | try 54 | { 55 | while (true) 56 | { 57 | ws.read(instruction); 58 | std::string instruction_str = 59 | std::string(static_cast(instruction.data().data()), instruction.size()); 60 | instruction.consume(instruction.size()); 61 | 62 | if (instruction_str == "SAVE") 63 | { 64 | std::cout << "ws_srv: recive SAVE instruction" << std::endl; 65 | content.consume(content.size()); 66 | ws.read(content); 67 | } 68 | else if (instruction_str == "LOAD") 69 | { 70 | std::cout << "ws_srv: recive LOAD instruction" << std::endl; 71 | ws.write(content.data()); 72 | } 73 | else 74 | { 75 | std::cout << "ws_srv: unknown instruction: " << instruction_str << std::endl; 76 | } 77 | } 78 | } 79 | catch (boost::system::system_error &error) 80 | { 81 | std::cerr << "ws_srv: websocket disconnected, reason follow:" << std::endl; 82 | std::cerr << error.what() << std::endl; 83 | } 84 | } 85 | 86 | public: 87 | using listener::listener; 88 | }; 89 | 90 | class http_listener 91 | : public listener 92 | { 93 | private: 94 | void handle(boost::system::error_code ec, boost::asio::ip::tcp::socket &sock) override 95 | { 96 | try 97 | { 98 | boost::asio::mutable_buffer buffer((void *)(response_str.data()), response_str.size()); 99 | boost::asio::write(sock, buffer); 100 | std::cout << "http_srv: write http response to client" << std::endl; 101 | 102 | sock.shutdown(boost::asio::ip::tcp::socket::shutdown_both); 103 | sock.close(); 104 | } 105 | catch (const boost::system::system_error &e) 106 | { 107 | std::cerr << "http_srv: socket disconnect, reason follow:" << std::endl; 108 | std::cerr << e.what() << '\n'; 109 | } 110 | } 111 | 112 | public: 113 | using listener::listener; 114 | }; 115 | -------------------------------------------------------------------------------- /src/html_def.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | std::string html_code = R"( 4 | 5 | 6 | 7 | 8 | 9 | simple web common clipboard 10 | 11 | 12 | 13 | 14 |
15 |

Common clipboard

16 |
17 | 18 |
19 | Server Address: 20 | 21 | 22 |
23 | 24 |
25 | Server State: 26 | disconnected 27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 | 36 | 37 | 118 | )"; 119 | 120 | std::string header_code_pre = R"(HTTP/2 200 OK 121 | Content-type: text/html; charset=utf-8 122 | Connection: close 123 | Server: custom)"; 124 | 125 | std::string header_code = header_code_pre + "\nContent-Length: " + 126 | std::to_string(html_code.size()) + "\n\n"; 127 | const std::string response_str = header_code + html_code; 128 | --------------------------------------------------------------------------------