【Linux】深入理解网络编程:应用层自定义协议、序列化、TCP粘包题目与Sock ...

打印 上一主题 下一主题

主题 907|帖子 907|积分 2721

目次
1.应用层
1.1.再谈应用层
1.2.再谈“协议”
2.初识序列化与反序列化
2.1.基本概念
为什么要转换成字符串在发送呢?
2.2.重新理解 read、write、recv、send 和 tcp 为什么支持全双工
我们的read大概recv为什么会壅闭?
2.3.如何办理粘包题目
2.3.1编码操作:
2.3.2.解码操作
2.4.如何使用Json举行序列化和反序列化
2.4.1.序列化
2.4.2.反序列化
3.封装socket类
3.1.封装头脑
3.2.基本框架
3.3.代码实现

1.应用层

1.1.再谈应用层

我们程序员写的一个个办理我们现实题目, 满意我们日常需求的网络程序, 都是在应用层。
1.2.再谈“协议”

根据前面的知识我们知道了协议是一种 "约定"。
socket api 的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的。 假如我们要传输一些 "结构化的数据" 怎么办呢?
协议的真正概念:
其实,协议就是双方约定好的结构化的数据!
2.初识序列化与反序列化


2.1.基本概念



  • 序列化:把信息由多变一,方便网络发送。
  • 反序列化:把信息由一变多,方便上层处理。
举个例子:
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要盘算的两个加数发过去, 然后由服务器举行盘算, 最后再把结果返回给客户端。
约定方案:


  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时间再按照相同的规则把字符串转化回结构体;
这就是序列化和反序列化!
为什么要转换成字符串在发送呢?

向上通过反序列化读取消息,向下通过序列化包装消息。而TCP/UDP不关心发送的是什么,都按照字符串举行传输!
2.2.重新理解 read、write、recv、send 和 tcp 为什么支持全双工




  • 在任何一台主机上,TCP 毗连既有发送缓冲区,又有接受缓冲区,以是,在内核中,可以在发消息的同时,也可以收消息,即全双工
  • 这就是为什么一个 tcp sockfd 读写都是它的缘故原由
  • 现实数据什么时间发,发多少,出错了怎么办,由 TCP 控制,以是 TCP 叫做传输控制协议。这就体现控制!传输层的题目都是由OS自主来决定的!
tcp能接受全双工的本质缘故原由是因为TCP毗连各有一对发送缓冲区和接受缓冲区。
tcp发送数据的本质:将自己的发送缓冲区拷贝到接收方的接受缓冲区中!
通信的本质就是拷贝!!!
read、write、recv、send本质是拷贝函数!
我们的read大概recv为什么会壅闭?

因为缓冲区中没有数据,壅闭的本质就是用户层在同步!
Tcp设计也是符合生产者消费者模子!因为发送缓冲区和接收缓冲区都是属于操作系统的,以是肯定是临界资源!会有多个生产者,多个消费者!而IO发生壅闭也就是为了维护同步关系,保证缓冲区的正确使用!
接受的时间,我们还需要办理一个题目,保证我们拿到的是一个完整请求!
举个例子:
对方的接收缓冲区写满了,对方不停不读,那么我们的发送缓冲区就积存了许多同样的请求,假如一次性革新过去,对方就读取到多条信息;又大概只发送了一条请求的一半过去,那么接受方读取就读取一半了,就不可能举行反序列化!这个过程就叫面向字节流!!!
以是怎么保证读取的是一个完整的请求呢???
这就是经典的TCP的粘包题目了!
2.3.如何办理粘包题目

需要我们自定义协议出马了!(协议的再理解)
   我们自己规定协议如下:
  报文 = 报头+有用载荷
  "有用载荷的长度"\r\n"有用载荷"\r\n 
  "len"\r\n"_x _op _y"\r\n  -> len: 有用载荷的长度,约定\r\n是分隔符,不参与统计
  2.3.1编码操作:

我们自己利用自定义协议将报文封装起来,方便解码判断是否是一个完整的报文。
编码后的报文:
有用载荷的长度 + 分隔符 + 有用载荷 + 分隔符
  1.     const std::string SEP = "\r\n";
  2.     // 我们把tcp中读到的报文,可能读到半个,也可能读到1个半个, TCP 粘报问题
  3.     // 解决TCP的粘报问题
  4.     std::string Encode(const std::string &json_str)
  5.     {
  6.         int json_str_len = json_str.size();
  7.         std::string proto_str = std::to_string(json_str_len);
  8.         proto_str += SEP;
  9.         proto_str += json_str;
  10.         proto_str += SEP;
  11.         return proto_str;
  12.     }
