什么是协议?
协议就是一种约定,类似客户端与服务器之间为便于交互而制定的约定;
TCP简易版网络计算器
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最 后再把结果返回给客户端
方案一:
客户端发送类似于"1+1"的字符串,server收到以后进行解析,计算出结果再返回给客户端;
则有规定:
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
方案二:
定义结构体来表示我们需要交互的信息;
规定:
//请求
struct Request{
int a;//操作数a
int b;//操作数b
char op;//操作类型:+-*/%
};//规定进行a op b运算;
//响应
struct Response{
int statu = 0;//返回状态码,正常0,异常-1;
int ret;//运算结果:规定返回a op b 的结果 ret;
};
显然方案一和方案二都能完成需求,上述的规定,就是一种server与client为了方便交互而进行的约定,也就是一种协议,下面采取方案2实现;
protocol.hpp(协议)
//请求
struct Request{
int a;//操作数a
int b;//操作数b
char op;//操作类型:+-*/%
};//规定进行a op b运算;
//响应
struct Response{
int statu = 0;//返回状态码,正常0,异常-1;
int ret;//运算结果:规定返回a op b 的结果 ret;
};
server.hpp
#pragma once
#include "protocol.hpp"
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdio>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <pthread.h>
#include <string>
using namespace std;
class TcpServer
{
int listen_sock;
int _port;
public:
TcpServer(int port) : _port(port)
{
}
void Init()
{
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(_port);
server.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_sock, (sockaddr *)&server, sizeof(server)))
{
cerr << "bind error" << endl;
exit(1);
}
//服务器初始化完成,进入监听状态;
listen(listen_sock, 5);
}
static void *fun(void *p) //类里搞多线程的执行函数毕需static,不然会多一个参数导致匹配不上;
{
int sock = *(int *)p;
delete (int*)p;
while (true)
{
Request rq;
Response rs;
int c = recv(sock, &rq, sizeof(rq), 0); //接收数据,类似于read, 少了一个反序列化(将所收数据转成对应的类型录入)
if (c > 0)
{
char op = rq.op;
int a = rq.a;
int b = rq.b;
cout<<"client send:"<<a<<op<<b<<endl;
switch (op)
{ //进行运算;
case '+':
rs.ret = a + b;
break;
case '-':
rs.ret = a - b;
break;
case '*':
rs.ret = a * b;
break;
case '/':
if (b == 0)
{
rs.statu = -1; //运算格式错误;
break;
}
rs.ret = a / b;
break;
case '%':
if (b == 0)
{
rs.statu = -1; //运算格式错误;
break;
}
rs.ret = a % b;
break;
default:
rs.statu = -1; //运算格式错误;
}
send(sock, &rs, sizeof(rs), 0); //发回数据,类似于write,少了一个序列化(将所发数据转成一个字符串发送出去)
}
else{
cout<<"client close!,me too"<<endl;
close(sock);
break;
}
}
return nullptr;
}
void Loop()
{
while (true)
{
sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(_port);
peer.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock, (sockaddr *)&peer, &len); //连接到client的socket;
int *p = new int(sock); //防止局部sock销毁导致找不到sock,搞堆上;
pthread_t tid;
pthread_create(&tid, 0, fun, p);
pthread_detach(tid); //线程分离,直接accept下一个client的连接,实现并发
}
}
~TcpServer()
{
if (listen_sock >= 0)
close(listen_sock);
}
};
server.cc
#include"server.hpp"
int main(int argc,char*argv[])
{
TcpServer svr(atoi(argv[1]));
svr.Init();
svr.Loop();
return 0;
}
client.hpp
#pragma once
#include"server.hpp"
class Client{
char* _ip;
int _port;
int sock;
public:
Client(char* ip,int port):_ip(ip),_port(port)
{}
void Init()
{
sock = socket(AF_INET,SOCK_STREAM,0);
//client不需要bind listen之类的了
}
void Run()
{
sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(_port);
peer.sin_addr.s_addr = inet_addr(_ip);
connect(sock,(sockaddr*)&peer,sizeof(peer));
while(true)
{
Request rq;
Response rp;
cout<<"请输入第一个数据:";
cin>>rq.a;
cout<<"请输入运算符:";
cin>>rq.op;
cout<<"请输入第二个数据:";
cin>>rq.b;
send(sock,&rq,sizeof(rq),0); //发数据,类似于write,少了一个序列化(将所发数据转成一个字符串发送出去)
int c = recv(sock,&rp,sizeof(rp),0); //接收数据,类似于read, 少了一个反序列化(将所收数据转成对应的类型录入)
if(rp.statu!=0){
cout<<"输入有误!statu:"<<rp.statu<<endl;
continue;
}
if(c>0){
cout<<"运算结果:"<<rp.ret<<endl;
}
}
}
~Client(){if(sock>=0) close(sock);}
};
client.cc
#include"client.hpp"
int main(int argc,char*argv[])
{
Client cl(argv[1],atoi(argv[2]));
cl.Init();
cl.Run();
return 0;
}
运行结果:
总结
这里订制的协议就是当服务器获得client发出的request类型以后,需要计算出其三个成员a op b的结果,顺序类型都已固定不可变,结果存入response类型后返回给client,client使用一个response类型进行接收;