序文
那么这里博主先安利一些干货满满的专栏了!
首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助。
高质量干货博客汇总https://blog.csdn.net/yu_cblog/category_12379430.html?spm=1001.2014.3001.5482
プロジェクトのGITHUBアドレス
バイトストリームとデータグラムとは何ですか
コンピュータ ネットワーク理論では、バイト ストリームとは何ですか、データグラムとは何ですか。TCP や UDP との関係は何ですか。また、バイト ストリームとデータグラムの関係は何ですか?
バイト ストリームは、連続した無制限のデータ ストリームです。データは、明確なグループ化や境界のない連続したバイトのシーケンスとして扱われます。バイト ストリームでは、データは送信された順序で送信され、受信側は同じ順序でデータを受信します。バイト ストリームは、信頼性が高く、順序付けられたエラー検出および修正ベースのデータ転送を提供する接続指向のトランスポートです。TCP(Transmission Control Protocol)は、バイトストリーム伝送を使用するプロトコルです。
データグラムは、データを送信するための個別の制限された方法です。データを固定サイズのパケットに分割し、それぞれに独自のヘッダー情報 (通常、送信元アドレス、宛先アドレス、シーケンス番号などが含まれる) を付けて、独立して送信します。各データ パケットはネットワーク上を独立して移動し、場合によっては受信者まで異なるパスをたどります。データグラムの送信は通常コネクションレスであり、信頼性や順序性の保証はありません。UDP (User Datagram Protocol) は、データグラム伝送を使用するプロトコルです。
TCP および UDP との関係について:
- TCP は、バイト ストリーム送信を使用して、TCP 接続を通じて信頼性が高く、整然とした接続指向のデータ送信を提供します。TCP はシーケンス番号と確認応答メカニズムを使用して、データの信頼性と順序を保証します。
- UDP はデータグラム伝送方式を使用して、コネクションレスで信頼性の低いデータ伝送を提供します。UDP は、リアルタイム パフォーマンスに対する高い要件があるものの、データの信頼性と順序に対する要件が低いアプリケーション シナリオに適しています。
バイト ストリームとデータグラムの間に直接の関係はなく、これらは異なるトランスポートです。TCP はバイト ストリーム伝送を使用しますが、UDP はデータグラム伝送を使用します。これら 2 つの送信方法は、さまざまなネットワーク アプリケーション シナリオに適しており、具体的な選択はアプリケーションの要件と設計によって異なります。
粘着袋問題
スティッキー パケットの問題はネットワーク通信でよく見られる現象で、受信側がバイト ストリームを元のデータグラムに正確に分割できず、データ解析エラーが発生することを意味します。粘着性パッケージの問題を解決するには、次の方法を採用できます。
- 固定長セグメンテーションを使用すると、各データグラムの長さが固定され、受信側は固定長に従ってデータを抽出します。
- 特定の文字セグメンテーションを使用して、特殊文字または文字シーケンスをデータグラム間の区切り文字として定義すると、受信側は区切り文字に従ってデータグラムを抽出します。
- 長さフィールドの分割を使用すると、データグラム ヘッダーに長さを示すフィールドが追加され、受信側は長さフィールドを読み取り、対応する長さのバイトをデータグラムとして抽出します。
コンピューター ネットワーク アプリケーション層プロトコルを学習する原則は、ネットワーク通信メカニズムを理解し、カスタム プロトコルを実装することです。カスタム プロトコルの原則を研究すると、特殊文字に従ってバイト ストリームをデータグラムに分割し、逆シリアル化を通じて必要なメッセージを解析するのに役立ちます。これは、Web アプリケーションの開発、データ対話、エラー処理にとって重要です。
シリアル化と逆シリアル化
シリアル化は、オブジェクトの状態をバイト ストリームに変換して、メモリ、ファイル、ネットワーク上に保存したり、他のリモート システムに送信したりできるようにするプロセスです。シリアル化によりオブジェクトがバイト シーケンスに変換され、異なるプラットフォーム、システム、またはプログラミング言語間でのデータ交換と永続的なストレージが可能になります。
逆シリアル化 (逆シリアル化) は、バイト ストリームをオブジェクトの状態に変換して戻すプロセスです。つまり、シリアル化されたバイト ストリームからオブジェクトのプロパティとデータ構造を回復します。逆シリアル化は、バイト ストリームを元のオブジェクトの形式に変換して戻し、これらのオブジェクトを使用および操作できるようにします。
シリアル化と逆シリアル化は通常、分散システム、ネットワーク通信、永続ストレージなどのシナリオで使用されます。シリアル化を通じて、オブジェクトをバイト ストリームに変換して、ネットワーク経由で送信したり、ディスクに保存したりできます。その後、逆シリアル化を通じて、オブジェクトの状態の処理、操作、復元のためにバイト ストリームを元のオブジェクトに戻すことができます。
HTTP プロトコルを使用する場合、ブラウザを使用する場合、ブラウザのバックエンドとサーバーのバックエンドは、これらのことをうまく行うのに役立ちますが、今日はブロガーが独自のプロトコルをカスタマイズする方法を説明します。上記の原則を理解してください。
このプロジェクト: 独自のカスタム プロトコルを使用して電卓のネットワーク バージョンを実装します。
Githubアドレス
基本的なサーバーの準備
基礎となる層は TCP 接続を使用します。ブロガーによって実装されたコードでは、ブロガーは簡単にマルチスレッドを実装し、マルチスレッド バージョンを作成しました。ここでは、ブロガーは基礎となるサーバーのコードを表示しません。友人は直接読み取ることができます。 blogger's github 上記のもので十分です。
シリアル化と逆シリアル化のルールとメッセージ ルールを策定する
操作が 3 つのフィールドで構成されることを指定します。
x、y、op
x は最初のオペランドを表します
y は 2 番目のオペランドを表します
opは演算子の略です
1+2 つまり、x=1、y=2、op='+'
規定によると、クライアントの入力形式は次のとおりです。
シリアル化後は次のようになります。
リクエストメッセージのフォーマット
length\r\n__x __op __y\r\n
つまり、最初のフィールドはメッセージの長さで、その後に 2 つの特殊文字 \r\n、x、y、op がスペースで区切られ、最後に \r\n が追加されます。
これが私たちのメッセージです!
次のように進めます。
ユーザーがクライアントに入力した後、クライアントはまず 3 つのフィールドを Request 構造体に保存し、次にシリアル化インターフェイスを呼び出してその構造体を Request 文字列にシリアル化します。これは、上で示したメッセージの形式です。
サーバーはリクエスト メッセージを受信すると、デシリアライズされたインターフェイスを呼び出して 3 つのフィールドとリクエスト構造を取得します。
次に、Request 構造のコンテンツを通じて、結果を計算した後、Respone 構造を形成し、Respone をシリアル化し、クライアントに送信します。クライアントは Respone を逆シリアル化して最終結果を取得します。
応答メッセージの形式
length\r\n__x __op __y\r\n
プロトコル.hpp
#ifndef __Yufc_Protocol_For_Cal
#define __Yufc_Protocol_For_Cal
#include <iostream>
#include <string>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
/* 协议的本质 --> 约定! */
#define RECV_MAX_SIZE 1024
/* 请求协议是必须自带序列化功能的 一个结构体是很难发送的 需要序列化成string */
/* 上次0222定制的协议是不完善的!我们需要定制报文*/
// 协议
/* length\r\n__x __op __y\r\n*/
/* length这些就叫做协议报头 */
namespace yufc_ns_protocol
{
#define MYSELF true
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP)
class Request
{
public:
std::string Serialize()
{
#if MYSELF
std::string str;
str = std::to_string(__x);
str += SPACE;
str += __op; // BUG
str += SPACE;
str += std::to_string(__y);
return str;
#else
/* 使用别人的序列化方案 */
#include <jsoncpp/json/json.h>
Json::Value root;
root["x"] = __x;
root["y"] = __y;
root["op"] = __op;
Json::FastWriter writer;
return writer.write(root);
#endif
}
bool Deserialize(const std::string &str)
{
#if MYSELF
std::size_t left = str.find(SPACE);
if (left == std::string::npos)
{
return false; // 反序列化失败
}
std::size_t right = str.rfind(SPACE);
if (right == std::string::npos)
{
return false; // 反序列化失败
}
__x = atoi(str.substr(0, left).c_str()); // 拿到__x的字符串之后直接 atoi 就行
__y = atoi(str.substr(right + SPACE_LEN).c_str());
if (left + SPACE_LEN > str.size())
return false;
__op = str[left + SPACE_LEN];
return true;
#else
#include <jsoncpp/json/json.h>
Json::Value root;
Json::Reader reader;
reader.parse(str, root);
__x = root["x"].asInt();
__y = root["y"].anInt();
__op = root["op"].asInt();
return true;
#endif
}
public:
Request() {}
Request(int x, int y, char op) : __x(x), __y(y), __op(op) {}
~Request() {}
public:
int __x;
int __y;
char __op; // '+' ...
};
class Response
{
public:
/* "code result" */
std::string Serialize()
{
#if MYSELF
std::string s;
s = std::to_string(__code);
s += SPACE;
s += std::to_string(__result);
return s;
#else
#include <jsoncpp/json/json.h>
Json::Value root;
root["code"] = __code;
root["result"] = __result;
Json::FastWriter writer;
return writer.write(root);
#endif
}
bool Deserialize(const std::string &s)
{
#if MYSELF
// std::cerr << "s: " << s << std::endl;
std::size_t pos = s.find(SPACE);
if (pos == std::string::npos)
return false;
__code = atoi(s.substr(0, pos).c_str());
__result = atoi(s.substr(pos + SPACE_LEN).c_str());
return true;
#else
Json::Value root;
Json::Reader reader;
reader.parse(s, root);
__code = root["code"].asInt();
__result = root["result"].asInt();
return true;
#endif
}
public:
Response() {}
Response(int result, int code) : __result(result), __code(code) {}
~Response() {}
public:
int __result; // 计算结果
int __code; // 计算结果的状态码
/* 0表示计算结果可信任 */
};
// 临时方案
bool Recv(int sock, std::string *out)
{
char buffer[RECV_MAX_SIZE];
memset(buffer, 0, sizeof buffer);
ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s] = 0;
*out += buffer;
}
else if (s == 0)
{
logMessage(NORMAL, "client quit");
return false;
}
else
{
std::cerr << "recv error" << std::endl;
return false;
}
return true;
}
// 临时方案
void Send(int sock, const std::string sendStr)
{
send(sock, sendStr.c_str(), sendStr.size(), 0);
}
// "length\r\n__x __op __y\r\n"
std::string Decode(std::string &buffer)
{
std::size_t pos = buffer.find(SEP);
if (pos == std::string::npos) // 没有找到 返回空串
return "";
int size = atoi(buffer.substr(0, pos).c_str());
int surplus = buffer.size() - pos - SEP_LEN - SEP_LEN; // 如果这个surplus大于报文长度
// 则说明 这个报文长度是可以保证我们整体解析出一个合法字符串的
if (surplus >= size)
{
// 至少具有一个合法报文,可以动手提取了
buffer.erase(0, pos + SEP_LEN); // 此时"__x __op __y\r\n"
std::string s = buffer.substr(0, size);
buffer.erase(0, size + SEP_LEN);
// 此时我们就把合法的部分截取出来了 就是s
return s;
}
else
{
return "";
}
}
std::string Encode(std::string &s)
{
// 添加报头
std::string length = std::to_string(s.size());
std::string new_package = length;
new_package += SEP;
new_package += s;
new_package += SEP;
return new_package;
}
}
#endif
サーバープロセスをデーモンプロセスとして設定します
CalServer.cc
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include <memory>
#include <signal.h>
#include "Daemon.hpp"
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc << " port\n"
<< std::endl;
}
void debug(int sock)
{
std::cout << "service for debug, sock: " << sock << std::endl;
}
static yufc_ns_protocol::Response calHelper(const yufc_ns_protocol::Request &req)
{
yufc_ns_protocol::Response resp(0, 0);
switch (req.__op)
{
case '+':
resp.__result = req.__x + req.__y;
break;
case '-':
resp.__result = req.__x - req.__y;
break;
case '*':
resp.__result = req.__x * req.__y;
break;
case '/':
if (0 == req.__y)
resp.__code = 1;
else
resp.__result = req.__x / req.__y;
break;
case '%':
if (0 == req.__y)
resp.__code = 2;
else
resp.__result = req.__x % req.__y;
break;
default:
resp.__code = 3;
break;
}
return resp;
}
void Calculator(int sock)
{
/* 此处我们就要去定制协议 */
std::string inbuffer;
while (true)
{
// 1. 读取数据
bool res = yufc_ns_protocol::Recv(sock, &inbuffer); // 在这里我们读到了一个请求
// 2. 协议解析 -- 需要保证得到一个完整的报文
if (!res)
break; // 读取失败
std::string package = yufc_ns_protocol::Decode(inbuffer); // 这里Decode保证返回的是一个完整的字节流,是正确的,是可以序列化反序列化的!
// 如果这个package是空 表示Decode没有给我们返回完整报文
if (package.empty())
continue;
// 3. 保证该报文是一个完整的报文
yufc_ns_protocol::Request req;
// 4. 反序列化
req.Deserialize(package);
// 5. 业务逻辑
yufc_ns_protocol::Response resp = calHelper(req);
// 6. 序列化
std::string respString = resp.Serialize(); // 序列化
// 7. 在发送之前,添加报头
// "length\r\ncode result\r\n"
respString = yufc_ns_protocol::Encode(respString);
// 8. 发送(暂时这样写,高级IO的时候,我们再来谈发送的逻辑)
yufc_ns_protocol::Send(sock, respString); // 发送
}
}
int main(int argc, char **argv)
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
signal(SIGPIPE, SIG_IGN);
/*
一般经验: server 在编写的时候都要有严谨的判断逻辑
一般的服务器,都是要忽略SIGPIPE信号的!防止运行中出现非法写入的问题
*/
std::unique_ptr<yufc_tcpServer::TcpServer> server(new yufc_tcpServer::TcpServer(atoi(argv[1])));
server->BindService(Calculator);
MyDaemon();
server->Start();
return 0;
}