复制代码
2.3.2.解码操作

根据我们自己定义的协议,分离报头,分隔符后,看是否是一个完整的报头。
假如输入流还有内容,我们只需要从输入流中提取出一个完整的请求,剩余留着下一次处理!
  1.     std::string Decode(std::string &inbuffer)
  2.     {
  3.         auto pos = inbuffer.find(SEP);
  4.         if (pos == std::string::npos)
  5.             return std::string();
  6.         std::string len_str = inbuffer.substr(0, pos);
  7.         if (len_str.empty())
  8.             return std::string();
  9.         int packlen = std::stoi(len_str);
  10.         int total = packlen + len_str.size() + 2 * SEP.size();
  11.         if (inbuffer.size() < total)
  12.             return std::string();
  13.         std::string package = inbuffer.substr(pos + SEP.size(), packlen);
  14.         inbuffer.erase(0, total);
  15.         return package;
  16.     }
复制代码
2.4.如何使用Json举行序列化和反序列化

下面重要是重要代码
2.4.1.序列化

  1.         bool Serialize(std::string *out)
  2.         {
  3.             // 转换成为字符串
  4.             Json::Value root;
  5.             root["result"] = _result;
  6.             root["code"] = _code;
  7.             Json::FastWriter writer;
  8.             // Json::StyledWriter writer;
  9.             *out = writer.write(root);
  10.             return true;
  11.         }
复制代码
2.4.2.反序列化

  1.         bool Deserialize(const std::string &in)
  2.         {
  3.             Json::Value root;
  4.             Json::Reader reader;
  5.             bool res = reader.parse(in, root);
  6.             if (!res)
  7.                 return false;
  8.             _result = root["result"].asInt();
  9.             _code = root["code"].asInt();
  10.             return true;
  11.         }
复制代码
3.封装socket类

3.1.封装头脑

将socket系列操作分类封装,设计为基类,派生出Tcp和Udp两种具体的Socket!基类都需要举行创建socket文件 、举行绑定、 进入listen 、获取链接、 申请链接…由于两种类的操作方式不一致,以是基类只需要举行一个声明就可以,具体实现在派生类中完成!
以后直接调用相应接口即可,非常优雅!TcpSocket继承Socket类 成员变量 sockfd
3.2.基本框架

通过这些操作的组合,可以举行建立监听链接 ,建立客户端毗连等操作,十分方便!这种设计模式是模版方法设计模式!!!

