引言
理解网络协议里的协议二字,协议的本质就是一种约定.
下面我们自己定制协议,实现一个网络版本的计算器来帮助理解协议.
网路版的计算器
注释
socket编程就不详细解释了,有点套路化,具体的可以看这篇👉socket编程_
Makefile
.PHONY:all
all: client server
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f client server
protocol.hpp
#include <iostream>
namespace ns_protocol
{
struct Request
{
int x;
int y;
char op;//"+-*/%"
Request()
:x(0)
,y(0)
,op('+')
{
}
};
struct Response
{
int code;//状态码 0表示成功,其余表示错误
int result;//计算后的结果
Response()
:code(0)
,result(-1)
{
}
};
}
server.hpp
#include<iostream>
#include <unistd.h>
#include<sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include "protocol.hpp"
namespace ns_server
{
class Server
{
private:
uint16_t port;
int listen_sock;
public:
Server(uint16_t _port)
:port(_port)
,listen_sock(-1)
{
}
void InitServer()
{
//创建套接字
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"listen_sock err\n";
exit(2);
}
//绑定端口
sockaddr_in local;
socklen_t len=sizeof(local);
bzero(&local,len);
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,len)<0)
{
std::cerr<<"bind err\n";
exit(3);
}
//监听,创建链接
if(listen(listen_sock,5)<0)
{
std::cerr<<"listen err\n";
exit(5);
}
}
static void* calc(void* args)
{
pthread_detach(pthread_self());
int sock=*(int*)args;
delete (int*)args;
while(true)
{
ns_protocol::Request request;
ssize_t s=recv(sock,&request,sizeof(request),0);
if(s<0)
{
std::cerr<<"recv err,client quit,me too\n";
break;
}
else if(s==0)//关闭客户端就走这里
{
std::cout<<"recv end,client quit,me too\n";
break;
}
else
{
//拿到request
ns_protocol::Response resp;
switch(request.op)
{
case '+':
resp.result=request.x+request.y;
break;
case '-':
resp.result=request.x-request.y;
break;
case '*':
resp.result=request.x*request.y;
break;
case '/':
if(request.y==0)
{
resp.code=2;
}
else
{
resp.result=request.x/request.y;
}
break;
case '%':
if(request.y==0)
{
resp.code=3;
}
else
{
resp.result=request.x%request.y;
}
break;
default:
resp.code=-1;//操作失误
break;
}
send(sock,&resp,sizeof(resp),0);
}
}
close(sock);
return nullptr;
}
void Loop()
{
while(true)
{
struct sockaddr_in peer;
socklen_t len_peer=sizeof(peer);//不能是nullptr
bzero(&peer,sizeof(peer));
//获取链接
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len_peer);
if(sock<0)
{
std::cerr<<"accept err\n";
// exit(6);//客户端没连上应该continue而不是服务器退出,因为链接很频繁
continue;
}
//拿到sock后创建线程去处理
pthread_t tid;
int* p=new int(sock);
pthread_create(&tid,nullptr,calc,p);
}
}
~Server()
{
if(listen_sock>=0)
{
close(listen_sock);
}
}
};
}
clinet.hpp
#include<iostream>
#include <unistd.h>
#include<sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include "protocol.hpp"
#include <string>
namespace ns_client
{
class Client
{
private:
int sock;
uint16_t port;
std::string ip;
public:
Client(std::string _ip,uint16_t _port)
:sock(-1)
,port(_port)
,ip(_ip)
{
}
void InitClient()
{
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cerr<<"sock err\n";
exit(2);
}
//不需要绑定,直接链接服务器
}
void Run()
{
//connect
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
bzero(&peer,len);
peer.sin_family=AF_INET;
peer.sin_port=htons(port);
peer.sin_addr.s_addr=inet_addr(ip.c_str());
if(connect(sock,(sockaddr*)&peer,len)<0)
{
std::cerr<<"connect err\n";
exit(3);//没连接上直接退出
}
while(true)
{
ns_protocol::Request request;
std::cout<<"请输入第一个数->";
std::cin>>(request.x);
std::cout<<"请输入第二个数->";
std::cin>>(request.y);
std::cout<<"请输入操作符(+-*/%)->";
std::cin>>(request.op);
send(sock,&request,sizeof(request),0);
ns_protocol::Response resp;
ssize_t s=recv(sock,&resp,sizeof(resp),0);
if(s>0)
{
std::cout<<"code: "<<resp.code<<"\n";
std::cout<<"result: "<<resp.result<<"\n";
}
}
}
~Client()
{
if(sock>=0)
{
close(sock);
}
}
};
}
server.cc
#include "server.hpp"
void Usage(char* proc)
{
std::cout<<"Usage:\n\t"<<proc<<" local_port\n";
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
return 2;
}
ns_server::Server svr(atoi(argv[1]));
svr.InitServer();
svr.Loop();
return 0;
}
client.cc
#include "client.hpp"
void Usage(char* proc)
{
std::cout<<"Usage\n\t"<<proc<<" peer_ip peer_port\n";
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
return 1;
}
ns_client::Client cli(argv[1],atoi(argv[2]));
cli.InitClient();
cli.Run();
return 0;
}
运行效果
思考
我们先看一下protocol.hpp的内容
#include <iostream>
namespace ns_protocol
{
struct Request
{
int x;
int y;
char op;//"+-*/%"
Request()
:x(0)
,y(0)
,op('+')
{}
};
struct Response
{
int code;//状态码 0表示成功,其余表示错误
int result;//计算后的结果
Response()
:code(0)
,result(-1)
{
}
};
}
struct Request里的x是什么?y是什么?op是什么?
假如我们输入的op是’/',为什么一定是x/y?不能是y/x?
struct Response里的code又是啥?result又是啥?各自代表什么含义?
我们能正常使用这个网络版的计算器,因为这是我们自己定制的协议,我们规定一定是x/y,规定状态码code为0就表示成功.
网络计算器里的协议是我针对具体的场景设计的协议,可扩展性和健壮性不足.
在互联网里面有一些高频场景,所以有很多大佬已经给我们做好了很多应用层的协议,我们就不用自己设计协议了,只需要学习了解他们定制的协议就好了.比如http协议,所以之后我们的任务就是学习了解一些常用协议,然后自己可以编写小部分协议.
当然不排除一些特殊场景需要我们自己写协议,比如游戏领域.