網絡--應用層自定義協議與序列化

目錄

4-1 應用層

4-2 重新理解 read、write、recv、send 和 tcp 為什么支持全雙工

4-3 開始實現


4-1 應用層

我們程序員寫的一個個解決我們實際問題 , 滿足我們日常需求的網絡程序 , 都是在應用
.
再談 " 協議 "
協議是一種 " 約定 ". socket api 的接口 , 在讀寫數據時 , 都是按 " 字符串 " 的方式來發送接
收的 . 如果我們要傳輸一些 " 結構化的數據 " 怎么辦呢 ?
其實,協議就是雙方約定好的結構化的數據
網絡版計算器
例如 , 我們需要實現一個服務器版的加法器 . 我們需要客戶端把要計算的兩個加數發過
, 然后由服務器進行計算 , 最后再把結果返回給客戶端 .
約定方案一 :
?
客戶端發送一個形如 "1+1" 的字符串 ;
?
這個字符串中有兩個操作數 , 都是整形 ;
?
兩個數字之間會有一個字符是運算符 , 運算符只能是 + ;
?
數字和運算符之間沒有空格 ;
?
...
約定方案二 :
?
定義結構體來表示我們需要交互的信息 ;
?
發送數據時將這個結構體按照一個規則轉換成字符串 , 接收到數據的時候再按
照相同的規則把字符串轉化回結構體 ;
?
這個過程叫做 " 序列化 " " 反序列化 "
序列化 和 反序列化

無論我們采用方案一 , 還是方案二 , 還是其他的方案 , 只要保證 , 一端發送時構造的數據 ,
在另一端能夠正確的進行解析 , 就是 ok . 這種約定 , 就是 應用層協議
但是,為了讓我們深刻理解協議,我們打算自定義實現一下協議的過程。
?
我們采用方案 2 ,我們也要體現協議定制的細節
?
我們要引入序列化和反序列化,只不過我們課堂直接采用現成的方案 -- jsoncpp
?
我們要對 socket 進行字節流的讀取處理

4-2 重新理解 readwriterecvsend tcp 為什么支持全雙工

在任何一臺主機上, TCP 連接既有發送緩沖區,又有接受緩沖區,所以,在內核
中,可以在發消息的同時,也可以收消息,即全雙工
?
這就是為什么一個 tcp sockfd 讀寫都是它的原因
實際數據什么時候發,發多少,出錯了怎么辦,由 TCP 控制,所以 TCP 叫做傳輸控制協議

4-3 開始實現

