当前位置: 首页 > news >正文

深入了解linux网络—— 自定义协议(上)

序列化和反序列化

我们知道,协议是一种约定;且在调用soccket相关通信接口时,都是以字符串的形式发送信息。

如果,要传输一些结构化数据呢?协议也是双方约定好的一种结构化数据

例如,现在要实现一个网络版本的计算器,客户端就要将要计算的数字和运算符发送给服务端,由服务端处理完毕之后再返回给客户端。

在客户端就势必会存在Request结构化字段,其中存储在要运算的数字xy和运算符oper

而客户端要发送一个类似于1+1的字符串给服务端,为了保证服务端在接收到消息字符串时知道如何去处理;

就要做好约定:字符串中存在两个操作数,两个数字之间存在一个操作符,操作符可能是+-*/

这里在客户端和服务端就存在结构化字段RequestResponce

在发送信息时将结构化字段转化为字符串信息。在接受到字符串信息后,也能将字符串转化为结构化数据。

序列化:将结构化信息转化为字符串信息

反序列化:将字符串信息转化为结构化信息

这里无论如何去实现,只要保证一端发送的数据,在另一端能够正确的进行解析即可。

而这种约定,就是 应用层协议

所以,在协议当中就势必要存在序列化和反序列化的相关方法

理解readwriterecvsendTCP支持全双工

我们知道TCP在进行通信时是支持全双工的(可以同时读写)

读写相关接口readwriterecvsend都是支持全双工的;如何理解呢?

  • 这里,在任何一台主机上,TCP连接既有发送缓冲区,也有接受缓冲区;所以就支持全双工(发送信息的同时,也可以接受信息)
  • writesend接口,本质上就是将数据拷贝到TCP的发送缓冲区中;而readrecv接口本质上就是从TCP的接受缓冲区中将数据拷贝到内核中。
  • 对于数据什么时候发送、发送多少、出错了怎么办都由TCP控制;TCP传输控制协议。

在这里插入图片描述

那也就是说,我们之前使用的readwrite接口都是将数据交给了操作系统,也都是从操作系统中读取数据。

我们找直到TCP是面向字节流的,而之前的文件也是面向字节流的。

之前在使用writeread进行文件读写时,写端可以调用了多次write,而读端可能一次调用read就将写端写的所有信息都读取出来了;也可以写端写了一半的数据被读取上来了。

所以,协议不仅要提供序列化和反序列化的方法;还要保证读取到的报文的完整性

socket封装

这里简单对socket进行封装,使用模版设计模式:

这里设计一个基类Socket,其中包含纯虚函数:对socketbindconnect等的封装。

而基类中还存在CreateTcpServerSocket方法,其中调用对socketbind等封装好的纯虚方法。

class Socket
{
public:virtual void SocketOrDie() = 0;virtual void BindOrDie() = 0;virtual void ListenOrDie() = 0;virtual void AcceptOrDie() = 0;
public:void CreateTcpServerSocket(){SocketOrDie();BindOrDie();ListenOrDie();}
};

这里只罗列出了部分方法,在后续实现中进一步完善其中方法。

有了Socket,现在就要实现TcpSocket,而TcpSocket类就要继承Socket类,实现Socket中的纯虚方法。

Tcp创建套接字:socketbindlisten这里就不详细介绍了。

class TcpSocket : public Socket
{
public:void SocketOrDie() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(Level::FATAL) << "socket error";exit(SOCKET_ERR);}LOG(Level::DEBUG) << "socket success, sockfd : " << _sockfd;}void BindOrDie(int port) override{InetAddr addr(port);int n = bind(_sockfd, addr.GetInetAddr(), addr.GetLen());if (n < 0){LOG(Level::FATAL) << "bind error";exit(SOCKET_ERR);}LOG(Level::DEBUG) << "bind success, sockfd : " << _sockfd;}void ListenOrDie(int backlog) override{int n = listen(_sockfd, backlog);if (n < 0){LOG(Level::FATAL) << "listen error";exit(SOCKET_ERR);}LOG(Level::DEBUG) << "listen success, sockfd : " << _sockfd;}
private:int _sockfd;
};

要绑定端口号,服务端就只需要端口号,这里端口号就通过参数传递;

listen的第二个参数backlog,这里也设置也可以通过参数传递,且也设置了缺省参数。

