客户端和服务器使用TCP进行长连接发送接收数据时,会出现粘包、拆包现象。因为TCP传输数据时是以流方式传输的,消息并非一包一包发送。传统的解决此问题的方式是发送端在发送数据前,先发送一个固定字节(例如4字节)的包含数据长度的消息。接收端先接收4字节数据,获取要接收的数据的长度,然后再获取该长度的数据。这就使编码流程复杂化。
websocket实现了客户端和服务器之间的TCP长链接,全双工通信。并且websocket内部的帧结构处理了粘包,拆包的问题,使我们不需要手动处理TCP层面的拆包和粘包问题。所以有客户端和服务器之间的长链接需求,可以集成现有的websocket库,简化编码流程。
下面就以c++客户端和golang服务端为例来说明。
c++客户端使用的websocket来源于libpoco库(git@github.com:pocoproject/poco.git)。
golang 服务端使用的websocket来源于(github.com/gorilla/websocket)。
服务器golang代码
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{}
type WsTransport struct {
conn *websocket.Conn
ready bool
}
func NewWsTransport() *WsTransport {
ws := &WsTransport{ready: false}
return ws
}
func (ws *WsTransport) Handle(w http.ResponseWriter, r *http.Request) {
ws.conn, _ = upgrader.Upgrade(w, r, nil)
ws.ready = true
for {
t, buff, err := ws.conn.ReadMessage()
fmt.Printf("%d, %s, %v\n", t, string(buff), err)
if err != nil {
return
}
}
}
func (ws *WsTransport) SendMessage(msg string) {
if !ws.ready {
return
}
err := ws.conn.WriteMessage(1, []byte(msg))
if err != nil {
fmt.Println(err)
return
}
}
func main() {
ws := NewWsTransport()
http.HandleFunc("/ws", ws.Handle)
go SendMessage(ws)
log.Fatal(http.ListenAndServe("localhost:8800", nil))
}
func SendMessage(ws *WsTransport) {
for {
ws.SendMessage("message from server")
time.Sleep(1 * time.Second)
}
}
客户端c++代码 (poco的头文件和库需编译和继承到项目中)
#include <iostream>
#include <set>
#include <chrono>
#include <memory>
#include <thread>
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/HTTPClientSession.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/ServerSocket.h"
using namespace std;
using namespace Poco;
class UaTransport {
public:
UaTransport(const std::string& remote_ip, uint16_t port):cs_(remote_ip, port) {
MakeWebSocket();
th_ = std::thread(&UaTransport::RecvWork, this);
}
virtual ~UaTransport(){}
int SendFrame(const std::string& payload) {
return ws_->sendFrame(payload.data(), (int)payload.size());
}
void StopRecv() {
stop_ = true;
th_.join();
}
private:
void MakeWebSocket() {
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, "/ws", Poco::Net::HTTPRequest::HTTP_1_1);
Poco::Net::HTTPResponse response;
ws_ = std::make_unique<Poco::Net::WebSocket>(cs_, request, response);
ws_->setBlocking(false);
ws_->setSendBufferSize(256 * 1024);
ws_->setReceiveBufferSize(256 * 1024);
}
void RecvWork() {
constexpr int bufsize = 256 * 1024;
auto buffer = std::make_unique<char>(bufsize);
int flags;
while (!stop_) {
ws_->poll(10000000, Poco::Net::Socket::SELECT_READ);
int n = ws_->receiveFrame(buffer.get(), bufsize, flags);
std::cout << std::string(buffer.get()) << std::endl;
}
}
private:
bool stop_{ false };
Poco::Net::HTTPClientSession cs_;
std::unique_ptr<Poco::Net::WebSocket> ws_;
std::thread th_;
};
int main(int argc, char** argv) {
std::string ip{ "127.0.0.1" };
uint16_t port = 8800;
auto tr = std::make_unique<UaTransport>(ip, port);
while (1) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string str{ "message from client" };
tr->SendFrame(str);
}
return 0;
}
先运行服务器,再运行客户端,可以看到客户端和服务器都能收到对方发送来的数据。