通过打包的方式,将结构体message发送给对方 对方收到后就会报告给上层QQ客户端
结构化的数据 是由 多个 string 构成的
而以前在网络套接字 发送时,都是按照一个字符串的方式来发送和接收的
所以想办法 ,把多个字符串 转化为 一个大"字符串",对方在接收时也是一个长的字符串, 再想办法把这个字符串转回结构化的数据,就可以让上层使用
把一个结构化的数据 转化为 一个长的字符串 的 过程 称之为 序列化 把一个长的字符串 转化为 一个结构化的数据的 过程 称之为 反序列化
实现一个服务器版的加法器,把客户端把要计算的两个加数发过去,由服务器计算,最后把结果返回给客户端
Sock.hpp 表示 对Tcp套接字的封装
设置一个私有变量 监听套接字 (与accept返回的文件描述符 进行区分)
输入 man socket,创建套接字
第一个参数 domain ,用于区分 进行网络通信还是 本地通信 若想为网络通信,则使用 AF_INET 若想为本地通信,则使用 AF_UNIX
第二个参数 type, 套接字对应的服务类型
SOCK_STREAM 流式套接 SOCK_DGRAM 无连接不可靠的通信(用户数据报)
第三个参数 protocol ,表示想用那种协议,协议默认为0 若为 流式套接,则系统会认为是TCP协议 ,若为用户数据报,则系统会认为是UDP协议
套接字的返回值:若成功则返回文件描述符,若失败则返回 -1
使用socket 创建一个TCP的网络通信,并返回文件描述符到 _listensock中 把上篇博客的 日志(log.hpp)与错误信息枚举(err.hpp)拷贝过来 若套接字创建失败,则通过日志将错误信息打印处来,并借助 错误信息枚举 终止程序
输入 man 2 bind ,查看绑定
给一个套接字绑定一个名字 第一个参数 sockfd 为 套接字 第二个参数 addr 为 通用结构体类型 第三个参数 addrlen 为 第二个参数的实际长度大小
bind返回值:若成功,则返回0,若失败,返回 -1
想要使用bind函数,就需要先创建一个网络通信类型的变量,通过该变量存储端口号 IP地址 16位地址类型 所以要先定义一个 struct sockaddr_in(网络通信) 类型的 变量 local
htons 主机序列转化为 网络序列 需要借助 htons 将传进来的参数 port端口号进行转化 INADDR_ANY 表示 本机的所有IP
若小于0,则绑定失败 依旧使用日志打印处错误码和错误原因,再终止程序
输入 man 2 listen 设置当前套接字状态为 监听状态
第一个参数 sockfd 为 套接字 第二个参数 暂不做解释,一般设为整数 若成功则返回0,若失败返回-1
若小于0,则监听失败 依旧使用日志打印处错误码和错误原因,再终止程序
输入 man 2 accept
需要知道谁连的你,所以要获取到客户端的相关信息
第一个参数 sockfd 为套接字 第二个参数 addr 为通用结构体类型的 结构体 这个结构体是用来记录客户端内的port号以及IP地址 、16位地址类型等信息 第三个参数 addrlen 为 结构体的大小
返回值: 若成功,则返回一个合法的整数 即文件描述符 若失败,返回-1并且设置错误码
sock 这个文件描述符 是真正给用户提供IO服务的 若连接失败,则返回-1,使用日志将错误信息打印出来
若连接成功,则需获取到对应的客户端的 端口号 与客户端的IP地址 使用 inet_ntoa 4字节风格IP转化为字符串风格IP 使用 ntohs 网络序列转主机序列
connect 函数功能为客户端主动连接服务器 成功返回0,失败返回-1
使用Sock这个类,实例化对象_listensock
在初始化中,使用_listensock这个对象 去访问 Scok类中实现过的 Socket Bind Listen 等函数
作为一款服务器,就需要一直运行 作数据的分析
通过_listensock对象访问Accept函数获取客户端的IP地址和端口号
在类中的函数如果不加static修饰,就会导致存在隐藏的this指针 所以 回调函数 需加 static 修饰
使用 pthread_join 默认是阻塞的 ,即主线程等待 新线程退出 在这个过程中,主线程会直接卡住,就没办法继续向后运行,也就什么都干不了 若主线程 想做其他事情 ,所以就提出了线程分离的概念
创建一个结构体ThreadData内部包含sock套接字以及一个指向服务器的指针 ip地址 port端口号
在初始化 多线程部分,new对象,将sock clientip client port 与this指针传递过去作为参数 完成构造
再将td传过去作为回调函数的参数
在回调函数内部调用 serviceIO函数 来完成协议
在命名空间Protocol_n中,定义两个类,分别为Request类和Reponse类
若读到 字符串风格的Request ,就需要通过 序列化 转成 结构化的数据
自己定义 将结构化的数据 转化为 字符串 假设空格作为分割符
使用to_string 将任意类型转化为string
使用 宏, 将SEP表示为空格
将_x _y _op 使用空格连接起来
提供一个函数StringSplit ,去掉字符串中的空格,分别填入vector数组中,作为vetcor数组中的元素 下标为0开始的位置 填入_x ,下标为1开始的位置 填入 _op 下标为2开始的位置 填入 _y
借助函数 toInt,将string类型的元素 转化为 整数
_op在 vector数组的1号下标中,对应其中的一个字符
寻找SEP分割符所在位置,即可分割出区间 使用find函数,从start位置开始寻找分隔符sep,找到分割符sep后,将区间内的子串插入vector数组中
当sep为空格时,只占用一个位置,pos处于空格位置 ,只需加1即可跳出空格 故start的位置 只需 从pos 位置 加上 sep长度即可得到
若出了循环str中依旧有子串没有被插入vector中,则全部当做一个整体放入vector中
使用 atoi 函数 将字符串转化为 整形
使用to_string 将任意类型转化为string
将 res_string SEP 和 code_string 连接起来
同样取调用 StringSplit函数 将字符串 转换为 vector数组中的元素 分别将结果和错误码提取出来
定义一个string类型的package,从套接字sock读取,将结果添加到package中 若有完整报文就交给package,没有完整报文,则一直读取 inbuffer 用于记录报文的所有数据
输入 man recv
第一个参数为 套接字 第二个参数为缓冲区 第三个参数 为缓冲区长度 第四个参数为 读取方式 ,一般默认为0 返回值为读取到的字节数,若字节数小于0,则表示读取出错
先使用recv,将sock中的数据读取到buffer中,再将数据传入inbuffer中
通过find 查找inbuffer中的\r\n的位置,在使用substr将提取到的头部字符串(报头) , 使用 toInt 将字符串转化为数字 ,即获取到字符串长度 最终将有效载荷数据传入 package中
若返回值为-1,则表示读取失败,若返回值为0,则表示继续读取 若返回值为1,则表示读取成功,即可进入下面步骤
从后面先减去一个分隔符,再减去有效载荷的长度 从有效载荷位置开始 取 有效载荷的长度个字符 即 取到有效载荷
构建一个Request 对象 通过该对象去访问请求的 反序列化 ,将字符串str转化为结构化的数据
定义一个包装器,其返回值类型为Response ,参数为 Request ,并重命名为 func_t
使用func_t类型 定义 一个func的私有成员变量
将Request处理完 变为 Response
在Calculatorserver.cc中,进行请求处理
先将结果与错误码默认都设置为0,表示成功 使用 switch case 把request变量的req 中的 _x _y 通过 加 减 乘 除 取模 等进行运算 若期间错误码 出现 1 2 3,则表示错误 最终 将执行后的结果 返回resp中
对response结构进行序列化,将其转化为字符串
将send_string字符串 中 添加字符串长度 分隔符 \r\n
输入 man send
第一个参数为 套接字 第二个参数为特定字符串数据 第三个参数为 数据长度 第四个参数为 默认为0