这些参数都由调用用CreateTcpServerSocket来传递。

//Socket类
class Socket
{
protected:virtual void SocketOrDie() = 0;virtual void BindOrDie(int port) = 0;virtual void ListenOrDie(int backlog) = 0;virtual int AcceptOrDie() = 0;
public:void CreateTcpServerSocket(int port, int backlog = 6){SocketOrDie();BindOrDie(port);ListenOrDie(backlog);}
};

有了上述这些,服务端就只需要调用CreateTcpServerSocket,传递端口号和backlog(可以不传);即可创建套接字、绑定端口号和设置监听状态。

TcpServer封装实现

有了上述实现的TcpSocket,现在先来完善一点TcpServer

对于TcpServer成员,这里就设置成智能指针对象;基类Socket智能指针执行派生类对象。

对于TcpServer构造函数,只需要调用Socket类中的CreateTcpSeverSocket方法将端口号传递进去即可。

class TcpServer
{
public:TcpServer() {}TcpServer(int port) : _socket(std::make_unique<TcpSocket>()){_socket->CreateTcpServerSocket(port);}private:std::unique_ptr<Socket> _socket;
};

这里简单测试一下,创建TcpServer对象,然后程序休眠,查看一下日志和listen状态即可。

在这里插入图片描述

可以看到,创建套接字、绑定端口号和设置监听状态都是成功的。

那现在就要让服务器运行起来,就要有accept获取连接请求。

accept封装

TcpSocket实现AcceptOrDie,对accept的封装。(AcceptOeDie返回值暂时设置为int

accept除了获取连接请求外,还会获取对方的struct sockaddr_in和长度,这里就通过输出性参数见对方的sockaddr传递出去(这里使用封装好的InetAddr即可

//Socket
class Socket
{
protected:virtual int AcceptOrDie(InetAddr *addr) = 0;
public:
};class TcpSocket : public Socket
{
public:int AcceptOrDie(InetAddr *addr) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);int fd = accept(_sockfd, (struct sockaddr *)&peer, &len);if (fd < 0){LOG(Level::FATAL) << "accept error";return -1;}LOG(Level::DEBUG) << "accept success";addr->Set(peer);return fd;}
private:int _sockfd;
};

这里如果获取连接请求失败,就直接返回-1,由调用方去处理accept的情况。

上述中返回的是accept返回的,用来通信的文件描述符;

但是这里我们都对socket进行了封装,这里就可以直接返回一个std::shared_ptr<TcpSocket>的智能指针对象;

这样在调用读写操作时,就可以面向对象式调用。(统一化,TcpServer包含的就是指向Socket的智能指针对象)

//Socket
class Socket
{
protected:// virtual int AcceptOrDie(InetAddr *addr) = 0;virtual std::shared_ptr<Socket> AcceptOrDie(InetAddr *addr) = 0;
public:
};
class TcpSocket : public Socket
{
public:TcpSocket(){}TcpSocket(int fd) : _sockfd(fd) {}std::shared_ptr<Socket> AcceptOrDie(InetAddr *addr) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);int fd = accept(_sockfd, (struct sockaddr *)&peer, &len);if (fd < 0){LOG(Level::FATAL) << "accept error";return nullptr;}LOG(Level::DEBUG) << "accept success";addr->Set(peer);return std::make_shared<TcpSocket>(fd);}private:int _sockfd;
};

实现了对accept的封装,那在TcpServer中,运行时,只需要通过智能指针对象调用AcceptOrDie既可以获得用来通信的TcpSocket

获取连接请求成功之后,就要进行通信,这里使用多进程版;

子进程创建子进程,让孙子进程去执行。

子进程和父进程都要关闭不要的文件描述符,那对应的Socket就还需要提供一个Close方法来关闭文件描述符。

对于孙子进程如何进行服务:

这里,就通过回调函数,由上层去决定如何进行服务。(这里要自定义协议,就先使用回调函数)

