文章目录
- 协议的概念
- 结构化数据
- 网络计算机
- 服务端
- 定制协议
- 客户端代码
协议的概念
计算机之间的传输媒介是光信号和电信号。通过 “频率” 和 “强弱” 来表示 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;
}