网络(三)——协议是什么???

协议的概念

计算机之间的传输媒介是光信号和电信号。通过 “频率” 和 “强弱” 来表示 0 和 1 这样的信息。要想传递各种不同的信息,就需要约定好双方的数据格式。

结构化数据

我们知道TCP是面向字节流的方式进行通信,但如何保证读到一个完整的数据呢?
在使用QQ发送消息的时候别人接收到的不仅仅只有消息,而是包含了头像信息,昵称,消息。这就叫做结构化的数据;
这些结构化的数据可以打包成一个报文,这个就是序列化。把这个报文解开的过程就是反序列化;

序列化和反序列化的目的
在这里插入图片描述

我们可以认为网络通信和业务处理处于不同的层级;
网络通信时底层看到的都是二进制序列的数据;
业务处理时看得到则是可被上层识别的数据;
业务处理和网络通信之间交互即需要序列化和反序列化;

网络计算机

服务端

  1. 创建套接字;
  2. 绑定端口号;
  3. 设置监听;
  4. 启动服务器;
#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;

协议定制必须要被客户端和服务端同时看到

客户端代码

  1. 创建套接字;
  2. 链接服务器;
  3. 发起请求;(构建请求、接受服务端的响应);
#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值