文章目录
- 协议的概念
- 结构化数据
- 网络计算机
- 服务端
- 定制协议
- 客户端代码
协议的概念
计算机之间的传输媒介是光信号和电信号。通过 “频率” 和 “强弱” 来表示 0 和 1 这样的信息。要想传递各种不同的信息,就需要约定好双方的数据格式。
结构化数据
我们知道TCP是面向字节流的方式进行通信,但如何保证读到一个完整的数据呢?
在使用QQ发送消息的时候别人接收到的不仅仅只有消息,而是包含了头像信息,昵称,消息。这就叫做结构化的数据;
这些结构化的数据可以打包成一个报文,这个就是序列化。把这个报文解开的过程就是反序列化;
序列化和反序列化的目的
我们可以认为网络通信和业务处理处于不同的层级;
网络通信时底层看到的都是二进制序列的数据;
业务处理时看得到则是可被上层识别的数据;
业务处理和网络通信之间交互即需要序列化和反序列化;
网络计算机
服务端
- 创建套接字;
- 绑定端口号;
- 设置监听;
- 启动服务器;
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "protocol.hpp"
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 2)
{
cerr << "Usage: " << argv[0] << " port" << endl;
exit(1);
}
int port = atoi(argv[1]);
// 创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
cerr << "socket error!" << endl;
exit(2);
}
// 绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
cerr << "bind error!" << endl;
exit(3);
}
// 监听
if (listen(listen_sock, 5) < 0)
{
cerr << "listen error!" << endl;
exit(4);
}
// 启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
while (true)
{
socklen_t len = sizeof(peer);
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0)
{
cerr << "accept error!" << endl;
continue;
}
pthread_t tid = 0;
int* p = new int(sock);
pthread_create(&tid, nullptr, Routine, p);
}
return 0;
}
定制协议
要保证通信双方能够进行通信,就必须让双方遵守某种协议,也就是双方的约定;
请求结构体中需要包括两个操作数,以及操作符;
响应结构体中需要包括一个计算结果,以及一个状态字段,表示本次计算状态;
// 请求结构体
typedef struct request{
int _x; // 左操作数
int _y; // 右操作数
char _op; // 操作符
}_request;
// 响应结构体
typedef struct response{
int _code; // 计算状态
int _result; // 计算结果
}_response;
协议定制必须要被客户端和服务端同时看到
客户端代码
- 创建套接字;
- 链接服务器;
- 发起请求;(构建请求、接受服务端的响应);
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "protocol.hpp"
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 3)
{
cerr << "Usage: " << argv[0] << " server_ip server_port" << endl;
exit(1);
}
string server_ip = argv[1];
int server_port = atoi(argv[2]);
// 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
cerr << "socket error!" << endl;
exit(2);
}
// 连接服务器
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(server_port);
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
if (connect(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
{
cerr << "connect failed!" << endl;
exit(3);
}
// 发起请求
while (true)
{
// 构建请求
_request rq;
cout << "请输入左操作数: ";
cin >> rq._x;
cout << "请输入右操作数: ";
cin >> rq._y;
cout << "请输入需要进行的操作";
cin >> rq._op;
send(sock, &rq, sizeof(rq), 0);
// 接收响应
_response rp;
recv(sock, &rp, sizeof(rp), 0);
cout << "status: " << rp._code << endl;
cout << rq._x << rq._op << rq._y << " = " << rp._result << endl;
}
return 0;
}
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
- sockfd:特定的文件描述符,表示将数据写入该文件描述符对应的套接字
- buf:需要发送的数据
- len:需要发送数据的字节个数
- flags:发送的方式,一般设置为0,表示阻塞式发送
返回值:
- 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
- sockfd:特定的文件描述符,表示从该文件描述符中读取数据
- buf:数据的存储位置,表示将读取到的数据存储到该位置
- len:数据的个数,表示从该文件描述符中读取数据的字节数
- flags:读取的方式,一般设置为0,表示阻塞式读取
返回值:
- 大于0,则表示本次实际读取到的字节个数
- 等于0,则表示对端已经把连接关闭了
- 小于0,则表示读取时遇到了错误
pthread_create中的Routine函数
void* Routine(void* arg)
{
pthread_detach(pthread_self()); // 分离线程
int sock = *(int*)arg;
delete (int*)arg;
while (true)
{
_request rq;
ssize_t size = recv(sock, &rq, sizeof(rq), 0);
if (size > 0){
_response rp = { 0, 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.code = 1; // 除0错误
}
else
{
rp._result = rq._x / rq._y;
}
break;
case '%':
if (rq._y == 0)
{
rp._code = 2; // 模0错误
}
else
{
rp._result = rq._x % rq._y;
}
break;
default:
rp._code = 3; // 非法运算
break;
}
send(sock, &rp, sizeof(rp), 0);
}
else if (size == 0)
{
cout << "service done" << endl;
break;
}
else
{
cerr << "read error" << endl;
break;
}
}
close(sock);
return nullptr;
}