代碼結構
C++
Calculate.hpp Makefile Socket.hpp TcpServer.hpp
Daemon.hpp Protocol.hpp TcpClientMain.cc TcpServerMain.cc
// 簡單起見,可以直接采用自定義線程
// 為了減少課堂時間的浪費,也建議不用用戶輸入,直接 client<<->>server 通
信,這樣可以省去編寫沒有干貨的代碼
Socket 封裝
socket.hpp
C++
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define Convert(addrptr) ((struct sockaddr *)addrptr)
namespace Net_Work
{
const static int defaultsockfd = -1;
const int backlog = 5;
enum
{
SocketError = 1,
BindError,
ListenError,
};
// 封裝一個基類,Socket 接口類
// 設計模式:模版方法類
class Socket
{
public:
virtual ~Socket() {}
virtual void CreateSocketOrDie() = 0;
virtual void BindSocketOrDie(uint16_t port) = 0;
virtual void ListenSocketOrDie(int backlog) = 0;
virtual Socket *AcceptConnection(std::string *peerip,
uint16_t *peerport) = 0;
virtual bool ConnectServer(std::string &serverip, uint16_t
serverport) = 0;
virtual int GetSockFd() = 0;
virtual void SetSockFd(int sockfd) = 0;
virtual void CloseSocket() = 0;
virtual bool Recv(std::string *buffer, int size) = 0;
virtual void Send(std::string &send_str) = 0;
// TODO
public:
void BuildListenSocketMethod(uint16_t port, int backlog)
{
CreateSocketOrDie();
BindSocketOrDie(port);
ListenSocketOrDie(backlog);
}
bool BuildConnectSocketMethod(std::string &serverip,
uint16_t serverport)
{
CreateSocketOrDie();
return ConnectServer(serverip, serverport);
}
void BuildNormalSocketMethod(int sockfd)
{
SetSockFd(sockfd);
}
};
class TcpSocket : public Socket
{
public:
TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
{
}
~TcpSocket()
{
}
void CreateSocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
exit(SocketError);
}
void BindSocketOrDie(uint16_t port) override
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
int n = ::bind(_sockfd, Convert(&local),
sizeof(local));
if (n < 0)
exit(BindError);
}
void ListenSocketOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
exit(ListenError);
}
Socket *AcceptConnection(std::string *peerip, uint16_t
*peerport) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsockfd = ::accept(_sockfd, Convert(&peer),
&len);
if (newsockfd < 0)
return nullptr;
*peerport = ntohs(peer.sin_port);
*peerip = inet_ntoa(peer.sin_addr);
Socket *s = new TcpSocket(newsockfd);
return s;
}
bool ConnectServer(std::string &serverip, uint16_t
serverport) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
server.sin_port = htons(serverport);
int n = ::connect(_sockfd, Convert(&server),
sizeof(server));
if (n == 0)
return true;
else
return false;
}
int GetSockFd() override
{
return _sockfd;
}
void SetSockFd(int sockfd) override
{
_sockfd = sockfd;
}
void CloseSocket() override
{
if (_sockfd > defaultsockfd)
::close(_sockfd);
}
bool Recv(std::string *buffer, int size) override
{
char inbuffer[size];
ssize_t n = recv(_sockfd, inbuffer, size-1, 0);
if(n > 0)
{
inbuffer[n] = 0;
*buffer += inbuffer; // 故意拼接的
return true;
}
else if(n == 0) return false;
else return false;
}
void Send(std::string &send_str) override
{
// 多路轉接我們在統一說
send(_sockfd, send_str.c_str(), send_str.size(), 0);
}
private:
int _sockfd;
};
}
定制協議
基本結構
?
定制基本的結構化字段,這個就是協議
C++
class Request
{
private:
// _data_x _oper _data_y
// 報文的自描述字段
// "len\r\nx op y\r\n" : \r\n 不屬于報文的一部分,約定
// 很多工作都是在做字符串處理!
int _data_x; // 第一個參數
int _data_y; // 第二個參數
char _oper; // + - * / %
};
class Response
{
private:
// "len\r\n_result _code\r\n"
int _result; // 運算結果
int _code; // 運算狀態
};
protocol.hpp
C++
#pragma once
#include <iostream>#include <memory>
#include <jsoncpp/json/json.h>
namespace Protocol
{
// 問題
// 1. 結構化數據的序列和反序列化
// 2. 還要解決用戶區分報文邊界 --- 數據包粘報問題
// 講法
// 1. 自定義協議
// 2. 成熟方案序列和反序列化
// 總結:
// 我們今天定義了幾組協議呢??我們可以同時存在多個協議嗎???可以
// "protocol_code\r\nlen\r\nx op y\r\n" : \r\n 不屬于報文的一部
分,約定
const std::string ProtSep = " ";
const std::string LineBreakSep = "\r\n";
// "len\r\nx op y\r\n" : \r\n 不屬于報文的一部分,約定
std::string Encode(const std::string &message)
{
std::string len = std::to_string(message.size());
std::string package = len + LineBreakSep + message +
LineBreakSep;
return package;
}
// "len\nx op y\n" : \n 不屬于報文的一部分,約定
// 我無法保證 package 就是一個獨立的完整的報文
// "l
// "len
// "len\r\n
// "len\r\nx
// "len\r\nx op
// "len\r\nx op y
// "len\r\nx op y\r\n"
// "len\r\nx op y\r\n""len
// "len\r\nx op y\r\n""len\n
// "len\r\nx op
// "len\r\nx op y\r\n""len\nx op y\r\n"
// "len\r\nresult code\r\n""len\nresult code\r\n"
bool Decode(std::string &package, std::string *message)
{
// 除了解包,我還想判斷報文的完整性, 能否正確處理具有"邊界"的報
文
auto pos = package.find(LineBreakSep);
if (pos == std::string::npos)
return false;
std::string lens = package.substr(0, pos);
int messagelen = std::stoi(lens);
int total = lens.size() + messagelen + 2 *
LineBreakSep.size();
if (package.size() < total)
return false;
// 至少 package 內部一定有一個完整的報文了!
*message = package.substr(pos + LineBreakSep.size(),
messagelen);
package.erase(0, total);
return true;
}
class Request
{
public:
Request() : _data_x(0), _data_y(0), _oper(0)
{
}
Request(int x, int y, char op) : _data_x(x), _data_y(y),
_oper(op)
{
}
void Debug()
{
std::cout << "_data_x: " << _data_x << std::endl;
std::cout << "_data_y: " << _data_y << std::endl;
std::cout << "_oper: " << _oper << std::endl;
}
void Inc()
{
_data_x++;
_data_y++;
}
// 結構化數據->字符串
bool Serialize(std::string *out)
{
Json::Value root;
root["datax"] = _data_x;
root["datay"] = _data_y;
root["oper"] = _oper;
Json::FastWriter writer;
*out = writer.write(root);
return true;
}
bool Deserialize(std::string &in) // "x op y" [)
{
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if(res)
{
_data_x = root["datax"].asInt();
_data_y = root["datay"].asInt();
_oper = root["oper"].asInt();
}
return res;
}
int GetX() { return _data_x; }
int GetY() { return _data_y; }
char GetOper() { return _oper; }
private:
// _data_x _oper _data_y
// 報文的自描述字段
// "len\r\nx op y\r\n" : \r\n 不屬于報文的一部分,約定
// 很多工作都是在做字符串處理!
int _data_x; // 第一個參數
int _data_y; // 第二個參數
char _oper; // + - * / %
};
class Response
{
public:
Response() : _result(0), _code(0)
{
}
Response(int result, int code) : _result(result),
_code(code)
{
}
bool Serialize(std::string *out)
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
*out = writer.write(root);
return true;
}
bool Deserialize(std::string &in) // "_result _code" [)
{
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if(res)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return res;
}
void SetResult(int res) { _result = res; }
void SetCode(int code) { _code = code; }
int GetResult() { return _result; }
int GetCode() { return _code; }
private:
// "len\r\n_result _code\r\n"
int _result; // 運算結果
int _code; // 運算狀態
};
// 簡單的工廠模式,建造類設計模式
class Factory
{
public:
std::shared_ptr<Request> BuildRequest()
{
std::shared_ptr<Request> req =
std::make_shared<Request>();
return req;
}
std::shared_ptr<Request> BuildRequest(int x, int y, char
op)
{
std::shared_ptr<Request> req =
std::make_shared<Request>(x, y, op);
return req;
}
std::shared_ptr<Response> BuildResponse()
{
std::shared_ptr<Response> resp =
std::make_shared<Response>();
return resp;
}
std::shared_ptr<Response> BuildResponse(int result, int
code)
{
std::shared_ptr<Response> req =
std::make_shared<Response>(result, code);
return req;
}
};
}
4-4 關于流式數據的處理
?
你如何保證你每次讀取就能讀完請求緩沖區的所有內容?
?
你怎么保證讀取完畢或者讀取沒有完畢的時候,讀到的就是一個完整的請求呢?
?
處理 TCP 緩沖區中的數據,一定要保證正確處理請求
C++
const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";
// "len\nx op y\n" : \n 不屬于報文的一部分,約定
std::string Encode(const std::string &message)
{
std::string len = std::to_string(message.size());
std::string package = len + LineBreakSep + message +
LineBreakSep;
return package;
}
// "len\nx op y\n" : \n 不屬于報文的一部分,約定
// 我無法保證 package 就是一個獨立的完整的報文
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n""len\n
// "len\nx op
// "len\nx op y\n""len\nx op y\n"
// "len\nresult code\n""len\nresult code\n"
bool Decode(std::string &package, std::string *message)
{
// 除了解包,我還想判斷報文的完整性, 能否正確處理具有"邊界"的報
文
auto pos = package.find(LineBreakSep);
if (pos == std::string::npos)
return false;
std::string lens = package.substr(0, pos);
int messagelen = std::stoi(lens);
int total = lens.size() + messagelen + 2 *
LineBreakSep.size();
if (package.size() < total)
return false;
// 至少 package 內部一定有一個完整的報文了!
*message = package.substr(pos + LineBreakSep.size(),
messagelen);
package.erase(0, total);
return true;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/79708.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/79708.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/79708.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

fastlio用mid360錄制的bag包離線建圖,提示消息類型錯誤

我用mid360錄制的bag包&#xff0c;激光雷達的數據類型是sensor_msgs::PointCloud2&#xff0c;但是運行fast_lio中的mid360 launch文件&#xff0c;會報錯&#xff08;沒截圖&#xff09;&#xff0c;顯示無法從livox_ros_driver2::CustomMsg轉換到sensor_msgs::PointCloud2。…

C# WinForm窗口TextBox控件只能輸入數字(包括小數)并且恢復Ctrl+C復制和Ctrl+V粘貼功能

1. 前言 最近在寫定GPS定位時&#xff0c;經緯度是用的double類型&#xff0c;并且經緯度的要求是小數點后最少6位&#xff0c;多了能達到17位&#xff0c;又遇到了常用的TextBox控件只能輸入數字、小數的功能&#xff0c;因為有一年多沒有寫程序&#xff0c;現在再來寫這些感…

【MySQL數據庫】數據類型

目錄 1&#xff0c;數據類型分類 2&#xff0c;bit類型 3&#xff0c;小數類型 3-1&#xff0c;float/double類型 3-2&#xff0c;decimal類型 4&#xff0c;字符串類型 4-1&#xff0c;char 4-2&#xff0c;varchar 5&#xff0c;日期和時間類型 6&#xff0c;enum和…

Spark-SQL核心編程2

路徑問題 相對路徑與絕對路徑&#xff1a;建議使用絕對路徑&#xff0c;避免復制粘貼導致的錯誤&#xff0c;必要時將斜杠改為雙反斜杠。 數據處理與展示 SQL 風格語法&#xff1a;創建臨時視圖并使用 SQL 風格語法查詢數據。 DSL 風格語法&#xff1a;使用 DSL 風格語法查詢…

pandas庫詳解

CONTENT 基本數據結構SeriesDataFrame 數據讀取與寫入讀取 CSV 文件寫入 CSV 文件 數據清洗處理缺失值數據類型轉換 數據操作索引與切片數據合并數據分組與聚合 數據可視化 基本數據結構 Series Series 屬于一維標記數組&#xff0c;由一組數據和對應的索引構成。 import pa…

黑馬商城(五)微服務保護和分布式事務

一、雪崩問題 二、雪崩-解決方案&#xff08;服務保護方案&#xff09; 請求限流&#xff1a; 線程隔離&#xff1a; 服務熔斷&#xff1a; 服務保護組件&#xff1a; 三、Sentinel 引入依賴&#xff1a; <!--sentinel--> <dependency><groupId>com.aliba…

洛谷P1312 [NOIP 2011 提高組] Mayan 游戲

題目 #算法/進階搜索 思路: 根據題意,我們可以知道,這題只能枚舉,剪枝,因此,我們考慮如何枚舉,剪枝. 首先,我們要定義下降函數down(),使得小木塊右移時,能夠下降到最低處,其次,我們還需要寫出判斷函數,判斷矩陣內是否有小木塊沒被消除.另外,我們還需要消除函數,將矩陣內三個相連…

基于Redis的3種分布式ID生成策略

在分布式系統設計中&#xff0c;全局唯一ID是一個基礎而關鍵的組件。隨著業務規模擴大和系統架構向微服務演進&#xff0c;傳統的單機自增ID已無法滿足需求。高并發、高可用的分布式ID生成方案成為構建可靠分布式系統的必要條件。 Redis具備高性能、原子操作及簡單易用的特性&…

Spotlight on Mysql詳細介紹

1. 版本............................................................................................................................................1 2. 使用介紹...............................................................................................…

背包 DP 詳解

文章目錄 背包DP01 背包完全背包多重背包二進制優化單調隊列優化 小結 背包DP 背包 DP&#xff0c;說白了就是往一個背包里扔東西&#xff0c;求最后的最大價值是多少&#xff0c;一般分為了三種&#xff1a;01 背包、完全背包和多重背包。而 01 背包則是一切的基礎。 01 背包…

二級評論列表-Java實現

二級評論列表是很常見的功能&#xff0c;文章記錄了新手用Java實現的具體邏輯。 整體實現邏輯是先用2個sql&#xff0c;分別查出兩層數據。然后用java在service中實現數據組裝&#xff0c;返給前端。這種實現思路好處是SQL簡潔&#xff0c;邏輯分明&#xff0c;便于維護。 一…

快速入手-基于python和opencv的人臉檢測

1、安裝庫 pip install opencv-python 如果下載比較卡的話&#xff0c;指向國內下載地址&#xff1a; pip3 install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 2、下載源碼 https://opencv.org/ windows11對應的版本下載&#xff1a; https://pan.baidu…

GitLab本地安裝指南

當前GitLab的最新版是v17.10&#xff0c;安裝地址&#xff1a;https://about.gitlab.com/install/。當然國內也可以安裝極狐GitLab版本&#xff0c;極狐GitLab 是 GitLab 中國發行版&#xff08;JH&#xff09;。極狐GitLab支持龍蜥&#xff0c;歐拉等國內的操作系統平臺。安裝…

OpenCv高階(六)——圖像的透視變換

目錄 一、透視變換的定義與作用 二、透視變換的過程 三、OpenCV 中的透視變換函數 1. cv2.getPerspectiveTransform(src, dst) 2. cv2.warpPerspective(src, H, dsize, dstNone, flagscv2.INTER_LINEAR, borderModecv2.BORDER_CONSTANT, borderValue0) 四、文檔掃描校正&a…

資源-又在網上淘到金了

前言&#xff1a; 本期再分享網上沖浪發現的特效/動畫/視頻資源網站。 一、基本介紹&#xff1a; mantissa.xyz&#xff0c;about作者介紹為&#xff1a;Midge “Mantissa” Sinnaeve &#xff08;米奇辛納夫&#xff09;是一位屢獲殊榮的藝術家和導演&#xff0c;提供動畫、…

Linux疑難雜惑 | 云服務器重裝系統后vscode無法遠程連接的問題

報錯原因&#xff1a;本地的known_hosts文件記錄服務器信息與現服務器的信息沖突了&#xff0c;導致連接失敗。 解決方法&#xff1a;找到本地的known_hosts文件&#xff0c;把里面的所有東西刪除后保存就好了。 該文件的路徑可以在報錯中尋找&#xff1a;比如我的路徑就是&a…

FFMPEG-視頻解碼-支持rtsp|rtmp|音視頻文件(低延遲)

本人親測解碼顯示對比延遲達到7到20毫秒之間浮動兼容播放音視頻文件、拉流RTSP、RTMP等網絡流 基于 Qt 和 FFmpeg 的視頻解碼播放器類,繼承自 QThread,實現了視頻流的解碼、播放控制、幀同步和錯誤恢復等功能 工作流程初始化階段: 用戶設置URL和顯示尺寸 調用play()啟動線程解…

【音視頻】音視頻FLV合成實戰

FFmpeg合成流程 示例本程序會?成?個合成的?頻和視頻流&#xff0c;并將它們編碼和封裝輸出到輸出?件&#xff0c;輸出格式是根據?件擴展名?動猜測的。 示例的流程圖如下所示。 ffmpeg 的 Mux 主要分為 三步操作&#xff1a; avformat_write_header &#xff1a; 寫?件…

全鏈路開源數據平臺技術選型指南:六大實戰工具鏈解析

在數字化轉型加速的背景下&#xff0c;開源技術正重塑數據平臺的技術格局。本文深度解析數據平臺的全鏈路架構&#xff0c;精選六款兼具創新性與實用性的開源工具&#xff0c;涵蓋數據編排、治理、實時計算、聯邦查詢等核心場景&#xff0c;為企業構建云原生數據架構提供可落地…

JAVA設計模式——(1)適配器模式

JAVA設計模式——&#xff08;1&#xff09;適配器模式 目的理解實現優勢 目的 將一個類的接口變換成客戶端所期待的另一種接口&#xff0c;從而使原本因接口不匹配而無法一起工作的兩個類能夠在一起工作。 理解 可以想象成一個國標的插頭&#xff0c;結果插座是德標的&…