文章目录
网络分层十分复杂,但是我们开发的大部分网络程序,均在应用层上运行
(1)协议
OSI分层模型中,每一层都有自己的协议,还是那句话协议是双方建立的约定,在应用层也是这样。读写数据时,实际上是按照比特位进行接受的,那么对于一些结构化的数据应该怎样传输呢,这样就需要我们制定出一些协议。这种打包数据和解包数据的过程称之为序列化和反序列化
- 序列化:把对象转为字节序列
- 反序列化:把字节序列恢复为对象
(2)网络版“计算器”
根据之前学习过的套接字编程,我们可以编写一个网络版的计算器。代码如下
Server.cpp :服务器接受客户端的发来的操作数和运算符完成相应的计算
#include "Server.h"
int main(int argc,char* argv[])
{
if(argc!=2)
{
cout<<"端口号未传入"<<endl;
exit(1);
}
Server* se=new Server(atoi(argv[1]));
se->InitServer();
se->StartServer();
return 0;
}
Server.h
#include <iostream>
#include <signal.h>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "protocol.h"
using namespace std;
class Server
{
private:
int _port;
int _listen_socket;
public:
Server(int port):_port(port)
{}
void InitServer()
{
signal(SIGCHLD,SIG_IGN);
_listen_socket=socket(AF_INET,SOCK_STREAM,0);
if(_listen_socket < 0)
{
cout<<"socket creat faile"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_listen_socket,(struct sockaddr*)&local,sizeof(local)) < 0)
{
cout<<"socket bind faile"<<endl;
exit(3);
}
if(listen(_listen_socket,5) < 0)
{
cout<<"listen fail"<<endl;
exit(4);
}
}
void Service(int sock)
{
//服务器执行计算
request_t rq;
response_t rp={4,0};//默认状态码从4开始
ssize_t s=recv(sock,&rq,sizeof(rq),0);//接受客户端发来的计算要求
if(s > 0)
{
switch(rq.op)//判断是什么操作数
{
case '+':
rp.result=rq.x+rq.y;
break;
case '-':
rp.result=rq.x-rq.y;
break;
case '*':
rp.result=rq.x*rq.y;
break;
case '/':
if(rq.y!=0)
{
rp.result=rq.x/rq.y;
}
else
{
rp.code=1;//状态码为1表示除数为0
}
break;
case '%':
if(rq.y!=0)
{
rp.result=rq.x%rq.y;
}
else
{
rp.code=2;//状态码为2表示%运算时除数为0
}
default:
rp.code=3;//状态码为3表示运算符输入错误
}
}
send(sock,&rp,sizeof(rp),0);//把结果发给客户端
close(sock);
}
void StartServer()
{
struct sockaddr_in peer;
while(1)
{
socklen_t len=sizeof(peer);
int sock=accept(_listen_socket,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
cout<<"accept faile"<<endl;
continue;
}
pid_t id=fork();
if(id == 0)
{
close(_listen_socket);
Service(sock);
exit(0);
}
close(sock);
}
}
~Server()
{
close(_listen_socket);
}
};
Client.cpp:客户端将数据发送给服务端
#include "Client.h"
int main(int argc,char* argv[])
{
if(argc!=3)
{
cout<<"请完整输入ip地址和端口号"<<endl;
exit(1);
}
Client* cp=new Client(argv[1],atoi(argv[2]));
cp->InitClient();
cp->StartClient();
return 0;
}
Client.h
#include <iostream>
#include <signal.h>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "protocol.h"
using namespace std;
class Client
{
private:
int _port;
string _ip;//服务器ip地址
int _socket;
public:
Client(string ip,int port):_ip(ip),_port(port)
{}
void InitClient()
{
signal(SIGCHLD,SIG_IGN);
_socket=socket(AF_INET,SOCK_STREAM,0);
if(_socket < 0)
{
cout<<"socket creat faile"<<endl;
exit(2);
}
}
void StartClient()
{
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(_port);
server.sin_addr.s_addr=inet_addr(_ip.c_str());
if(connect(_socket,(struct sockaddr*)&server,sizeof(server)) < 0)
{
cout<<"connected fail"<<endl;
exit(2);
}
request_t rq;//客户端接收用户输入
cout<<"请输入左操作数:"<<endl;
cin>>rq.x;
cout<<"请输入右操作数:"<<endl;
cin>>rq.y;
cout<<"请输入运算符:"<<endl;
cin>>rq.op;
send(_socket,&rq,sizeof(rq),0);
response_t rp;//接收结果
recv(_socket,&rp,sizeof(rp),0);
cout<<"状态码为:"<<rp.code<<endl;
cout<<"结果为:"<<rp.result<<endl;
}
~Client()
{
close(_socket);
}
};
Protocol.h:服务端和客户端都必须遵从的计算协议
#ifndef _PROTOCOL_H
#define _PROTOCOL_H
#include <iostream>
typedef struct request
{
int x;//操作数1
int y;//操作数2
char op;//运算符
}request_t;
typedef struct response
{
int code;//返回状态,有可能计算失败
int result;//计算结果
}response_t;
#endif
关于这份代码有以下几点需要强调
- 所谓的协议:指的就是对于客户端和服务端他们都要认为x是左操作数,y是右操作数,并且右操作数在进行某些运算时不能为0;如果出现非法计算,客户端和服务单都要明白不同状态码代表的意思是什么;客户端发送时发送了一个request,接受时接受了一个response
- 上述过程其实已经完成了序列化和反序列化,之所以没有明显的感觉是因为客户端发送时发送了一个
request
,接受时接受了一个response
,他们都是非常标准的结构化数据,在发送时是二进制流,但是解包时被当做了标准的结构体解包 -
这种“序列化后反序列化”的方式是严重不推荐的,因为客户端和服务端可能在完全不同的环境中。实际工作中可以使用正规的序列化工具,如
json
,xml
代码效果如下