應(yīng)用層
我們程序員寫的一個(gè)個(gè)解決我們實(shí)際問(wèn)題, 滿足我們?nèi)粘P枨蟮木W(wǎng)絡(luò)程序, 都是在應(yīng)用層
初識(shí)TCP協(xié)議通訊流程
- 建立鏈接和斷開(kāi)鏈接
基于TCP協(xié)議,我們需要知道寫代碼時(shí)對(duì)應(yīng)的接口大概在TCP通訊的過(guò)程中屬于什么樣的時(shí)間點(diǎn)角色,在TCP協(xié)議時(shí)詳談。三次握手,四次揮手
listen狀態(tài):準(zhǔn)備好了,可以進(jìn)行鏈接,accept:獲取鏈接,不是創(chuàng)造鏈接,鏈接已經(jīng)在底層創(chuàng)建好了,在應(yīng)用層調(diào)用accept把鏈接拿上來(lái)
connect:1.發(fā)起鏈接請(qǐng)求2.綁定套接字;建立鏈接,在底層向服務(wù)端建立鏈接請(qǐng)求,在TCP中,采用鏈接的方案是三次握手的方案,connect會(huì)發(fā)起三次握手,發(fā)起鏈接請(qǐng)求和真正的建立鏈接是兩碼事,建立鏈接由雙方OS自動(dòng)完成的,為什么自動(dòng)完成?網(wǎng)絡(luò)分層中,下三層是OS內(nèi)部的,用戶感知不到。通過(guò)客戶端調(diào)用connect讓OS來(lái)幫我們把三次握手的工作做完。
而accept是獲取鏈接,鏈接是已經(jīng)建立好了的,所以accept并不參與三次握手的任何細(xì)節(jié),accept一定是在獲取鏈接前別人把鏈接做完,既鏈接建立完。三次握手是OS自己完成的,connect只是發(fā)起,accept只是收尾。即使上層不調(diào)用accept,三次握手也是能夠建立好的。
TCP保證可靠性不是write和read有關(guān)系的,由雙方OS完成的,后面詳談。
建立鏈接后面就要斷開(kāi)鏈接,所以UDP由于不需要建立鏈接,自然不需要談?wù)摂嚅_(kāi)鏈接
而四次揮手的工作都是由雙方的OS完成,而我們決定什么時(shí)候分手一旦調(diào)用系統(tǒng)調(diào)用close,用戶層就不用管了。
- 理解鏈接
談男女朋友時(shí),都會(huì)表達(dá)自己的愛(ài)意,一定有一方主動(dòng)發(fā)起鏈接,無(wú)論如何表達(dá),雙方看對(duì)眼的概率是極低的。而主動(dòng)發(fā)起鏈接,是怎么發(fā)起的呢?首先,男方先表白,然后女方在做表態(tài),什么時(shí)候在一起?男方回答就現(xiàn)在。這就是雙方三次握手成功。(雖然現(xiàn)實(shí)生活中被拒絕是常態(tài))
建立鏈接究竟在干什么:記下一些東西
- 什么是建立鏈接
所謂的建立鏈接,三次握手根本就是手段,不是目的,為了達(dá)到讓雙方都能記住這一套,一個(gè)服務(wù)端鏈接客戶端,很多客戶端來(lái)鏈接了,意味著很多的客戶端來(lái)了,OS應(yīng)該區(qū)分清楚,需要把鏈接管理起來(lái),先描述在組織,需要?jiǎng)?chuàng)建對(duì)應(yīng)的鏈接數(shù)據(jù)結(jié)構(gòu),把所有的鏈接描述起來(lái),在對(duì)其進(jìn)行管理。所謂的鏈接就是OS內(nèi)部創(chuàng)建的鏈接結(jié)構(gòu)體,包含了在建立鏈接時(shí)對(duì)應(yīng)的屬性信息。當(dāng)有新的鏈接進(jìn)來(lái)時(shí),每到來(lái)一個(gè)鏈接,服務(wù)端會(huì)構(gòu)建一個(gè)鏈接對(duì)象 ,將所有的鏈接對(duì)象在內(nèi)部中用特定的數(shù)據(jù)結(jié)構(gòu)管理起來(lái)。這就是鏈接的建模過(guò)程。維護(hù)鏈接是需要成本的。占用內(nèi)存資源,要用對(duì)象進(jìn)行管理。
斷開(kāi)鏈接需要四次揮手,斷開(kāi)鏈接的最終目的毫無(wú)疑問(wèn)就是把建立好的鏈接信息釋放。四次揮手理解:
男女朋友處的非常好,走到了婚姻的殿堂,但是被現(xiàn)實(shí)打敗了,過(guò)不下去啦。然后一方提出離婚,但是你自己說(shuō)了不算,另一方說(shuō)好啊,過(guò)了一會(huì),對(duì)象又說(shuō)離就離,那我也要離,那么你一看,我也OK。所以斷開(kāi)鏈接是雙方的事情,必須得征求雙方的意見(jiàn)。雙方在協(xié)商,TCP要保證可靠性,你說(shuō)的話要保證你也聽(tīng)到了,我也知道了,反之也一樣。這就是傳說(shuō)中的四次揮手
TCP與UDP對(duì)比
可靠傳輸VS不可靠傳輸
有連接VS無(wú)連接
字節(jié)流VS數(shù)據(jù)報(bào)
定制協(xié)議
應(yīng)用層協(xié)議的定制
再談協(xié)議
協(xié)議是一種約定,socket api的接口,在讀寫數(shù)據(jù)時(shí),都是按照字符串的方式來(lái)接收的,如果要傳輸一些”結(jié)構(gòu)化的數(shù)據(jù)“怎么辦呢?
結(jié)構(gòu)化的數(shù)據(jù):群里說(shuō)話的時(shí)候除了消息本身,還有頭像,昵稱時(shí)間等等信息 。但是不是一個(gè)一個(gè)獨(dú)立的個(gè)體,你需要做的把這些消息形成一個(gè)報(bào)文——打包成一個(gè)字符串。
由多變一這個(gè)過(guò)程就是序列化。經(jīng)過(guò)網(wǎng)絡(luò)傳輸后,收到的是一個(gè)報(bào)文,收到一個(gè)報(bào)文要的是什么?把一個(gè)字符串變成多個(gè)字符串,這個(gè)過(guò)程是反序列化
業(yè)務(wù)數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)的時(shí)候,先序列化發(fā)送,收到的是序列字節(jié)流,要先進(jìn)行反序列化, 然后才能使用
業(yè)務(wù)協(xié)議就是結(jié)構(gòu)體,這樣說(shuō)還是不夠的,所以我們要手寫一個(gè)協(xié)議。
應(yīng)用場(chǎng)景:形成字符串對(duì)方收到,收到之后上層來(lái)不及接收,對(duì)方又發(fā)一個(gè),有可能一次全讀的,上層如何保證收到的是一個(gè)報(bào)文?
tcp這里怎么保證收到一個(gè)完整的報(bào)文
理解業(yè)務(wù)協(xié)議,理解序列化和反序列化。
網(wǎng)絡(luò)版本計(jì)算器
例如, 我們需要實(shí)現(xiàn)一個(gè)服務(wù)器版的加法器. 我們需要客戶端把要計(jì)算的兩個(gè)加減乘除數(shù)發(fā)過(guò)去, 然后由服務(wù)器進(jìn)行計(jì)算, 最后再把結(jié)果返回給客戶端
TCP是面向字節(jié)流的,所以明確報(bào)文和報(bào)文的邊界:
TCP是全雙工的,如果接收方來(lái)不及讀,那接收緩沖區(qū)就會(huì)存在很多數(shù)據(jù),讀的時(shí)候怎么怎么保證讀到一個(gè)完整的報(bào)文:
1.定長(zhǎng)2.特殊符號(hào)3.自描述方式
序列化、反序列化與定制協(xié)議是兩碼事,是不同階段的事情,定制協(xié)議:報(bào)頭+有效載荷
Protocal.hpp
自定義協(xié)議:
#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)
請(qǐng)求和響應(yīng):Request,Response
Request:x,y,op(“x op y”)x和y是數(shù)據(jù),op是操作符,比如1+2
Response:設(shè)置了退出碼exitcode和結(jié)果result()
對(duì)請(qǐng)求和響應(yīng)添加報(bào)頭,這里設(shè)置的報(bào)頭是長(zhǎng)度,enLength(即添加大小,轉(zhuǎn)化成字符串),也就是封裝了enLength函數(shù):
//"x op y"->"content_len"\r\n"x op y"\r\n
//"exitcode result"->"cotent_len"\r\n"exitcode result"\r\n
const std::string enLength(const std::string &text)
{
std::string send_string = std::to_string(text.size());
send_string += LINE_SEP;
send_string += text;
send_string += LINE_SEP;
return send_string;
}
對(duì)請(qǐng)求和響應(yīng)提取報(bào)文,只要報(bào)文,不要報(bào)頭,也就是封裝了deLength函數(shù):
//"cotent_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
auto pos = package.find(LINE_SEP);
if (pos == std::string::npos)
return false;
std::string text_len_string = package.substr(0, pos);
int text_len = std::stoi(text_len_string);
*text = package.substr(pos + LINE_SEP_LEN, text_len);
return true;
}
對(duì)請(qǐng)求和響應(yīng)進(jìn)行序列化和反序列化:對(duì)于序列化和反序列化我們可以用Json來(lái)進(jìn)行實(shí)現(xiàn)
序列化過(guò)程:結(jié)構(gòu)化數(shù)據(jù)->“x op y”
反序列化過(guò)程:“x op y”->結(jié)構(gòu)化數(shù)據(jù)
Protocal.hpp還提供了recvPackage函數(shù)
#define SEP " "
#define SEP_LEN strlen(SEP)
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP)
enum
{
OK = 0,
DIV_ZERO,
MOD_ZERO,
OP_ERROR
};
//"x op y" --->"content_len"\r\n"x op y"\r\n,添加報(bào)頭
std::string enLength(const std::string &text)
{
std::string send_string = std::to_string(text.size());
send_string += LINE_SEP;
send_string += text;
send_string += LINE_SEP;
return send_string;
}
//"content_len"\r\n"exitcode result"\r\n
// 去掉報(bào)頭,得到"exitcode result"
bool deLength(const std::string &package, std::string *text)
{
auto pos = package.find(LINE_SEP);
if (pos == std::string::npos)
return false;
std::string text_len_string = package.substr(0, pos); // content_len:如“14”
int text_len = std::stoi(text_len_string);
*text = package.substr(pos + LINE_SEP_LEN, text_len);
return true;
}
class Request
{
public:
Request() : x(0), y(0), op(0)
{
}
Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
{
}
// 序列化:
// 結(jié)構(gòu)化-> "x op y"
bool serialize(std::string *out)
{
#ifdef MYSELF
*out = "";
std::string x_string = std::to_string(x);
std::string y_string = std::to_string(y);
*out = x_string;
*out += SEP;
*out += op;
*out += SEP;
*out += y_string;
#else
Json::Value root;
root["first"] = x;
root["second"] = y;
root["oper"] = op;
Json::FastWriter writer;
*out = writer.write(root);
#endif
return true;
}
// 反序列化化:
//"x op y"->結(jié)構(gòu)化
bool deserialize(const std::string &in)
{
#ifdef MYSELF
auto left = in.find(SEP);
auto right = in.rfind(SEP);
if (left == std::string::npos || right == std::string::npos)
return false;
if (left == right)
return false;
if (right - (left + SEP_LEN) != 1)
return false;
std::string x_string = in.substr(0, left);
std::string y_string = in.substr(right + SEP_LEN);
if (x_string.empty())
return false;
if (y_string.empty())
return false;
x = std::stoi(x_string);
y = std::stoi(y_string);
op = in[left + SEP_LEN];
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
x = root["first"].asInt();
y = root["second"].asInt();
op = root["oper"].asInt();
#endif
return true;
}
public:
int x;
int y;
char op;
};
class Response
{
public:
Response() : exitcode(0), result(0)
{
}
Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
{
}
bool serialize(std::string *out)
{
#ifdef MYSELF
*out = "";
std::string ec_string = std::to_string(exitcode);
std::string res_string = std::to_string(result);
*out = ec_string;
*out += SEP;
*out += res_string;
#else
Json::Value root;
root["exitcode"] = exitcode;
root["result"] = result;
Json::FastWriter writer;
*out = writer.write(root);
#endif
return true;
}
bool deserialize(const std::string &in)
{
#ifdef MYSELF
auto mid = in.find(SEP);
if (mid == std::string::npos)
return false;
std::string ec_string = in.substr(0, mid);
std::string res_string = in.substr(mid + SEP_LEN);
if (ec_string.empty() || res_string.empty())
return false;
exitcode = std::stoi(ec_string);
result = std::stoi(res_string);
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
exitcode = root["exitcode"].asInt();
result = root["result"].asInt();
#endif
return true;
}
public:
int exitcode;
int result;
};
// 讀取報(bào)文,保證讀取的是一個(gè)完整的報(bào)文 ,inbuffer由外部傳入
// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{
char buffer[1024];
while(true)
{
ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if(n>0)
{
buffer[n] = 0;
inbuffer+=buffer;
auto pos = inbuffer.find(LINE_SEP);
if(pos == std::string::npos) continue;
std::string text_len_string = inbuffer.substr(0,pos);
int text_len =std::stoi(text_len_string);
int total_len = text_len_string.size()+2*LINE_SEP_LEN+text_len;
std::cout<<"處理前#inbuffer:\n"<<inbuffer<<std::endl;
if(inbuffer.size()< total_len)
{
std::cout<<"你輸入的消息,沒(méi)有遵守所定制的協(xié)議,正在等待后續(xù)的內(nèi)容,continue"<<std::endl;
continue;
}
//至少是一個(gè)完整的報(bào)文
*text = inbuffer.substr(0,total_len);
inbuffer.erase(0,total_len);
std::cout<<"處理后#inbuffer:\n"<<inbuffer<<std::endl;
break;
}
else
return false;
}
return true;
}
對(duì)于recvPackage函數(shù)我們要保證讀到的至少是一個(gè)完整的報(bào)文
CalServer
服務(wù)端代碼文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-493174.html
//CalServer.hpp
namespace server
{
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
static const uint16_t gport = 8080;
static const int gbacklog = 5;
typedef std::function<bool(const Request &req, Response &resp)> func_t;
void handlerEntery(int sock,func_t func)
{
std::string inbuffer;
while(true)
{
//1.讀取:"content_len"\r\n"x op y"\r\n
//保證讀到的消息是一個(gè)完整的請(qǐng)求
std::string req_text,req_str;
if(!recvPackage(sock,inbuffer,&req_text)) return;
std::cout<<"帶報(bào)頭的請(qǐng)求:\n"<<req_text<<std::endl;
//去掉報(bào)頭
if(!deLength(req_text,&req_str)) return;
std::cout<<"去掉報(bào)頭的正文:\n"<<req_str<<std::endl;
//2.對(duì)請(qǐng)求Request,反序列化
//2.1得到一個(gè)結(jié)構(gòu)化的請(qǐng)求對(duì)象
Request req;
if(!req.deserialize(req_str)) return;
//3.計(jì)算機(jī)處理————業(yè)務(wù)邏輯
Response resp;
func(req,resp);
//4.對(duì)響應(yīng)Response,進(jìn)行序列化
//4.1得到一個(gè)"字符串"
std::string resp_str;
resp.serialize(&resp_str);
std::cout<<"計(jì)算完成,序列化響應(yīng):"<<resp_str<<std::endl;
//5.發(fā)送響應(yīng)
std::string send_string = enLength(resp_str);
std::cout<<"構(gòu)建完成完整的響應(yīng)\n"<<send_string<<std::endl;
send(sock,send_string.c_str(),send_string.size(),0);
}
}
class CalServer
{
public:
CalServer(const uint16_t&port = gport):_listensock(-1),_port(port)
{}
void initServer()
{
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
logMessage(FATAL,"create socket error");
exit(SOCKET_ERR);
}
logMessage(NORMAL,"create socket success:%d",_listensock);
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)
{
logMessage(FATAL,"bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL,"bind socket success");
if(listen(_listensock,gbacklog)<0)
{
logMessage(FATAL,"listen socker error");
exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen socket success");
}
void start(func_t func)
{
for(;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
if(sock<0)
{
logMessage(ERROR,"accept error,next");
continue;
}
logMessage(NORMAL,"accept a new link success,get new sock:%d",sock);
pid_t id = fork();
if(id == 0)
{
close(_listensock);
handlerEntery(sock,func);
close(sock);
exit(0);
}
close(sock);
pid_t ret = waitpid(id,nullptr,0);
if(ret>0)
{
logMessage(NORMAL,"wait child success");
}
}
}
~CalServer() {}
public:
int _listensock;
uint16_t _port;
};
}
//CalServer.cc
#include "calServer.hpp"
#include <memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
// req是處理好的完整的請(qǐng)求對(duì)象
// resp:根據(jù)req進(jìn)行業(yè)務(wù)處理,填充resp,不需要管理任何IO,序列化和反序列化
bool cal(const Request &req, Response &resp)
{
// req已經(jīng)有結(jié)構(gòu)化的數(shù)據(jù)
resp.exitcode = OK;
resp.result = 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(req.y==0) resp.exitcode = DIV_ZERO;
else resp.result = req.x/req.y;
}
break;
case '%':
{
if(req.y == 0)
resp.exitcode = MOD_ZERO;
else resp.result = req.x%req.y;
}
break;
default:
resp.exitcode = OP_ERROR;
break;
}
return true;
}
// tcp服務(wù)器,啟動(dòng)上和udp server一模一樣
// ./tcpserver local_port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<CalServer> tsvr(new CalServer(port));
tsvr->initServer();
tsvr->start(cal);
return 0;
}
CalClient
ParseLine:解析,構(gòu)建一個(gè)請(qǐng)求:“1+1”文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-493174.html
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Protocol.hpp"
#define NUM 1024
class CalClient
{
public:
CalClient(const std::string &serverip, const uint16_t &serverport)
: _sock(-1), _serverip(serverip), _serverport(serverport)
{
}
void initClient()
{
// 1. 創(chuàng)建socket
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
std::cerr << "socket create error" << std::endl;
exit(2);
}
}
void start()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_serverport);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
{
std::cerr << "socket connect error" << std::endl;
}
else
{
std::string line;
std::string inbuffer;
while (true)
{
std::cout << "mycal>>> ";
std::getline(std::cin, line); // 1+1
Request req = ParseLine(line); // "1+1"
std::string content;
req.serialize(&content);
std::string send_string = enLength(content);
send(_sock, send_string.c_str(), send_string.size(), 0); // bug?? 不管
std::string package, text;
// "content_len"\r\n"exitcode result"\r\n
if (!recvPackage(_sock, inbuffer, &package))
continue;
if (!deLength(package, &text))
continue;
// "exitcode result"
Response resp;
resp.deserialize(text);
std::cout << "exitCode: " << resp.exitcode << std::endl;
std::cout << "result: " << resp.result << std::endl;
}
}
}
Request ParseLine(const std::string &line)
{
// 建議版本的狀態(tài)機(jī)!
//"1+1" "123*456" "12/0"
int status = 0; // 0:操作符之前,1:碰到了操作符 2:操作符之后
int i = 0;
int cnt = line.size();
std::string left, right;
char op;
while (i < cnt)
{
switch (status)
{
case 0:
{
if(!isdigit(line[i]))
{
op = line[i];
status = 1;
}
else left.push_back(line[i++]);
}
break;
case 1:
i++;
status = 2;
break;
case 2:
right.push_back(line[i++]);
break;
}
}
std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
return Request(std::stoi(left), std::stoi(right), op);
}
~CalClient()
{
if (_sock >= 0)
close(_sock);
}
private:
int _sock;
std::string _serverip;
uint16_t _serverport;
};
#include "calClient.hpp"
#include <memory>
using namespace std;
static void Usage(string proc)
{
cout<<"\nUasge:\n\t"<<proc<<" serverip serverport\n\n";
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<CalClient> tcli(new CalClient(serverip,serverport));
tcli->initClient();
tcli->start();
return 0;
}
Json的安裝
sudo yum install -y jsoncpp-devel
到了這里,關(guān)于【網(wǎng)絡(luò)】協(xié)議的定制與Json序列化和反序列化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!