1. beast网络库搭建http服务器 1.1 简介 通过asio来实现http服务器也需要严格服从http报文头的格式,其实http报文头的格式就是为了避免我们之前提到的粘包现象,告诉服务器一个数据包的开始和结尾,并在包头里标识请求的类型如get或post等信息。一个标准的HTTP报文头通常由请求头 和响应头 两部分组成。
http请求头格式:
1 2 3 4 5 6 GET /index.html HTTP/1.1 Host: www.example.com Accept: text/html, application/xhtml+xml User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64; rv:123.0 ) Gecko/20100101 Firefox/123.0 Cookie: sessionid=abcdefg1234567 Connection: keep-alive
http响应头格式:
1 2 3 4 5 6 HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Content-Length: 1024 Set-Cookie: sessionid=abcdefg1234567; HttpOnly; Path=/ Server: Apache/2.2 .32 (Unix) mod_ssl/2.2 .32 OpenSSL/1.0 .1 e-fips mod_bwlimited/1.4 Connection: keep-alive
1.2 http服务器实现 首先需要重新定义boost库的命名空间,并且创建了两个函数,方便后面对程序进行检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 namespace beast = boost::beast;namespace http = beast::http;namespace net = boost::asio;using tcp = boost::asio::ip::tcp;namespace my_program_state { std::size_t request_count () { static std::size_t count = 0 ; return ++count; } std::time_t now () { return std::time (0 ); } }
主函数:负责初始化ip和端口的初始化,启动上下文服务轮询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int main () { try { auto const address = net::ip::make_address ("127.0.0.1" ); unsigned short port = static_cast <unsigned short >(8080 ); net::io_context ioc{ 1 }; tcp::acceptor acceptor{ ioc,{address,port} }; tcp::socket socket{ ioc }; http_server (acceptor, socket); ioc.run (); } catch (std::exception& e) { std::cerr << "Error:" << e.what () << std::endl; return EXIT_FAILURE; } }
http_server函数实现:http_server中添加了异步接收连接的逻辑,当有新的连接到来时创建http_connection
类型的智能指针,并且启动服务,新连接监听对端接收和发送数据。然后http_server继续监听对端的新连接。
1 2 3 4 5 6 7 8 void http_server (tcp::acceptor& acceptor, tcp::socket& socket) { acceptor.async_accept (socket, [&](boost::system::error_code ec) { if (!ec) { std::make_shared <http_connection>(std::move (socket))->start (); } http_server (acceptor, socket); }); }
http_connection连接类的实现:负责读取对端发来数据的请求头,并且设置响应头,通过异步方式发送回给对端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 class http_connection :public std::enable_shared_from_this<http_connection> {public : http_connection (tcp::socket socket):socket_ (std::move (socket)) { } void start () { read_request (); check_deadline (); } private : tcp::socket socket_; beast::flat_buffer buffer_{ 8192 }; http::request<http::dynamic_body>request_; http::response<http::dynamic_body>response_; net::steady_timer deadline_{ socket_.get_executor (), std::chrono::seconds (60 ) }; void read_request () { auto self = shared_from_this (); http::async_read (socket_, buffer_, request_, [self](beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused (bytes_transferred); if (!ec) { self->process_request (); } }); } void check_deadline () { auto self = shared_from_this (); deadline_.async_wait ([self](boost::system::error_code ec) { if (!ec) { self->socket_.close (ec); } }); } void process_request () { response_.version (request_.version ()); response_.keep_alive (false ); switch (request_.method ()) { case http::verb::get: response_.result (http::status::ok); response_.set (http::field::server, "Beast" ); create_response (); break ; case http::verb::post: response_.result (http::status::ok); response_.set (http::field::server, "Beast" ); create_post_response (); break ; default : response_.result (http::status::bad_request); response_.set (http::field::content_type, "text/plain" ); beast::ostream (response_.body ()) << "Invalid request-method'" << std::string (request_.method_string ()) << "'" ; break ; } write_response (); } void create_response () { if (request_.target () == "/count" ) { response_.set (http::field::content_type, "text/html" ); beast::ostream (response_.body ()) << "<html>\n" << "<head><title>Request count </title></head>\n" << "<body>\n" << "<h1>Request count</h1>\n" << "<p>There have been " << my_program_state::request_count () << "requests so far.</p>\n" << "</body>\n" << "</html>\n" ; } else if (request_.target () == "/time" ) { response_.set (http::field::content_type, "text/html" ); beast::ostream (response_.body ()) << "<html>\n" << "<head><title>Cyrrent time </title></head>\n" << "<body>\n" << "<h1>Request count</h1>\n" << "<p>The current time is " << my_program_state::now () << "seconds since the epoch..</p>\n" << "</body>\n" << "</html>\n" ; } else { response_.result (http::status::not_found); response_.set (http::field::content_type, "text/plain" ); beast::ostream (response_.body ()) << "File not found\r\n" ; } } void write_response () { auto self = shared_from_this (); response_.content_length (response_.body ().size ()); http::async_write (socket_, response_, [self](beast::error_code ec, std::size_t ) { self->socket_.shutdown (tcp::socket::shutdown_send); self->deadline_.cancel (); }); } void create_post_response () { if (request_.target () == "/email" ) { auto & body = this ->request_.body (); auto body_str = boost::beast::buffers_to_string (body.data ()); std::cout << "receive body is " << body_str << std::endl; this ->response_.set (http::field::content_type, "text/json" ); Json::Value root; Json::Reader reader; Json::Value src_root; bool parse_success = reader.parse (body_str, src_root); if (!parse_success) { std::cout << "Failed to parse Json data" << std::endl; root["error" ] = 1001 ; std::string jsonstr = root.toStyledString (); beast::ostream (response_.body ()) << jsonstr; return ; } auto email = src_root["email" ].asString (); std::cout << "email is " << email << std::endl; root["error" ] = 0 ; root["email" ] = src_root["email" ]; root["mag" ] = "receive email post success" ; std::string jsonstr = root.toStyledString (); beast::ostream (response_.body ()) << jsonstr; } else { response_.result (http::status::not_found); response_.set (http::field::content_type, "text/plain" ); beast::ostream (response_.body ()) << "File not found\r\n" ; } } };
最后启动http的服务器,如果本地浏览器输入127.0.0.1:8080/count
,进入界面后不断刷新,就可以看到严格计数的效果;如果在本地浏览器输入127.0.0.1:8080/time
,也可以获得当前的时间戳;对于实现的post类型请求,需要结合软件完成检测。
2. beast网络库实现websocket服务器 2.1 简介 对于如何使用Beast库实现一个WebSocket服务器,以及如何处理不同类型的请求。具体来说,这句话包含了以下几个要点:
WebSocket协议和HTTP协议的关系 :WebSocket是一种长连接协议,允许服务器和客户端之间进行双向通信。虽然它是在HTTP协议之上建立的,但它在建立连接后升级为WebSocket协议。
请求的区分 :当在浏览器中输入一个以ws://
开头的URL(例如ws://127.0.0.1:9501
),浏览器会发起一个WebSocket请求,目标是本地服务器的9501端口。
Beast库的作用 :Beast库提供了处理WebSocket协议的功能。它允许我们在一个HTTP服务器的基础上,通过协议升级的方式来处理WebSocket请求。
请求处理逻辑 :当服务器收到一个请求时,需要判断该请求是普通的HTTP请求还是WebSocket请求。如果是WebSocket请求,服务器将升级协议并处理该请求;如果是普通的HTTP请求,则按HTTP请求处理。
2.2 websocket服务器实现 主函数实现:负责初始化工作任务需要的内容,并启动上下文服务。
1 2 3 4 5 6 7 int main () { net::io_context ioc; WebSocketServer server (ioc, 10086 ) ; server.StartAccept (); ioc.run (); }
管理类ConnectionMgr实现:在类中创建了一个无序的map容器_map_cons
,key值是转化为字符串的uuid值,value值是对应的Connection类型的智能指针。并且定义了两个函数,对容器_map_cons
对连接进行加入和删除操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class ConnectionMgr { public : static ConnectionMgr& GetInstance () ; void AddConnection (std::shared_ptr<Connection>conptr) ; void RmvConnection (std::string) ; private : ConnectionMgr (const ConnectionMgr&) = delete ; ConnectionMgr& operator =(const ConnectionMgr&) = delete ; ConnectionMgr (); boost::unordered_map<std::string, std::shared_ptr<Connection>>_map_cons; }; ConnectionMgr& ConnectionMgr::GetInstance () { static ConnectionMgr instance; return instance; } void ConnectionMgr::AddConnection (std::shared_ptr<Connection>conptr) { _map_cons[conptr->GetUid ()] = conptr; } void ConnectionMgr::RmvConnection (std::string id) { _map_cons.erase (id); } ConnectionMgr::ConnectionMgr () { }
WebSocketServer类实现:负责不断监听对端的连接,当与对端建立连接后,就调用Connection类的AsyncAccept函数,将协议升级为了websocket。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class WebSocketServer { public : WebSocketServer (const WebSocketServer&) = delete ; WebSocketServer& operator = (const WebSocketServer&) = delete ; WebSocketServer (net::io_context& ioc, unsigned short port); void StartAccept () ; private : net::ip::tcp::acceptor _acceptor; net::io_context& _ioc; }; WebSocketServer::WebSocketServer (net::io_context& ioc, unsigned short port) :_ioc(ioc), _acceptor(ioc, net::ip::tcp::endpoint (net::ip::tcp::v4 (), port)) { std::cout << "Server start on port: " << port << std::endl; } void WebSocketServer::StartAccept () { auto con_ptr = std::make_shared <Connection>(_ioc); _acceptor.async_accept (con_ptr->GetSocket (), [this , con_ptr](error_code err) { try { if (!err) { con_ptr->AsyncAccept (); } else { std::cout << "acceptor async_accept failed, err is " << err.what () << std::endl; } StartAccept (); } catch (std::exception& exp) { std::cout << "async_accept error is " << exp.what () << std::endl; } }); }
Connection类实现:负责将协议生成websocketm,当与对端建立成功后,异步接收数据和异步发送数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 class Connection :public std::enable_shared_from_this<Connection> { public : Connection (net::io_context& ioc); std::string GetUid () ; net::ip::tcp::socket& GetSocket () ; void AsyncAccept () ; void Start () ; void AsyncSend (std::string msg) ; private : std::unique_ptr<stream<tcp_stream>>_ws_ptr; std::string _uuid; net::io_context& _ioc; flat_buffer _recv_buffer; std::queue<std::string>_send_que; std::mutex _send_mtx; }; Connection::Connection (net::io_context& ioc):_ioc(ioc), _ws_ptr(std::make_unique<stream<tcp_stream>>(make_strand (ioc))) { boost::uuids::random_generator generator; boost::uuids::uuid uuid = generator (); _uuid = boost::uuids::to_string (uuid); } std::string Connection::GetUid () { return _uuid; } net::ip::tcp::socket& Connection::GetSocket () { return boost::beast::get_lowest_layer (*_ws_ptr).socket (); } void Connection::AsyncAccept () { auto self = shared_from_this (); _ws_ptr->async_accept ([self](boost::system::error_code err) { try { if (!err) { ConnectionMgr::GetInstance ().AddConnection (self); self->Start (); } else { std::cout << "websocket accept failed,err is" << err.what () << std::endl; } } catch (std::exception& exp) { std::cout << "websocket async accept exception is " << exp.what (); } }); } void Connection::Start () { auto self = shared_from_this (); _ws_ptr->async_read (_recv_buffer, [self](error_code err, std::size_t buffer_bytes) { try { if (err) { std::cout << "websocket async read error is " << err.what () << std::endl; ConnectionMgr::GetInstance ().RmvConnection (self->GetUid ()); return ; } self->_ws_ptr->text (self->_ws_ptr->got_text ()); std::string recv_data = boost::beast::buffers_to_string (self->_recv_buffer.data ()); self->_recv_buffer.consume (self->_recv_buffer.size ()); std::cout << "websocket recvive msg is " << recv_data << std::endl; self->AsyncSend (std::move (recv_data)); self->Start (); } catch (std::exception& exp) { std::cout << "exception is " << exp.what () << std::endl; ConnectionMgr::GetInstance ().RmvConnection (self->GetUid ()); } }); } void Connection::AsyncSend (std::string msg) { { std::lock_guard<std::mutex>lck_guard (_send_mtx); int que_len = _send_que.size (); _send_que.push (msg); if (que_len > 0 ) { return ; } } auto self = shared_from_this (); _ws_ptr->async_write (boost::asio::buffer (msg.c_str (), msg.length ()), [self](error_code err, std::size_t nsize) { try { if (err) { std::cout << "async_send err is " << err.what () << std::endl; ConnectionMgr::GetInstance ().RmvConnection (self->GetUid ()); return ; } std::string send_msg; { std::lock_guard<std::mutex>lck_guard (self->_send_mtx); self->_send_que.pop (); if (self->_send_que.empty ()) { return ; } send_msg = self->_send_que.front (); } self->AsyncSend (std::move (send_msg)); } catch (std::exception& exp) { std::cout << "async_send exception is " << err.what () << std::endl; ConnectionMgr::GetInstance ().RmvConnection (self->_uuid); } }); }
2.3 执行效果 1.启动服务器后,会跳出如下页面,这也表示服务器开始监听对端发来的连接
2.在WebSocket的一个在线测试网站进行测试
3.点击发送,接收服务器发送来的信息
4.服务器终端情况
3. windows配置和使用grpc 3.1 简介 当在windows环境配置好grpc,接下来是使用visual studio配置grpc,可以将之前编译好的库配置到项目中来完成grpc通信。
3.2 项目配置 1.创建Grpc-Server项目,在项目的根目录下创建一个名字为demo.proto
的文件,编写程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 syntax = "proto3" ; package hello;service Greeter { rpc SayHello(HelloRequest) returns (HelloReply) {} } message HelloRequest { string message = 1 ; } message HelloReply { string message = 1 ; }
2.在demo.proto
所在文件打开Powershell窗口(终端好像也可),然后利用grpc编译后生成的proc.exe
生成proto的头文件和源文件,即执行如下命令生成demo.grpc.pb.h
和demo.grpc.pb.cc
文件(这两个文件是为grpc服务的)。
1 C:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe -I ="." --grpc_out ="." --plugin =protoc-gen-grpc ="C:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe" "demo.proto"
命令解释:
C:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe
:是存放protoc.exe所在的路径,也可以将其配置到环境变量,然后直接使用protoc.exe就行
-I="."
:指定 demo.proto
所在的路径为当前路径
--grpc_out="."
:表示生成的pb.h和pb.cc文件的输出位置为当前目录
--plugin=protoc-gen-grpc="C:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe"
:表示要用到的插件是protoc-gen-grpc
,位置在C:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe
"demo.proto"
:要编译的proto文件
3.因为要序列化数据,所以需要生成grpc类需要的pb文件,即在demo.proto所在目录下打开powershell窗口,执行如下命令,就会生成demo.pb.h和demo.pb.cc文件(这两个文件是为消息服务的)。
1 C:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe --cpp_out =. "demo.proto"
4.为项目中配置grpc库的包含目录和库目录。先配置Debug版本,方便调试。
右键vs里面的项目,选择:属性 —> c/c++ — > 常规 —> 附加包含目录 —> 编辑
点击编辑后,在弹出的窗口中添加如下文件目录(根据自己存放的文件目录添加):
1 2 3 4 5 C:\cppsoft\grpc\include C:\cppsoft\grpc\third_party\protobuf\src C:\cppsoft\grpc\third_party\abseil-cpp C:\cppsoft\grpc\third_party\address_sorting\include C:\cppsoft\grpc\third_party\re2
5.再配置库路径, 选择:链接器 —> 常规 —> 附加库目录 —> 编辑
点击编辑后,在弹出的窗口添加以下路径(根据自己存放的文件目录添加):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 C:\cppsoft\grpc\visualpro\third_party\re2\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\types\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\synchronization\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\status\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\random\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\flags\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\debugging\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\container\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\hash\Debug C:\cppsoft\grpc\visualpro\third_party\boringssl-with-bazel\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\numeric\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\time\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\base\Debug C:\cppsoft\grpc\visualpro\third_party\abseil-cpp\absl\strings\Debug C:\cppsoft\grpc\visualpro\third_party\protobuf\Debug C:\cppsoft\grpc\visualpro\third_party\zlib\Debug C:\cppsoft\grpc\visualpro\Debug C:\cppsoft\grpc\visualpro\third_party\cares\cares\lib\Debug
6.配置好库目录后,还要将要使用的库链接到项目。选择:链接器 —> 输入 —> 附加依赖项 —> 编辑
点击编辑后,在弹出的窗口添加依赖的库名字(以下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 libprotobufd.lib gpr.lib grpc.lib grpc++.lib grpc++_reflection.lib address_sorting.lib ws2_32.lib cares.lib zlibstaticd.lib upb.lib ssl.lib crypto.lib absl_bad_any_cast_impl.lib absl_bad_optional_access.lib absl_bad_variant_access.lib absl_base.lib absl_city.lib absl_civil_time.lib absl_cord.lib absl_debugging_internal.lib absl_demangle_internal.lib absl_examine_stack.lib absl_exponential_biased.lib absl_failure_signal_handler.lib absl_flags.lib absl_flags_config.lib absl_flags_internal.lib absl_flags_marshalling.lib absl_flags_parse.lib absl_flags_program_name.lib absl_flags_usage.lib absl_flags_usage_internal.lib absl_graphcycles_internal.lib absl_hash.lib absl_hashtablez_sampler.lib absl_int128.lib absl_leak_check.lib absl_leak_check_disable.lib absl_log_severity.lib absl_malloc_internal.lib absl_periodic_sampler.lib absl_random_distributions.lib absl_random_internal_distribution_test_util.lib absl_random_internal_pool_urbg.lib absl_random_internal_randen.lib absl_random_internal_randen_hwaes.lib absl_random_internal_randen_hwaes_impl.lib absl_random_internal_randen_slow.lib absl_random_internal_seed_material.lib absl_random_seed_gen_exception.lib absl_random_seed_sequences.lib absl_raw_hash_set.lib absl_raw_logging_internal.lib absl_scoped_set_env.lib absl_spinlock_wait.lib absl_stacktrace.lib absl_status.lib absl_strings.lib absl_strings_internal.lib absl_str_format_internal.lib absl_symbolize.lib absl_synchronization.lib absl_throw_delegate.lib absl_time.lib absl_time_zone.lib absl_statusor.lib re2.lib
当完成这些配置,就可以通过grpc来进行服务器和客户端的通信了。
4. grpc通信 4.1 简介 gRPC(Remote Procedure Call)是一种高性能、开源的远程过程调用(RPC)框架。它能够在不同的环境中进行跨语言的通信,并且使用HTTP/2作为其传输协议,提供诸如负载均衡、跟踪、健康检查和认证等功能。
gRPC通信过程包括服务器端和客户端的代码,其中通过协议定义文件(proto文件)来定义消息格式和服务接口。
4.2 proto文件 proto文件定义了通信协议的消息格式和服务接口。在这里的proto文件定义了一个名为hello
的包,包含一个Greeter
服务和两个消息类型HelloRequest
和HelloReply
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 syntax = "proto3" ; package hello; service Greeter{ rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest{ string message = 1 ; } message HelloReply{ string message = 1 ; }
4.3 服务器实现 服务器代码实现了proto文件中定义的Greeter
服务。服务器启动后,监听指定的端口,等待客户端的请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 using grpc::Server;using grpc::ServerBuilder;using grpc::ServerContext; using grpc::Status;using hello::HelloRequest;using hello::HelloReply;using hello::Greeter; class GreeterServicelmpl final :public Greeter::Service { ::grpc::Status SayHello (::grpc::ServerContext* ccontext, const ::hello::HelloRequest* request, ::hello::HelloReply* response) { std::string prefix ("llfc grpc server has received:" ) ; response->set_message (prefix + request->message ()); return Status::OK; } }; void RunServer () { std::string server_address ("0.0.0.0:50051" ) ; GreeterServicelmpl service; ServerBuilder builder; builder.AddListeningPort (server_address, grpc::InsecureServerCredentials ()); builder.RegisterService (&service); std::unique_ptr<Server>server (builder.BuildAndStart ()); std::cout << "Server listening on " << server_address << std::endl; server->Wait (); } int main () { RunServer (); }
补:127.0.0.1
和0.0.0.0
的区别:127.0.0.0
是本地回路地址,不能网络,如果是本地客户端请求本地服务器,是可以的,但其它客户端访问不到。
4.4 客户端实现 客户端代码创建一个与服务器通信的通道,通过这个通道,客户端可以调用服务器提供的服务(如SayHello
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 using grpc::ClientContext; using grpc::Channel; using grpc::Status;using hello::HelloReply;using hello::HelloRequest;using hello::Greeter;class FCClient {public : FCClient (std::shared_ptr<Channel>channel) :stub_ (Greeter::NewStub (channel)) { } std::string SayHello (std::string name) { ClientContext context; HelloReply reply; HelloRequest request; request.set_message (name); Status status = stub_->SayHello (&context, request, &reply); if (status.ok ()) { return reply.message (); } else { return "failure " + status.error_message (); } } private : std::unique_ptr<Greeter::Stub>stub_; }; int main () { auto channel = grpc::CreateChannel ("127.0.0.1:50051" , grpc::InsecureChannelCredentials ()); FCClient client (channel) ; std::string result = client.SayHello ("hello lxx93.online!" ); printf ("get result [%s]\n" , result.c_str ()); }
4.5 通信过程 1.定义服务接口和消息格式
在proto文件中,定义了一个名为Greeter
的服务,包含一个RPC方法SayHello
,该方法接受一个HelloRequest
消息并返回一个HelloReply
消息。
2.生成代码
使用protoc编译器从proto文件生成C++代码,包括消息类(如HelloRequest
和HelloReply
)和服务接口类(如Greeter::Service
)
3.实现服务器逻辑
在服务器端,继承生成的Greeter::Service
类,并实现SayHello
方法。这个方法接收客户端的请求(HelloRequest
),处理后返回响应(HelloReply
)。
4.启动服务器
服务器代码使用ServerBuilder
配置和启动gRPC服务器,并在指定端口上监听客户端请求。
5.客户端调用
客户端创建一个通道,通过这个通道与服务器通信。客户端创建一个Greeter::Stub
对象,这是一个客户端代理,通过它调用服务器端的SayHello
方法。
在客户端调用SayHello
方法时,客户端将HelloRequest
消息序列化并发送给服务器。服务器接收请求后,处理并返回HelloReply
消息。客户端接收响应并解码得到结果。
4.6 为什么客户端可以调用服务端的函数 客户端并不直接调用服务端的函数,而是通过gRPC框架生成的客户端存根(stub)来间接调用。客户端调用存根的SayHello
方法,gRPC框架负责处理网络通信,将请求发送到服务器,并接收服务器的响应。
步骤如下(结合上面编写的服务器和客户端):
客户端构建请求:创建并填充HelloRequest
消息。
发送请求:通过调用stub_->SayHello
将请求发送到服务器。
服务器处理请求:服务器接收请求,调用GreeterServiceImpl::SayHello
方法进行处理,并构建HelloReply
响应。
发送响应:服务器将响应发送回客户端。
客户端接收响应:客户端接收并解析HelloReply
消息,从而得到服务器处理后的结果