当前位置:首页 » 《关注互联网》 » 正文

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

23 人参与  2024年12月18日 14:01  分类 : 《关注互联网》  评论

点击全文阅读


目录

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编码操作:

我们自己利用自定义协议将报文封装起来,方便解码判断是否是一个完整的报文。

编码后的报文:
有效载荷的长度 + 分隔符 + 有效载荷 + 分隔符

    const std::string SEP = "\r\n";    // 我们把tcp中读到的报文,可能读到半个,也可能读到1个半个, TCP 粘报问题    // 解决TCP的粘报问题    std::string Encode(const std::string &json_str)    {        int json_str_len = json_str.size();        std::string proto_str = std::to_string(json_str_len);        proto_str += SEP;        proto_str += json_str;        proto_str += SEP;        return proto_str;    }

2.3.2.解码操作

根据我们自己定义的协议,分离报头,分隔符后,看是否是一个完整的报头。

如果输入流还有内容,我们只需要从输入流中提取出一个完整的请求,剩余留着下一次处理!

    std::string Decode(std::string &inbuffer)    {        auto pos = inbuffer.find(SEP);        if (pos == std::string::npos)            return std::string();        std::string len_str = inbuffer.substr(0, pos);        if (len_str.empty())            return std::string();        int packlen = std::stoi(len_str);        int total = packlen + len_str.size() + 2 * SEP.size();        if (inbuffer.size() < total)            return std::string();        std::string package = inbuffer.substr(pos + SEP.size(), packlen);        inbuffer.erase(0, total);        return package;    }

2.4.如何使用Json进行序列化和反序列化

下面主要是主要代码

2.4.1.序列化

        bool Serialize(std::string *out)        {            // 转换成为字符串            Json::Value root;            root["result"] = _result;            root["code"] = _code;            Json::FastWriter writer;            // Json::StyledWriter writer;            *out = writer.write(root);            return true;        }

2.4.2.反序列化

        bool Deserialize(const std::string &in)        {            Json::Value root;            Json::Reader reader;            bool res = reader.parse(in, root);            if (!res)                return false;            _result = root["result"].asInt();            _code = root["code"].asInt();            return true;        }

3.封装socket类

3.1.封装思想

将socket系列操作分类封装,设计为基类,派生出Tcp和Udp两种具体的Socket!基类都需要进行创建socket文件 、进行绑定、 进入listen 、获取链接、 申请链接…由于两种类的操作方式不一致,所以基类只需要进行一个声明就可以,具体实现在派生类中完成!

以后直接调用相应接口即可,非常优雅!TcpSocket继承Socket类 成员变量 sockfd

3.2.基本框架

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

3.3.代码实现

#pragma once#include <iostream>#include <string>#include <functional>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <cstring>#include <pthread.h>#include <sys/types.h>#include <memory>#include "InetAddr.hpp"#include "Log.hpp"// 模版方法模式namespace socket_ns{    class Socket;    const static int gbacklog = 8;    using socket_sptr = std::shared_ptr<Socket>;    enum    {        SOCKET_ERROR = 1,        BIND_ERROR,        LISTEN_ERROR,        USAGE_ERROR    };    // std::unique_ptr<Socket> listensock = std::make_unique<TcpSocket>();    // listensock->BuildListenSocket();    // std::unique_ptr<Socket> clientsock = std::make_unique<TcpSocket>();    // clientsock->BuildClientSocket();    // clientsock->send();    // clientsock->Recv();    class Socket    {    public:        virtual void CreateSocketOrDie() = 0;        virtual void BindSocketOrDie(InetAddr &addr) = 0;        virtual void ListenSocketOrDie() = 0;        virtual socket_sptr Accepter(InetAddr *addr) = 0;        virtual bool Connetcor(InetAddr &addr) = 0;        virtual void SetSocketAddrReuse() = 0;        virtual int SockFd() = 0;        virtual int Recv(std::string *out) = 0;        virtual int Send(const std::string &in) = 0;        virtual void Close() = 0;        // virtual void Recv() = 0;        // virtual void Send() = 0;        // virtual void other() = 0;    public:        void BuildListenSocket(InetAddr &addr)        {            CreateSocketOrDie();            SetSocketAddrReuse();            BindSocketOrDie(addr);            ListenSocketOrDie();        }        bool BuildClientSocket(InetAddr &addr)        {            CreateSocketOrDie();            return Connetcor(addr);        }        // void BuildUdpSocket()        // {        //     CreateSocketOrDie();        //     BindSocketOrDie();        // }    };    class TcpSocket : public Socket    {    public:        TcpSocket(int fd = -1) : _sockfd(fd)        {        }        void CreateSocketOrDie() override        {            // 1. 创建流式套接字            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);            if (_sockfd < 0)            {                LOG(FATAL, "socket error");                exit(SOCKET_ERROR);            }            LOG(DEBUG, "socket create success, sockfd is : %d\n", _sockfd);        }        void BindSocketOrDie(InetAddr &addr) override        {            // 2. bind            struct sockaddr_in local;            memset(&local, 0, sizeof(local));            local.sin_family = AF_INET;            local.sin_port = htons(addr.Port());            local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());            int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));            if (n < 0)            {                LOG(FATAL, "bind error\n");                exit(BIND_ERROR);            }            LOG(DEBUG, "bind success, sockfd is : %d\n", _sockfd);        }        void ListenSocketOrDie() override        {            int n = ::listen(_sockfd, gbacklog);            if (n < 0)            {                LOG(FATAL, "listen error\n");                exit(LISTEN_ERROR);            }            LOG(DEBUG, "listen success, sockfd is : %d\n", _sockfd);        }        socket_sptr Accepter(InetAddr *addr) override        {            struct sockaddr_in peer;            socklen_t len = sizeof(peer);            int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);            if (sockfd < 0)            {                LOG(WARNING, "accept error\n");                return nullptr;            }            *addr = peer;            socket_sptr sock = std::make_shared<TcpSocket>(sockfd);            return sock;        }        virtual bool Connetcor(InetAddr &addr)        {            // tcp client 要bind,不要显示的bind.            struct sockaddr_in server;            // 构建目标主机的socket信息            memset(&server, 0, sizeof(server));            server.sin_family = AF_INET;            server.sin_port = htons(addr.Port());            server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());            int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));            if (n < 0)            {                std::cerr << "connect error" << std::endl;                return false;            }            return true;        }        void SetSocketAddrReuse() override        {            int opt = 1;            ::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));        }        int Recv(std::string *out) override        {            char inbuffer[4096];            ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);            if (n > 0)            {                inbuffer[n] = 0;                *out = inbuffer; // ??? +=            }            return n;        }        int Send(const std::string &in) override        {            int n = ::send(_sockfd, in.c_str(), in.size(), 0);            return n;        }        int SockFd() override        {            return _sockfd;        }        void Close() override        {            if (_sockfd > -1)                ::close(_sockfd);        }    private:        int _sockfd;    };    // class SocketFactor    // {    // public:    //     void Build    // }} // namespace socket_ns

点击全文阅读


本文链接:http://m.zhangshiyu.com/post/203055.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1