对网站建设的讲话,公司标志logo设计免费,做网站怎么插音频,mvc做网站文章中有使用封装好的头文件#xff0c;可以在下面连接处查询。
Linux相关博文中使用的头文件_Gosolo#xff01;的博客-CSDN博客 1. 应用层
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层
1.2 协议
我们在之前的套接字编程中使用的是…文章中有使用封装好的头文件可以在下面连接处查询。
Linux相关博文中使用的头文件_Gosolo的博客-CSDN博客 1. 应用层
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层
1.2 协议
我们在之前的套接字编程中使用的是基于字符串的通信客户端connect()之后通过getlne()来输入数据从而通过send()发送给服务端。如果我们要传输一些结构化的数据 怎么办呢 那就需要指定一些约定。这些约定构成了协议。 客户端发送一个形如1 1的字符串; 这个字符串中有两个操作数, 都是整形; 两个数字之间会有一个字符是运算符, ... 定义结构体来表示我们需要交互的信息; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; ... 2. 序列化与反序列化
2.1 序列化
以聊天软件为例发送人、发送消息、发送时间明明是三个消息但是通过一定手段合成一个报文统一的发给对方。
结构化的数据转为字符串数据。
2.2 反序列化
对方读到之后将该报文再读回三个消息分别装填到发送人、发送消息、发送时间。
字符串结构转为结构化的数据。
2.3 结构化数据和字节流式数据对比
为什么我们之前实现tcp,udp时就是传递的结构化的数据呢 一是因为协议一旦定制完成就不会轻易更改而这个协议可以支持网路通信使用结构化的数据。二是网络协议有一些大小端问题代码也有一些条件编译会自动识别平台设置好对齐的规则。内核级的协议定制解决了这些问题。 一般而言结构化的数据是给上层应用来使用的。而字节流的数据更适用于网络传输这样在上层业务和网络通信之间增加了一个软件层使其解耦。
3.网络版计算器 要实现使用结构化的数据进行传输首先需要有两个类一个用于发送待计算的数字和运算方法一个用于保存结果。假定输入格式为x_ op_ y_那还需要定义一个分隔符。
//封装到一个命名空间里 即没有命名冲突 到时候还可以展开 using namespace ns_protocol
namespace ns_protocol
{#define SPACE
#define SPACE_LEN strlen(SPACE)一个用于返回结果//请求class Request{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:Response(){}Response(int result, int code, int x, int y, char op) :result_(result),code_(code),x_(x),y_(y),op_(op){}~Response() {}//序列化//反序列化public:int result_;//返回结果int code_;//用于评判本次计算是否合法int x_;int y_;char op_;};
}
3.1 自定义协议
如果要想使用这个计算器那么需要满足一些约定。仅规定输入格式为_x _op _y够用吗
对于TCP而言其是面向字节流的IO读写操作其实本质上是拷贝函数。 send || write 其实就是将我们自己定义的buffer中的内容拷贝到客户端的发送缓冲区中。 recv || read 其实就是将服务端的接收缓冲区中拷贝到我们自己定义的buffer里面。 那这些缓冲区什么时候发发多少出错了怎么办都是由TCP传输控制协议中决定的。所以发送的次数和接收的次数没有任何关系。
所以如果我们自主实现协议就需要增加一些报文(特殊标识符)。来确定我们接收到的数据中是否能组成一组“合法”的操作。
所以我们规定输入格式为 length\r\nx_ op_ y_\r\n至少读够一个这样的字符串才开始执行。
#define SEP \r\n
#define SEP_LEN strlen(SEP)
3.1.1 序列化
std::string Requst::Serialize()
{std::string str;strstd::to_string(x_);strSPACE;strop_;strSPACE;strstd::to_string(y_);return str;
} std::string Response::Serialize()
{std::string s;s std::to_string(code_);s SPACE;s std::to_string(result_);return s;
}
3.1.2 反序列化
bool Requst::Deserialized(const std::string str)
{size_t leftstr.find(SPACE);if(leftstd::string::npos) return false;size_t rightstr.rfind(SPACE);if(rightstd::string::npos) return false;//左右两个空格都找到了x_atoi(str.substr(0,left).c_str());y_atoi(str.substr(rightSPACE_LEN).c_str());if (left SPACE_LEN str.size())return false;elseop_ str[left SPACE_LEN];return true;
} bool Respon::Deserialized(const std::string s)
{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;
}
3.2 使用 json定制协议
安装json
sudo yum install jsoncpp-develjson的使用
用{}的KV结构支持数组 3.3 添加报文
namespace ns_protocol
{//添加报文std::string Encode(std::string s){std::string new_package std::to_string(s.size());new_package SEP;new_package s;new_package SEP;return new_package;}//删除报文// length\r\nx_ op_ y_\r\n... //std::string Decode(std::string buffer){std::size_t pos buffer.find(SEP);if(pos std::string::npos) return ;//拿到报文前面那个length int size atoi(buffer.substr(0, pos).c_str());//拿到真实的有效载荷的长度int surplus buffer.size() - pos - 2*SEP_LEN;if(surplus size){//至少具有一个合法完整的报文, 可以动手提取了buffer.erase(0, posSEP_LEN);std::string s buffer.substr(0, size);//提取完成了 但是删干净 不影响下次进行判断buffer.erase(0, size SEP_LEN);return s;}else{return ;}}
}
3.4 接受信息和发送信息 因为客户端和用户端都需要调用recv()和send()而且这两端都需要包括协议文件所以将这两个接口封装到这里。
namespace ns_protocol
{ bool Recv(int sock, std::string *out){char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer)-1, 0); // 9\r\n123789\r\nif (s 0){buffer[s] 0;*out buffer;}else if (s 0){std::cout client quit std::endl;return false;}else{std::cout recv error std::endl;return false;}return true;}void Send(int sock, const std::string str){std::cout send in std::endl;int n send(sock, str.c_str(), str.size(), 0);if (n 0)std::cout send error std::endl;}}
4.客户端 服务端的代码逻辑
4.1 服务端 CalServer.cc
#include TcpServer.hpp
#include Protocol.hpp#include memory
#include signal.husing namespace ns_tcpserver;//这个是TcpServer.hpp内的命名空间
using namespace ns_protocol;static void Usage(const std::string process)
{std::cout \nUsage: process port\n std::endl;
}static Response calculatorHelper(const Request req)
{Response resp(0, 0, req.x_, req.y_, req.op_);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;elseresp.result_ req.x_ / req.y_;break;case %:if (0 req.y_)resp.code_ 2;elseresp.result_ req.x_ % req.y_;break;default:resp.code_ 3;break;}return resp;
}//返回值类型为void 参数类型为int 符合包装器
void calculator(int sock)
{std::string inbuffer;while(true){//1.读取数据bool resRecv(sock,inbuffer);if(!res) break;std::cout begin: inbuffer: inbuffer std::endl;//2.协议解析得到一个完整的报文std::string packageDecode(inbuffer);if(package.empty()){//进行下一次读取continue;}logMessage(NORMAL, %s, package.c_str());Request req;//3.进行反序列化将字符数据存进结构数据req.Deserialized(package);//4.对这些数据进行处理Response resp calculatorHelper(req);//5. 再序列化回去std::string respString resp.Serialize();//6.添加报文respString Encode(respString);//7.发送数据Send(sock, respString);}}
int main(int argc, char *argv[])
{if (argc ! 2){Usage(argv[0]);exit(1);}// 一般服务器都是要忽略SIGPIPE信号的防止在运行中出现非法写入而发生抛异常signal(SIGPIPE, SIG_IGN);std::unique_ptrTcpServer server(new TcpServer(atoi(argv[1])));server-BindService(calculator);//屏蔽信号是为了这里 server-Start();return 0;
} 4.2 服务端 CalClient.cc
#include iostream
#include Sock.hpp
#include Protocol.hppusing namespace ns_protocol;static void Usage(const std::string process)
{std::cout \nUsage: process serverIp serverPort\n std::endl;
}// ./client server_ip server_port
int main(int argc, char *argv[])
{if (argc ! 3){Usage(argv[0]);exit(1);}std::string server_ip argv[1];uint16_t server_port atoi(argv[2]);Sock sock;int sockfd sock.Socket();//连接if (!sock.Connect(sockfd, server_ip, server_port)){std::cerr Connect error std::endl;exit(2);}bool quit false;std::string buffer;while (!quit){// 1. 获取需求Request req;std::cout Please Enter # ;std::cin req.x_ req.op_ req.y_;// 2. 序列化std::string s req.Serialize();// 3. 添加长度报头s Encode(s);// 4. 发送给服务端Send(sockfd, s);// 5. 正常读取while (true){bool res Recv(sockfd, buffer);if (!res){quit true;break;}//去除报文std::string package Decode(buffer);if (package.empty())continue;Response resp;//反序列化resp.Deserialized(package);std::string err;switch (resp.code_){case 1:err 除0错误;break;case 2:err 模0错误;break;case 3:err 非法操作;break;default:std::cout resp.x_ resp.op_ resp.y_ resp.result_ [success] std::endl;break;}if(!err.empty()) std::cerr err std::endl;break;}}close(sockfd);return 0;
}
4.3 运行截图
注意不能少打空格 有bug 5. 守护进程
我们之前写的程序全部都是在前台进行的即启动服务器必须输入 ./CalServer 8080 如果我退出了XShell 这个进程就退出了。如果我们不想让它退出就需要让它守护进程化。 一些概念
前台进程和终端关联的进程。判断一个进程是不是前台进程看能否处理你的输入。 任何xshell登录只允许一个前台进程和多个后台进程。进程除了有自己的pidppid还有一个组id。在命令行中同时用管道启动多个进程多个进程是兄弟进程而同时被创建的多个进程可以成为一个进程组的概念组长一般是第一个进程。任何一次登录登录的用户需要有多个进程(组)来给这个用户提供服务。用户也可以自己启动很多进程(组)。我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务整体归纳到一个叫做会话的机制中。
守护进程化其实就是让进程变成一个单独的会话。使退出bash也不会影响这个进程的存活。
如何将自己变成自成会话呢
5.1 setsid
#include unistd.h
pid_t setsid(void); 让自己自成会话并变成这个进程组的组长。成功返回pid 失败返回-1 注 setsid要被成功调用必须保证当前进程不是进程组的组长。所以需要配合fork()使用。守护进程不能直接向显示器打印消息一旦打印会被暂停、终止。5.2 写一个函数 让调用的进程变成守护进程 目标 1.忽略SIGPIPESIGCHLD。 2.不要让自己成为组长 ——fork() 3.调用setsid() 4.标准输入、标准输出、标准错误的重定向。——重定向到哪里呢 /dev/null /dev/null 该文件是一个写入自动丢弃读入的时候不阻塞但是什么都读不到。
#pragma once#include iostream
#include unistd.h
#include signal.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hvoid MyDaemon()
{// 1. 忽略信号SIGPIPESIGCHLDsignal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);// 2. 不要让自己成为组长if (fork() 0)exit(0);// 3. 调用setsidsetsid();// 4. 标准输入标准输出标准错误的重定向,守护进程不能直接向显示器打印消息int devnull open(/dev/null, O_RDONLY | O_WRONLY);if(devnull 0){dup2(0, devnull);dup2(1, devnull);dup2(2, devnull);close(devnull);}
}使用