using func_t = std::function<void(std::shared_ptr<Socket> fd, InetAddr &client)>;
class TcpServer
{
public:TcpServer() {}TcpServer(int port, func_t func) : _socket(std::make_unique<TcpSocket>()), _func(func){_socket->CreateTcpServerSocket(port);}void Start(){while (true){InetAddr peer;auto fd = _socket->AcceptOrDie(&peer);if (fd == nullptr){exit(ACCEPT_ERR);}// 通信int id = fork();if (id < 0){LOG(Level::FATAL) << "fork error";exit(FORK_ERR);}else if (id == 0){_socket->Close();if (fork() > 0)exit(OK);_func(fd, peer);}else{// 父进程fd->Close();waitpid(id, nullptr, 0);}}}private:std::unique_ptr<Socket> _socket;func_t _func;
};//Socket
class Socket
{
public:virtual void Close() = 0;
};
class TcpSocket : public Socket
{
public:void Close() override{close(_sockfd);}
private:int _sockfd;
};
//TcpServer
using func_t = std::function<void(std::shared_ptr<Socket> fd, InetAddr &client)>;
class TcpServer
{
public:TcpServer() {}TcpServer(int port, func_t func) : _socket(std::make_unique<TcpSocket>()), _func(func){_socket->CreateTcpServerSocket(port);}void Start(){while (true){InetAddr peer;auto fd = _socket->AcceptOrDie(&peer);if (fd == nullptr){exit(ACCEPT_ERR);}// 通信int id = fork();if (id < 0){LOG(Level::FATAL) << "fork error";exit(FORK_ERR);}else if (id == 0){_socket->Close();if (fork() > 0)exit(OK);_func(fd, peer);}else{// 父进程fd->Close();waitpid(id, nullptr, 0);}}}
private:std::unique_ptr<Socket> _socket;func_t _func;
};

这样,服务器在获取连接请求成功后,就让孙子进程(孤儿进程),去完成服务,父进程继续等待连接请求。

至于如何进行服务,就由上层传递的回调函数来决定。

自定义协议

这里要实现网页版计算器,我们就要制定相关协议(结构化数据、序列化反序列化等等)

要自定义协议,首先要有结构化数据,这里定义Request(请求结构化字段)、Responce(结果结构化字段)以及协议字段protocol

class Request
{
public:Request() {}Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}private:int _x;int _y;char _oper;
};class Responce
{
public:Responce() {}Responce(int result, int code) : _result(result), _code(code) {}private:int _result; // 结果int _code;   // 标识计算是否出错
};
class protocol
{public:
};

到这里,本篇文章大致内容就结束了

简答总结:

  • 序列化和反序列化
  • 理解TCP面向字节流,支持全双工。
  • 协议需要提供对应的序列化和反序列化方法、并且要保证读取到报文的完整性。
  • socket的封装、TcpServer的封装实现
  • 自定义协议:结构化数据ResquestResponce

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

http://www.dtcms.com/a/462779.html

相关文章:

  • 大连网站制作最好的公司建设企业网站得花多少
  • 服装公司网站源码给网站做seo的价格
  • 购买型网站建设青岛网站建设公司好找吗
  • 在网站建设中遇到的问题手工制作国庆节作品图片
  • 了游*薄俐仔斗哆消仔*游戏程序系统方案
  • jsp网站加载慢开发一款app的公司
  • 如何建设网站pdf下载个人网页制作成品代码免费
  • 邢台网站建设多少钱WordPress考试
  • 网站的集约化建设锦江建设和交通局网站
  • 长沙建网站企业网站建设流程共有几个阶段
  • 做网站人家直接百度能搜到的品牌建设费用
  • MQ 面试宝典
  • 《棒球运动规则》一级运动员年龄限制·棒球1号位
  • 中国建设银行网站 个人西部数码网站管理助手 提权
  • Linux基本指令(下)
  • 手机网站用什么软件开发html5 企业网站
  • IDEA在plugins里搜不到mybatisx插件的解决方法
  • 广州网站建设多少钱深圳网上申请个人营业执照
  • 高端响应式网站建设wordpress动态插件
  • fastadmin列表头部加按钮,点击弹出窗口提交数据保存
  • 网站进行中英文转换怎么做新手销售怎么和客户交流
  • MySQL索引调优之索引顺序是否应该“匹配查询书写顺序”?
  • 安阳网站建设开发用五百丁做名字的简历网站
  • 企业做网站的注意事项沈阳商城网站开发
  • 堆:数组中的第K个最大数
  • 如何添加网站 ico图标小游戏开发需要多少钱
  • printf输出乱码的解决办法
  • 汕头做网站优化的公司两个wordpress
  • 网站域名在哪里如何建立一个免费的网站
  • 重庆交通建设监理协会网站建设银行招标网站