使用websocket解决客户端和服务器TCP长链接拆包粘包问题

客户端和服务器使用TCP进行长连接发送接收数据时,会出现粘包、拆包现象。因为TCP传输数据时是以流方式传输的,消息并非一包一包发送。传统的解决此问题的方式是发送端在发送数据前,先发送一个固定字节(例如4字节)的包含数据长度的消息。接收端先接收4字节数据,获取要接收的数据的长度,然后再获取该长度的数据。这就使编码流程复杂化。

websocket实现了客户端和服务器之间的TCP长链接,全双工通信。并且websocket内部的帧结构处理了粘包,拆包的问题,使我们不需要手动处理TCP层面的拆包和粘包问题。所以有客户端和服务器之间的长链接需求,可以集成现有的websocket库,简化编码流程。

下面就以c++客户端和golang服务端为例来说明。

c++客户端使用的websocket来源于libpoco库([email protected]: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 
#include 
#include 
#include 
#include 

#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(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(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 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(ip, port);
	
	while (1) {
		std::this_thread::sleep_for(std::chrono::seconds(1));
		std::string str{ "message from client" };
		tr->SendFrame(str);
	}
	return 0;
}

先运行服务器,再运行客户端,可以看到客户端和服务器都能收到对方发送来的数据。

你可能感兴趣的:(websocket,服务器,tcp/ip)