3.3.代码实现

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <functional>
  5. #include <sys/types.h> /* See NOTES */
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>
  10. #include <cstring>
  11. #include <pthread.h>
  12. #include <sys/types.h>
  13. #include <memory>
  14. #include "InetAddr.hpp"
  15. #include "Log.hpp"
  16. // 模版方法模式
  17. namespace socket_ns
  18. {
  19.     class Socket;
  20.     const static int gbacklog = 8;
  21.     using socket_sptr = std::shared_ptr<Socket>;
  22.     enum
  23.     {
  24.         SOCKET_ERROR = 1,
  25.         BIND_ERROR,
  26.         LISTEN_ERROR,
  27.         USAGE_ERROR
  28.     };
  29.     // std::unique_ptr<Socket> listensock = std::make_unique<TcpSocket>();
  30.     // listensock->BuildListenSocket();
  31.     // std::unique_ptr<Socket> clientsock = std::make_unique<TcpSocket>();
  32.     // clientsock->BuildClientSocket();
  33.     // clientsock->send();
  34.     // clientsock->Recv();
  35.     class Socket
  36.     {
  37.     public:
  38.         virtual void CreateSocketOrDie() = 0;
  39.         virtual void BindSocketOrDie(InetAddr &addr) = 0;
  40.         virtual void ListenSocketOrDie() = 0;
  41.         virtual socket_sptr Accepter(InetAddr *addr) = 0;
  42.         virtual bool Connetcor(InetAddr &addr) = 0;
  43.         virtual void SetSocketAddrReuse() = 0;
  44.         virtual int SockFd() = 0;
  45.         virtual int Recv(std::string *out) = 0;
  46.         virtual int Send(const std::string &in) = 0;
  47.         virtual void Close() = 0;
  48.         // virtual void Recv() = 0;
  49.         // virtual void Send() = 0;
  50.         // virtual void other() = 0;
  51.     public:
  52.         void BuildListenSocket(InetAddr &addr)
  53.         {
  54.             CreateSocketOrDie();
  55.             SetSocketAddrReuse();
  56.             BindSocketOrDie(addr);
  57.             ListenSocketOrDie();
  58.         }
  59.         bool BuildClientSocket(InetAddr &addr)
  60.         {
  61.             CreateSocketOrDie();
  62.             return Connetcor(addr);
  63.         }
  64.         // void BuildUdpSocket()
  65.         // {
  66.         //     CreateSocketOrDie();
  67.         //     BindSocketOrDie();
  68.         // }
  69.     };
  70.     class TcpSocket : public Socket
  71.     {
  72.     public:
  73.         TcpSocket(int fd = -1) : _sockfd(fd)
  74.         {
  75.         }
  76.         void CreateSocketOrDie() override
  77.         {
  78.             // 1. 创建流式套接字
  79.             _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
  80.             if (_sockfd < 0)
  81.             {
  82.                 LOG(FATAL, "socket error");
  83.                 exit(SOCKET_ERROR);
  84.             }
  85.             LOG(DEBUG, "socket create success, sockfd is : %d\n", _sockfd);
  86.         }
  87.         void BindSocketOrDie(InetAddr &addr) override
  88.         {
  89.             // 2. bind
  90.             struct sockaddr_in local;
  91.             memset(&local, 0, sizeof(local));
  92.             local.sin_family = AF_INET;
  93.             local.sin_port = htons(addr.Port());
  94.             local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());
  95.             int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
  96.             if (n < 0)
  97.             {
  98.                 LOG(FATAL, "bind error\n");
  99.                 exit(BIND_ERROR);
  100.             }
  101.             LOG(DEBUG, "bind success, sockfd is : %d\n", _sockfd);
  102.         }
  103.         void ListenSocketOrDie() override
  104.         {
  105.             int n = ::listen(_sockfd, gbacklog);
  106.             if (n < 0)
  107.             {
  108.                 LOG(FATAL, "listen error\n");
  109.                 exit(LISTEN_ERROR);
  110.             }
  111.             LOG(DEBUG, "listen success, sockfd is : %d\n", _sockfd);
  112.         }
  113.         socket_sptr Accepter(InetAddr *addr) override
  114.         {
  115.             struct sockaddr_in peer;
  116.             socklen_t len = sizeof(peer);
  117.             int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);
  118.             if (sockfd < 0)
  119.             {
  120.                 LOG(WARNING, "accept error\n");
  121.                 return nullptr;
  122.             }
  123.             *addr = peer;
  124.             socket_sptr sock = std::make_shared<TcpSocket>(sockfd);
  125.             return sock;
  126.         }
  127.         virtual bool Connetcor(InetAddr &addr)
  128.         {
  129.             // tcp client 要bind,不要显示的bind.
  130.             struct sockaddr_in server;
  131.             // 构建目标主机的socket信息
  132.             memset(&server, 0, sizeof(server));
  133.             server.sin_family = AF_INET;
  134.             server.sin_port = htons(addr.Port());
  135.             server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());
  136.             int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
  137.             if (n < 0)
  138.             {
  139.                 std::cerr << "connect error" << std::endl;
  140.                 return false;
  141.             }
  142.             return true;
  143.         }
  144.         void SetSocketAddrReuse() override
  145.         {
  146.             int opt = 1;
  147.             ::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
  148.         }
  149.         int Recv(std::string *out) override
  150.         {
  151.             char inbuffer[4096];
  152.             ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
  153.             if (n > 0)
  154.             {
  155.                 inbuffer[n] = 0;
  156.                 *out = inbuffer; // ??? +=
  157.             }
  158.             return n;
  159.         }
  160.         int Send(const std::string &in) override
  161.         {
  162.             int n = ::send(_sockfd, in.c_str(), in.size(), 0);
  163.             return n;
  164.         }
  165.         int SockFd() override
  166.         {
  167.             return _sockfd;
  168.         }
  169.         void Close() override
  170.         {
  171.             if (_sockfd > -1)
  172.                 ::close(_sockfd);
  173.         }
  174.     private:
  175.         int _sockfd;
  176.     };
  177.     // class SocketFactor
  178.     // {
  179.     // public:
  180.     //     void Build
  181.     // }
  182. } // namespace socket_ns
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

卖不甜枣

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表