1 使用场合
1.1 设计到网络数据传输的对数据传输安全有需求的,就需要对数据进行加密;加密就需要用到密钥,密钥协商服务就是用来生成密钥的,该密钥是对称密钥。
1.2 密钥协商服务总体流程说明:
- 客户端发起密钥协商的请求,发送给服务端的随机字符串abc;
- 服务端收到客户端的随机字符串之后,自己也生成一个随机字符串123,且将123发送给客户端;
- 服务端将abc123作为生成密钥的原材料,通过使用某种算法生成一个新的密钥;
- 客户端收到服务端发送的123之后,也进行组合abc123,也使用与服务端相同的算法,生成新的密钥(应该与服务端生成的密钥一样);
- 当客户端和服务端都生成密钥之后,可以通过密钥校验验证客户端和服务端生成的密钥是否一致;
- 客户端将密钥写入共享内存,服务端将密钥写入共享内存。
2 知识点归纳
2.1网络通信.
socket API函数: socket bind listen accept read | recv send write
三次握手和四次挥手
TCP协议
服务端开发:
服务端的基本开发流程:
1 socket
2 setsockopt
3 bind
4 listen
5 while(1)
{
}
客户端的基本开发流程:
1 socket
2 connect
3 while(1)
{
write();
read();
}
多进程
多线程
多路复用技术: select poll epoll
多路IO复用与多线程或者多进程结合使用
第三方库: libevent
2.2 报文编解码
2.3 进程间通信:pipe fifo mmap 本地套接字 网络套接字 共享内存
2.4 数据库操作相关知识点:oracle的occi库
2.5 QT相关
2.6 守护进程的创建步骤
2.7 信号相关知识点: SIGUSR1 SIGUSR2 signal sigaction
2.8 shell编程相关
2.9 加密算法相关
2.10 多线程开发
2.11 c++基础的概念:封装 继承 多态
2.12 子系统划分:
- 密钥协商客户端子系统
- 密钥协商服务端子系统
- 客户端信息注册报备图形化界面系统
2.13 模块划分:
- 报文编解码模块
- 网络通信模块
- 共享内存操作模块
- 数据库操作模块
- 外联接口
2.14 加密三要素:
(密钥+加密算法)+明文==>密文
(密钥+解码算法)+密文==>明文
加密:
明文: 123456
算法: a+密钥
密钥是: 222222
密文: 345678
解密:
密文: 345678
算法: a-密钥
密钥是: 222222
明文: 123456
加密算法:
1 对称加密算法: DES 3DES AES
2 非对称加密: RSA
3 哈希算法: 哈希算法不是用来加密的, 因为不可逆, SHA1 SHA2 MD4 MD5 HMAC
2.15 UML类图
继承关系:使用带有空心箭头的实线来表示,箭头指向的是父类。
单向关联关系:使用带有箭头的实线表示,箭头指向类的成员对象
双向关联关系:使用带有双向箭头的实线来表示,两个类互相包含
自关联:最常见的就是链表操作,箭头指向类自己
聚合关系:使用带有空心菱形的带箭头的实线来表示,空心的菱形在聚合类一侧,箭头指向成员对象
组合关系:使用带有实心菱形的带箭头的实线来表示,空心的菱形在聚合类一侧,箭头指向成员对象
聚合和组合都说整体和部分的关系
依赖关系:类中的成员函数的参数用到了某个类的对象,使用带有箭头的虚线表示,箭头指向被依赖的类
2.16 报文编解码-ASN.1
ASN.1描述了一种对数据进行表示,编码,传输和解码的数据格式。
编码格式(TLV):
例如:name:xiaowu --> name6xiaowu
相关函数:
ITCAST_INT DER_ItAsn1_WriteInteger(ITCAST_UINT32 integer, ITASN1_INTEGER **ppDerInteger);
函数说明:对整形数据进行编码操作
函数参数:
integer:输入输出,表示待编码的整形数据
ppDerInteger:传输参数,编码之后的数据
返回值:
成功或失败
ITCAST_INT DER_ItAsn1_ReadInteger(ITASN1_INTEGER *pDerInteger, ITCAST_UINT32 *pInteger);
函数说明:对整形数据解码
参数说明:
pDerInteger:传入参数,表示待解码的数据
pInteger:传出参数,表示解码之后的数据
返回值:
成功或失败
ITCAST_INT DER_ItAsn1_WritePrintableString(ITASN1_PRINTABLESTRING *pPrintString, ITASN1_PRINTABLESTRING **ppDerPrintString);
函数说明:编码字符串数据
函数参数:
pPrintString:输入参数,表示要编码的数据
ppDerPrintString:输出参数,表示编码之后的数据
返回值:
成功或失败
ITCAST_INT DER_ItAsn1_ReadPrintableString(ITASN1_PRINTABLESTRING *pDerPrintString, ITASN1_PRINTABLESTRING **ppPrintString);
函数说明:解码函数,将ANYCAST_ANYBUF类型解码到第二个参数
参数说明:
pDerPrintString:输出参数,表示待解码的数据
ppPrintString:输出参数,存放解码之后的数据
返回值:
成功或失败
ITCAST_INT DER_ITCAST_String_To_AnyBuf(ITCAST_ANYBUF **pOriginBuf, unsigned char * strOrigin, int strOriginLen);
函数说明:将char *转换成ITCAST_ANYBUF类型
函数参数:
pOriginBUf:传出参数,ITCAST_ANYBUF指针
strOrigin:传出参数,待转换的字符串
strOriginLen:传出参数,strOrigin的字符串长度
返回值:
成功或失败
int EncodeChar(char *pData, int dataLen, ITCAST_ANYBUF **outBuf);
函数说明:将char *类型数据进行编码
函数参数:
pData:输入参数,指的是待编码的字符串
dataLen:输入参数,指的是pData的长度
outBuf:输出参数,ITCASTT_ANYBUF类型的数据,TLV格式
int DecodeChar(ITCAST_ANYBUF *inBuf, char **Data, int *pDataLen);
ITCAST_INT DER_ItAsn1_WriteSequence(ITASN1_SEQUENCE *pSequence, ITCAST_ANYBUF **ppDerSequence);
函数说明:序列化链表,将链表序列化成字节流数据
函数参数:
pSequence:输入参数,待序列化的数据
ppDerSequence:输出参数,序列化之后的数据
ITCAST_INT DER_ItAsn1_ReadSequence(ITCAST_ANYBUF *pDerSequence, ITASN1_SEQUENCE **ppSequence);
函数说明:反序列化
参数说明:
pDerSequence:输入参数,开始需要将char *转换成ITCAST_ANYBUF类型
ppSequence:输出参数,获得链表头节点
ITCAST_INT DER_ITCAST_FreeQueue(ITCAST_ANYBUF *pAnyBuf);
释放内存
3 工厂模式
使用一个单独的类来做创建实例的过程。
使用工厂类的目的是让工厂类去创建类对象;好处:易于维护,易于扩充,且不用原有的代码进行修改。
使用步骤:
- 创建一个工厂类对象,父类的指针指向子类工厂对象;
- 使用父类指针指向的子类对象的createcode函数创建对象;
- 由第二步创建处理的对象由其父类指针指向;
- 调用编解码函数。
使用hash函数加密和解密:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <openssl/sha.h>
#include <stdio.h>
using namespace std;
int main() {
char data[1024] = "xiaowu,hello world";
int len = strlen(data);
unsigned char md[SHA512_DIGEST_LENGTH] = {0};
SHA512_CTX c;
SHA512_Init(&c);
SHA512_Update(&c,data,len);
SHA512_Final(md,&c);
char buf[SHA512_DIGEST_LENGTH*2+1] = {0};
for (int i = 0;i < SHA512_DIGEST_LENGTH;i++) {
sprintf(&buf[i*2],"%02x",md[i]);
}
cout << buf << endl;
memset(md,0x00,sizeof(md));
unsigned char data1[1024] = "xiaowu,hello world";
SHA512(data1,len,md);
memset(buf,0x00,sizeof(buf));
for (int i = 0;i < SHA512_DIGEST_LENGTH;i++) {
sprintf(&buf[i * 2], "%02x", md[i]);
}
cout << buf << endl;
return 0;
}
4 知识点回顾
客户端的开发流程:
创建socket,得到一个通信的文件描述符cfd
int socket(int domain, int type, int protocol);
绑定---不是必须的
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
connect服务端
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
注意:在给IP和端口赋值的时候需要进行主机字节序到网络字节序的转换--htonl htons
while(1)
{
//发送数据
ssize_t write(int fd, const void *buf, size_t count);
int send(int s, const void *msg, size_t len, int flags);
//接收数据
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
}
关闭连接
int close(int fd);
int shutdown(int sockfd, int how);
服务端开发流程:
创建socket,得到一个监听的文件描述符lfd
int socket(int domain, int type, int protocol);
设置端口复用
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
绑定--必须的
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
--INADDR_ANY: 本地任意可用IP
IP+端口可以唯一确定网络中唯一一台主机的唯一一个服务
监听
int listen(int s, int backlog);
调用accept函数接受新的客户端连接
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
--返回一个通信的文件描述符cfd
--addr: 输出参数, 客户端的地址信息
--addrlen: 输入输出参数, 输入: addr内存大小, 输出: 传输的地址大小
while(1)
{
//接收数据
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//发送数据
ssize_t write(int fd, const void *buf, size_t count);
int send(int s, const void *msg, size_t len, int flags);
}
关闭套接字
close(fd);
同步和异步:
同步:例如:客户端发送数据给服务端,发送完成之后,就read阻塞等待读数据,读数据不完成,则不进行后续操作
异步:例如:客户端发送数据给服务端,发送完成之后,没有等待read数据,而是处理后续操作。
阻塞和非阻塞:
阻塞:比如accept和read都是阻塞函数,条件不满足,就一直阻塞等待
非阻塞:将文件描述符设置为非阻塞,则read函数会立刻返回。
同步非阻塞:若客户端发送数据之后,read函数不阻塞(文件描述符设置为非阻塞)
同步阻塞:客户端发送数据之后,read数据,若对方不发送应答数据,就一直阻塞
异步阻塞:比如:select poll epoll,若没有事件发生,select或者epoll可以一直阻塞
异步非阻塞:比如:将epoll设置非阻塞,不管有没有事件发生都会立刻返回
长连接和短链接:
长连接:通常用于通信双方数据交换频繁的情况下 – 心跳包
短链接:通常用于通信双方数据交换完成后就断开连接
网络知识:
7层模型:物数网传会表应
TCP/IP四层模型
一个文件描述符对应两个内核缓冲区(读缓冲区和写缓冲区)
三次握手过程
四次挥手过程
TCP状态转换图:CLOSE SYN_SEND SYN_RCVD LISTEN ESTABLISHED FIN_WAIT_1 FIN_WAIT_2 TIME_WAIT CLOSE_WAIT LAST_WAIT
滑动窗口:主要作用是进行流量控制
TIME_WAIT处于主动关闭方
TCP服务端和客户端开发流程:
多进程版本的服务器
多线程版本的服务器
多路IO复用:select poll epoll
epoll反应堆、线程池(理解生产者和消费者模型)
通信效率:单位时间内客户端或者服务端接收或者发送数据的量
accept();
while(1)
{
read();
}
while(1)
{
accept();
read();
}
可以将文件描述符设置为非阻塞
缺点: 处理起来比较麻烦
改进: 使用多进程或者多线程
多进程: 父进程负责accept接受新的客户端连接, 子进程负责收发数据.
多线程: 主线程负责accept接受新的客户端连接, 子线程父子处理通信.
比较: 最大的区别是多线程处理效率要比多进程快(多线程节省资源)
可以使用map来保存线程ID和文件描述符的对应关系.
多路IO复用技术:
在一个进程中让多个客户端同时请求服务.
都是委托内核进行监控, 若有事件发生则内核会通知应用程序.
select:
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
使用select开发服务端流程:
1 先定义fd_set set;
2 初始化set集合: FD_ZERO(&set);
3 将lfd加入到set集合中: FD_SET(lfd, &set);
4 while(1)
{
nready = select();
for(i=0; i<nfds+1; i++)
{
//有新的客户端连接到来
cfd = accept();
FD_SET(cfd, readfds);
//有客户端数据发来
read();
//若读失败或者对方关闭连接
FD_CLR(cfd, readfds);
}
}
poll:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
与select比较, 没有本质的变化, 但是poll将输入和输出分离.
epoll:
使用步骤:
1 创建一棵epoll树:
int epoll_create(int size);
2 将监听文件描述符上epoll树
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
event.data.fd = lfd;
event.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);
3 while(1)
{
//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
nready = epoll_wait(epfd, events, 1024, -1);
if(nready<0 && errno==EINTR)//被信号打断
{
continue;
}
//有事件发生:1 有客户端连接到来 2 有客户端发送数据到来
for(i=0; i<nready; i++)
{
sockfd = event[i].data.fd;
//1 有客户端连接到来
if(sockfd==lfd && events.events==EPOLLIN)
{
cfd = accept();
event.data.fd = cfd;
event.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
}
else//有客户端发送数据到来
{
n = read(sockfd, buf, sizeof(buf));
if(n<=0)
{
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
}
else
{
write(sockfd, buf, n);
}
}
sleep(100);
}
}
epoll和select比较:
1 epoll可以突破1024的限制
2 epoll精确的告诉应用程序有哪些文件描述符发生了变化, 而select需要进行判断.
可能存在的问题: 若每一个连接上处理的时间比较长, 会导致后面的连接上发来的数据得不到及时处理.
解决方法: 可以让主线程处理epoll_wait, 让子线程处理每一个连接
客户端:
1 单进程--只处理一个连接
while(1)
{
write();
//read();
}
2 使用多线程--多个线程使用同一个连接
3 使用多线程---多个线程使用多个连接
缺点: 如果频繁创建连接和销毁连接会有时间消耗.
4 连接池+线程池
连接池不能用于服务端, 因为连接是只有客户端发起连接请求之后才会有.
连接池只用于客户端.
for(i=0; i<num; i++)
{
//创建num个连接
fd[i] = socket();
connect(fd, servaddr, sizeof(servaddr));
//创建多个线程
pthread_create();
}
连接池:
1 有一个数据结构保存连接
2 创建连接池操作---poolInit()
3 获取连接的操作
4 将连接放回的操作
5 可以根据实际需要动态调整连接的数量
6 销毁连接池
共享内存:共享内存实质是将内核的一块内存映射到进程中的内存,操作本地内存就相当于操作共享内存
创建步骤:
- 创建共享内存
- 关联共享内存
- 使用共享内存 – 读写共享内存
- 断开与共享内存的关联
- 删除共享内存
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);
函数说明:创建或打开一块共享内存区
参数说明:
key:一个无符号整型值,唯一标识了一块共享内存
size:创建共享内存大小
shmflg:
IPC_CREAT:创建共享内存 -创建文件并指定权限:IPC_CREAT|0664
IPC_EXCL:只能和IPC_CREAT一起使用,若共享内存存在,则报错,errno=EEXITST
返回值:
成功:返回共享内存的ID值
失败:返回-1,并设置errno
用法:
如果共享内存已经存在:
key_t key = 0x1234;
int shmID = shmget(key,0,0);
如果共享内存不存在:
int shmID = shmget(key,100,IPC_CREAT|IPC_EXCL|0755);
if(shmID<0){
if(errno == EEXIST){
shmID = shmget(key,0,0);
return 0;
}
}
如果不知道到底存不存在:
int shmID = shmget(key,100,IPC_CREAT|0755);
void *shmat(int shmid,const void *shmaddr,int shmflg);
函数描述:连接共享内存
参数说明:
shmid:shmget函数返回的共享内存的ID值
shmaddr:传NULL,表示让内核分配地址
shmflg:
SHM_RDONLY:只能对共享内存进行读操作
0:可读可写
返回值:
成功:内存地址
失败:(void *)-1
int shmdt(const void *shmaddr);
函数描述:断开与共享内存的关联
参数说明:shmaddr :shmat返回的内存地址
返回值:
成功:返回0
失败:返回-1,并设置errno
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
函数描述:设置或删除共享内存
参数说明:
shmid:shmget函数返回的共享内存的ID值
cmd:
IPC_STAT:获得共享内存的状态信息
IPC_SET:设置共享内存信息
IPC_RMID:删除共享内存
buf:若cmd为IPC_RMID则buf为NULL
共享内存代码:
shm_write.c
int main{
//创建共享内存
int shmid = shmget(x12345678,1024,IPC_CREATIPC_EXCL|0755);
if(shmid<0){
if(errno==EEXIST){
shmid = shmget(0x12345678,0);
}else{
return -1;
}
}
printf("shmget success shmid==[%d]\n",shmid);
//连接共享内存
void *pAddr = shmat(shmid,NULL,0);
if(pAddr==(void *)-1){
return -1;
}
printf("pAddr==[%p]n",pAddr);
//操作共享内存
memcpy(pAddr,"hello world",strlen("hello world"));
printf("按任意键\n");
getchar();
//断开共享内存的关联
shmdt(pAddr);
printf("按任意键\n");
getchar();
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
shm_read.c
int main(){
//创建共享内存
int shmid = shmget(0x123456781024,IPC_CREATJIPC_EXCL|0755);
if(shmid<0){
if(errno==EEXIST){
shmid = shmget(0x12345678,,0);
}else{
return -1;
}
}
printf("shmge tsuccess shmid==[%d]\n",shmid);
//连接共享内存
void *pAddr = shmat(shmid,NULL,0);
if(pAddr==(void *)-1){
return -1;
}
printf("pAddr==[%p]\n", pAddr);
//操作共享内存
char buf[64] = {0};
memcpy(buf,pAddr,11);
printf("buf==[%s]\n", buf);
//断开共享内存的关联
printf("按任意键\n");
getchar();
shmdt(pAddr);
printf("按任意键\n");
getchar();
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
当一个进程与共享内存进行关联之后, 关联引用计数会加1, 断开关联之后会减1.
当删除共享内存的时候, 若共享内存的关联计数大于0,则不会被真正删除, 但是key值变成0了.
shm和mmap的区别:
- shm不需要磁盘文件,mmap需要磁盘文件
- shm效率高
- mmap操作的数据量比shm大
- shm内存位置在内核只有一块,mmap内存在用户区,每个进程都有各自的内存映射区
- shm和mmap的数据相比,mmap更安全,会通过映射的文件做备份
- 进程退出,共享内存依然存在,内存映射区就不存在了
ftok函数:
key_t ftok(const char *pathname,int proj_id);
函数说明:获取shmget所需的key值
参数说明:
pathname是路径或文件名,必须存在,对文件的权限没有要求
proj_id:只用到了一个字节,取值范围:0-255,也可以传递一字符 "a"
pathname可以随便设置,对权限并没有要求,proj_id的取值范围是0-255.
若pathname指向的文件或目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新的inode节点信息,这些进程调用ftok()都能正常返回,单键值key却不一定相同了
共享内存操作命令:
ipcs -a 打印当前系统中所有进程间通信方式的信息
ipcs -m 打印出使用共享内存进行进程间通信的信息
ipcrm -M shmkey 移除用shmkey创建的共享内存段
ipcrm -m shmid 移除用shmid标识的共享内存段
生成随机字符串:
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
void getRandString(char *pRandBuf,int len){
int flag;
char buf[] = "~!@#$%^&*()+-=";
for(int i=0;i<len-1;i++){
flag = rand()%4;
switch(flag){
case 0:
pRandBuf[i] = rand()%10 + '0';
break;
case 1:
pRandBuf[i] = rand()%26 + 'A';
break;
case 2:
pRandBuf[i] = rand()%26 + 'a';
break;
case 3:
pRandBuf[i] = buf[rand()%strlen(buf)];
break;
default:
break;
}
}
}
int main(){
srand(time(NULL));
char sRandBuf[64];
int len = sizeof(sRandBuf);
while(1){
getRandString(sRandBuf,len);
cout<< sRandBuf<<endl;
getchar();
}
return 0;
}
密钥协商系统处理流程
密钥协商整体流程:
- 客户端生成一个随机数r1,同时使用openssl中哈希函数对r1进行哈希运算,得到一个哈希值
- 将要发送的数据进行编码
- 发送数据给服务端
- 服务端收到请求数据之后,首先进行解码操作
- 服务端根据客户端ID和服务端ID查询数据库,校验客户端是否合法,如不合法直接拒绝服务
- 服务端校验r1消息认证码 – 首先使用与客户端相同的算法生成哈希值,然后将这个哈希值与接收到的哈希值作比较,如果不一样,则拒绝服务,如果一致则继续后续操作
- 服务端也生成随机数r2
- 服务端将r1和r2进行拼接,然后使用与客户端相同的哈希算法进行哈希运算,得到一个哈希值,这个哈希值就当作新的密钥seckey2
- 将密钥信息写入共享内存和数据库
- 服务端发送应答信息给客户端
- 客户端等待接收服务端的应答
- 对接收到的数据进行解码
- 判断rv的值,若rv为-1表示生成密钥失败
- 如果成功rv=0;获得服务端发来的随机字符串r2,将r2和r1进行拼接,然后进行哈希运算,得到一个新的密钥seckey1
- 客户端将密钥信息写入共享内存。
密钥校验:
- 客户端将密钥进行哈希运算,得到一个哈希值
- 将哈希值发送给服务端
- 服务端收到哈希值,并且自己也生成一个哈希值
- 将两个哈希值进行比较,相同则密钥协商成功,否则密钥协商失败
密钥注销:
- 将clientID,serverID,密钥ID发送给服务端
- 服务端收到请求之后,将共享内存中的密钥的状态修改为不可以状态,将数据库中的密钥的使用状态修改为不可以状态。
密钥协商客户端流程:
- 对请求结构体RequestMsg进行赋值,生成随机字符串r1,设置cmdType为0,clientID和serverID可以从配置文件或者是环境变量中获取;authCode是对r1进行哈希运算hmac算法得到一个值;
- 对请求结构体进行报文编码操作,得到一个字符串;
- 连接密钥协商服务器;
- 发送编码之后的字符串给密钥协商服务器;
- 等待接受服务端发来的应答数据inData;
- 解码服务端发来的应答数据到RespondMsg结构体变量rspMsg中;
- 根据rv的值判断密钥协商是否成功,为-1表示失败;
- 客户端使用r1和服务端发来的r2进行哈希运算,得到一个新的哈希值seckey1,这个值就当做生成的密钥;
- 将新的密钥信息写入共享内存;
10.断开与服务端的网络连接。
//客户端流程
int ClientOperation::secKeyAgree()
{
//准备请求数据
RequestMsg req;
memset(&req, 0x00, sizeof(RequestMsg));
//RequestCodec::NewOrUpdate枚举
req.cmdType = RequestCodec::NewOrUpdate;
//给clientID和serverID赋值
strcpy(req.clientId, m_info.clinetID);
strcpy(req.serverId, m_info.serverID);
//生成随机字符串r1
getRandString(sizeof(req.r1), req.r1);
//使用hmac函数生成哈希值----消息认证码
char key[64];
unsigned int len;
unsigned char md[SHA256_DIGEST_LENGTH];
memset(key, 0x00, sizeof(key));
sprintf(key, "@%s+%s@", req.serverId, req.clientId);
HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)req.r1, strlen(req.r1), md, &len);
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
{
sprintf(&req.authCode[2 * i], "%02x", md[i]);
}
cout << "key:" << key << endl;
cout << "r1:" << req.r1 << endl;
cout << "authCode:" << req.authCode << endl;
//将要发送的数据进行编码
int dataLen;
char *outData = NULL;
CodecFactory *factory = new RequestFactory(&req);
Codec *pCodec = factory->createCodec();
pCodec->msgEncode(&outData, dataLen);
delete factory;
delete pCodec;
//连接服务端
m_socket.connectToHost(m_info.serverIP, m_info.serverPort);
//发送请求数据给服务端
m_socket.sendMsg(outData, dataLen);
//等待接收服务端的应答
char *inData;
m_socket.recvMsg(&inData, dataLen);
//将接收到的数据进行解码
factory = new RespondFactory();
pCodec = factory->createCodec();
RespondMsg *pMsg = (RespondMsg *)pCodec->msgDecode(inData, dataLen);
//判断服务端是否成功
if (pMsg->rv == -1)
{
cout << "秘钥协商失败" << endl;
return -1;
}
else
{
cout << "秘钥协商成功" << endl;
}
//将服务端的r2和客户端的r1拼接生成秘钥
char buf[1024];
unsigned char md1[SHA_DIGEST_LENGTH];
memset(md1, 0x00, sizeof(md1));
char seckey[SHA_DIGEST_LENGTH*2+1];
memset(buf, 0x00, sizeof(buf));
memset(seckey, 0x00, sizeof(seckey));
sprintf(buf, "%s%s", req.r1, pMsg->r2);
SHA1((unsigned char *)buf, strlen((char *)buf), md1);
for(int i=0; i<SHA_DIGEST_LENGTH; i++)
{
sprintf(&seckey[i*2], "%02x", md1[i]);
}
cout << "r1: " << req.r1 << endl;
cout << "r2: " << pMsg->r2 << endl;
cout << "r1和r2生成的秘钥: " << seckey << endl;
//给秘钥结构体赋值
NodeSHMInfo node;
memset(&node, 0x00, sizeof(NodeSHMInfo));
node.status = 0;
strcpy(node.seckey, seckey);
strcpy(node.clientID, m_info.clinetID);
strcpy(node.serverID, m_info.serverID);
node.seckeyID = pMsg->seckeyid;
//将秘钥信息写入共享内存
m_shm->shmWrite(&node);
//关闭网络连接
m_socket.disConnect();
//释放资源
delete factory;
delete pCodec;
return 0;
}
//服务端
//创建多线程进行监听
void ServerOperation::startWork()
{
//连接,进行监听
m_server.setListen(m_info.sPort);
//创建线程
pthread_t threadID;
while (1) {
if(m_stop==true){
break;
}
//连接新的客户端
m_client = m_server.acceptConn();
pthread_create(&threadID, NULL, working, this);
//设置子线程为分离属性
pthread_detach(threadID);
//std::map<pthread_t, TcpSocket*> m_listSocket;
m_listSocket.insert(make_pair(threadID,m_client));
}
}
void * working(void * arg)
{
//接收数据
//获得线程id
pthread_t thread = pthread_self();
ServerOperation* op = (ServerOperation *)arg;
TcpSocket* socket = op->m_listSocket[thread]; //根据线程ID找client
char* inData;
int dataLen = -1;
int recvMsg = socket->recvMsg(&inData,dataLen); //接收数据
cout<<"recvMse:"<<recvMsg<<endl;
//解码
CodecFactory *factory = new RequestFactory();
Codec* pCodec = factory->createCodec();
RequestMsg* pMsg = (RequestMsg *)pCodec->msgDecode(inData,dataLen);
delete factory;
//判断clientID是否合法
//判断客户端要请求什么服务
char *outData;
switch(pMsg->cmdType)
{
//密钥协商
case RequestCodec::NewOrUpdate:
op->secKeyAgree(pMsg, &outData, dataLen);
break;
//密钥校验
case RequestCodec::Check:
op->secKeyCheck();
break;
//密钥注销
case RequestCodec::Revoke:
op->secKeyRevoke();
break;
//密钥查看
case RequestCodec::View:
op->secKeyView();
break;
default:
break;
}
//发送数据给客户端
socket->sendMsg(outData,dataLen);
free(outData);
socket->disConnect();
delete socket;
//pthread_t thread;
//thread = pthread_self();
auto it = op->m_listSocket.find(thread);
op->m_listSocket.erase(it);
}
int ServerOperation::secKeyAgree(RequestMsg * reqMsg, char ** outData, int & outLen)
{
//验证消息认证码
char key[128];
unsigned int len;
unsigned char md[SHA256_DIGEST_LENGTH];
char authCode[SHA256_DIGEST_LENGTH * 2 + 1] = { 0 };
memset(key,0x00,sizeof(key));
sprintf(key,"@%s+%s@",reqMsg->serverId,reqMsg->clientId);
HMAC(EVP_sha256(),key,strlen(key),(unsigned char *)reqMsg->r1,strlen(reqMsg->r1),md,&len);
//校验clientID的合法性
bool bl = checkClientID(reqMsg->clientId);
if(!bl){
cout<<"客户端ID校验不通过"<<endl;
return -1;
}
cout<<"客户端ID校验通过"<<endl;
for (int i = 0;i < SHA256_DIGEST_LENGTH;i++) {
sprintf(&authCode[2*i],"%02x",md[i]);
}
//将生成的消息认证码与客户端r1的消息认证码做对比
if (strcmp(authCode,reqMsg->authCode)!=0) {
cout << "消息认证码错误" << endl;
return -1;
}else{
cout << "消息认证码成功" << endl;
}
//生成随机字符串
RespondMsg rspMsg;
memset(&rspMsg,0x00,sizeof(rspMsg));
getRandString(sizeof(rspMsg.r2), rspMsg.r2);
cout<<"r1:"<<reqMsg->r1<<endl;
cout<<"r2:"<<rspMsg.r2<<endl;
//将r2和r1拼接,生成密钥
char buf[128];
unsigned char md1[SHA_DIGEST_LENGTH];
char seckey[SHA_DIGEST_LENGTH*2+1];
memset(buf,0x00,sizeof(buf));
memset(seckey, 0x00, sizeof(seckey));
sprintf(buf,"%s%s",reqMsg->r1,rspMsg.r2);
SHA1((unsigned char *)buf,strlen(buf),md1);
for (int i = 0;i < SHA_DIGEST_LENGTH;i++) {
sprintf(&seckey[i * 2], "%02x", md1[i]);
}
cout<<"r1和r2生成的密钥:"<<seckey<<endl;
//给应答结构体赋值
//获取密钥ID
rspMsg.seckeyid = 1;
rspMsg.rv = 0;
strcpy(rspMsg.clientId,reqMsg->clientId);
strcpy(rspMsg.serverId, m_info.serverID);
//将要发送给客户端的应答结构体进行编码
int dataLen;
char* sendData = NULL;
CodecFactory* factory = new RespondFactory(&rspMsg);
Codec* pCodec = factory->createCodec();
pCodec->msgEncode(&sendData,dataLen);
cout<<"*******************"<<endl;
//给输出参数赋值
*outData = sendData;
outLen = dataLen;
//发送数据给客户端
pthread_t thread = pthread_self();
TcpSocket* socket = m_listSocket[thread];
//写密钥信息到共享内存
NodeSHMInfo node;
memset(&node,0x00,sizeof(NodeSHMInfo));
node.status = 0;
strcpy(node.clientID,rspMsg.clientId);
strcpy(node.serverID,m_info.serverID);
strcpy(node.seckey,seckey);
node.seckeyID = rspMsg.seckeyid;
//将密钥写入共享内存
m_shm->shmWrite(&node);
return 0;
}
shell脚本:一些命令的集合,在脚本文件中可以有控制流程,如顺序,条件分支和循环等
脚本文件一般以.sh文件为拓展名,但不是必须的。用#做注释
执行shell脚本:首先要添加可执行权限chmod +x test.sh -> ./test.sh
shell脚本中的变量:
用户自定义变量:
value="hello world"
value1=123
value1="123"
value='123'
使用""和''的区别:对于""中的变量会直接展开, 而对于''当做普通字符串对待
例如: echo "$HOME" 和echo '$HOME'
环境变量:
如HOME PATH
如何将普通变量设置为环境变量: export FILEPATH="xxxxx"
在用户下设置了环境变量之后, 都可以在用户下的进程中通过调用getenv函数获得
环境变量的值.
位置变量:
$0: 脚本文件的名字
$1: 执行脚本的时候的第一个参数
$2: 执行脚本的时候的第二个参数
$3: 执行脚本的时候的第三个参数
.....
$#: 参数的个数
$?: 命令的执行结果, 成功为0, 失败为非0值
$$: shell脚本的PID
$@: 表示全部的参数
获取命令的值:
date1=`date`
date2=$(date)
if语句
if [ 判断语句 ]
then
处理语句
处理语句
elif [ 判断语句 ]
then
处理语句
处理语句
else
处理语句
处理语句
fi
循环控制:
for i in {1..100}
do
xxxx
done
for file in `ls`
do
echo $file
done
for file in a.txt b.txt c.txt
do
echo $file
done
i=0
sum=0
while [ i -le 10 ]
do
sum=$[$sum+$i]
i=$[$i+1]
done
awk用法:
ps -ef | grep itcast | awk '{print $2}'
使用awk查找出某个进程的PID:
ps -ef | grep svrMain | grep -v grep | awk '{print $2}'
QT连接数据库:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//查看数据库驱动的名字
//("QSQLITE", "QMYSQL", "QMYSQL3", "QODBC", "QODBC3", "QPSQL", "QPSQL7")
qDebug() << QSqlDatabase::drivers();
//加载驱动
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
//连接数据库
db.setHostName("localhost");
db.setUserName("root");
db.setPassword("000000");
db.setDatabaseName("mysql");
bool flag = db.open();
qDebug()<<flag;
if(!flag){
qDebug()<<"连接失败";
}else{
qDebug()<<"连接成功";
}
//执行select查询
QSqlQuery query;
//第一种方法
//query.exec("select * from dept");
//第二种方法
query.prepare("select * from dept");
query.exec();
QSqlRecord rec = query.record();
qDebug()<<"查询结果字段总数:"<<rec.count();
qDebug()<<"查询结果记录总数为:"<<query.size();
while(query.next()){
qDebug()<<query.value("deptno").toInt()<<" "
<<query.value("dname").toString()<<" "
<<query.value("loc").toString()<<" ";
}
//执行insert操作
//第一种方法
//query.exec("insert into dept values(11,'sale','shanxi')");
//第二种方法
//query.prepare("insert into dept values(21,'huanggua','jiangsu')");
//query.exec();
//第三种方法
/*query.prepare("insert into dept values(?,?,?)");
query.bindValue(0,31);
query.bindValue(1,"xiaoj");
query.bindValue(2,"dfg");
query.exec();*/
//执行update操作
//第一种方法
//query.exec("update dept set dname='xxxxxx' where deptno=11");
//第二种方法
//query.prepare("update dept set dname='xxxxxx' where deptno=11");
//query.exec();
//第三种方法
//query.prepare("update dept set dname='xxxxxx' where deptno=?");
//query.bindValue(0,11);
//query.exec();
//执行delete操作
//第一种方法
//query.exec("delete from dept where deptno=11");
//第二种方法
//query.prepare("delete from dept where deptno=21");
//query.exec();
//第三种方法
//query.prepare("delete from dept where deptno=?");
//query.bindValue(0,11);
//query.exec();
//执行select查询
//第一种方法
//query.exec("select * from dept");
//第二种方法
query.prepare("select * from dept");
query.exec();
qDebug()<<"查询结果字段总数:"<<rec.count();
qDebug()<<"查询结果记录总数为:"<<query.size();
while(query.next()){
qDebug()<<query.value("deptno").toInt()<<" "
<<query.value("dname").toString()<<" "
<<query.value("loc").toString()<<" ";
}
//事务处理
//开启一个新的事务
query.exec("start tranaction");
//设置事务为手工提交
query.exec("set autocommit=0");
//插入一条记录
query.exec("insert into dept values(11,'shitang','libu')");
//回滚
//query.exec("rollback");
//提交
query.exec("commit");
db.close();
return a.exec();
}
json文件
QT操作json,json分为json对象和json数组
#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QByteArray>
#include <QJsonValue>
#include <QFile>
#include <QDebug>
void writeJsonToFile_Arr(char *fileName){
//将json数组写入磁盘文件
//构造一个QJsonArray对象
QJsonArray array;
//给array对象添加值
array.append(10);
array.append("jinglong");
array.append(true);
//添加子数组到array中
QJsonArray subArray;
subArray.append("english");
subArray.append("chinese");
subArray.append("math");
array.append(subArray);
//添加对象到array中
QJsonObject subObj;
subObj.insert("mother","xiaoli");
subObj.insert("father","xiaotong");
subObj.insert("sister","damiao");
array.append(subObj);
//将QJsonObject转换为QJsonDocument
QJsonDocument jsonDoc(array);
//将QJsonDocument对象转换为QByteArray对象
QByteArray byteArray = jsonDoc.toJson();
//文件操作 -- 将byteArray写入文件
QFile file(fileName);
//打开文件
file.open(QIODevice::WriteOnly);
//写文件
file.write(byteArray);
//关闭文件
file.close();
return;
}
void writeJsonToFile_Obj(char *fileName){
//将json对象写入磁盘文件
//创建json对象
QJsonObject json;
//给json对象插入键值对
//iterator insert(const QString &key,const QJsonValue &value)
json.insert("name","xiaoqiu");
json.insert("age",21);
json.insert("sex","female");
//插入子对象 "family":{"father":"longji",...}
QJsonObject subJson;
subJson.insert("father","longji");
subJson.insert("mather","liwei");
subJson.insert("sister","wangjin");
json.insert("family",subJson);
//插入json数组
QJsonArray jsonArr;
jsonArr.append("english");
jsonArr.append("chinese");
jsonArr.append("math");
jsonArr.append("history");
json.insert("course",jsonArr);
//将QJsonObject转换为QJsonDocument
QJsonDocument jsonDoc(json);
//将QJsonDocument对象转换为QByteArray对象
QByteArray byteArray = jsonDoc.toJson();
//文件操作 -- 将byteArray写入文件
QFile file(fileName);
//打开文件
file.open(QIODevice::WriteOnly);
//写文件
file.write(byteArray);
//关闭文件
file.close();
return;
}
void readJsonFromFile(char *fileName){
//构造QFile类对象
QFile file;
//设置要读的文件
file.setFileName(fileName);
//打开文件
file.open(QIODevice::ReadOnly);
//读文件
QByteArray byteArray = file.readAll();
//将QByteArray对象转换为QJsonDocument对象或数组
QJsonDocument jsonDoc = QJsonDocument::fromJson(byteArray);
//关闭文件
file.close();
//判断是数组还是对象
if(jsonDoc.isObject()){
QJsonObject jsonObj = jsonDoc.object();
//获取对象中所有的key值
QStringList keys = jsonObj.keys();
for(int i=0;i<keys.size();i++){
//获取每一个key值
QString key = keys[i];
//qDebug()<<key<<":";
//根据key值获取value值
QJsonValue jsonValue = jsonObj.value(key);
//判断value值的类型
if(jsonValue.isString()){
qDebug()<<key<<":"<<jsonValue.toString();
}
else if(jsonValue.isDouble()){
qDebug()<<key<<":"<<jsonValue.toInt();
}
else if(jsonValue.isBool()){
qDebug()<<key<<":"<<jsonValue.toBool();
}
else if(jsonValue.isObject()){
QJsonObject obj = jsonValue.toObject();
QStringList subKeys = obj.keys();
qDebug()<<key<<":{";
for(int k=0;k<subKeys.size();k++){
QString subkey = subKeys[k];
QJsonValue subJsonValue = obj.value(subkey);
qDebug()<<" "<<subJsonValue.toString();
}
qDebug()<<"}";
}
else if(jsonValue.isArray()){
qDebug()<<key<<":[";
QJsonArray arr = jsonValue.toArray();
for(int j = 0;j<arr.size();j++){
QJsonValue va = arr[j];
if(va.isString()){
qDebug()<<" "<<va.toString();
}
}
qDebug()<<"]";
}
}
}else if(jsonDoc.isArray()){
QJsonArray array = jsonDoc.array();
for(int i=0;i<array.size();i++){
QJsonValue value = array[i];
if(value.isString()){
qDebug()<<value.toString();
}
else if(value.isDouble()){
qDebug()<<value.toDouble();
}
else if(value.isBool()){
qDebug()<<value.toBool();
}
else if(value.isArray()){
qDebug()<<"[";
QJsonArray subArray = value.toArray();
for(int j=0;j<subArray.size();j++){
qDebug()<<" "<<subArray[j].toString();
}
qDebug()<<"]";
}
else if(value.isObject()){
qDebug()<<"{";
QJsonObject subObj = value.toObject();
QStringList subKeys = subObj.keys();
for(int k=0;k<subKeys.size();k++){
QString subkey = subKeys[k];
QJsonValue value = subObj[subkey];
if(value.isString()){
qDebug()<<" "<<value.toString();
}
}
qDebug()<<"}";
}
}
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
readJsonFromFile("mytest.json");
return a.exec();
}
c++操作mysql数据库:
//头文件
#include <iostream>
#include <string>
#include <mysql/mysql.h>
using namespace std;
//函数定义
// 执行sql语句, 包括增加、删除、更新数据
bool ExecuteSql(MYSQL m_mysql,const char * sql)
{
//mysql_query与指定的连接标识符关联的服务器中的当前活动数据库发送一条查询
if (mysql_query(&m_mysql, sql))
{
// 打错误log,这里直接显示到控制台
cerr << "执行sql语句失败,错误信息为: " << mysql_error(&m_mysql) << endl;
return false;
}
else
{
cout << "执行sql语句成功!" << endl;
}
return true;
}
//主函数中调用
int main()
{
MYSQL mysql; //一个数据库结构体
MYSQL_RES* res; //一个结果集结构体
MYSQL_ROW row; //char** 二维数组,存放一条条记录
//初始化数据库
mysql_init(&mysql);
//设置编码方式
mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
//连接数据库
//判断如果连接失败就输出连接失败。
//注意你连接的账户名密码
if (mysql_real_connect(&mysql, "localhost", "root", "000000", "qcx", 3306, NULL, 0) == NULL)
printf("连接失败!\n");
//string st = "insert into dept values (15,'roujiamo','xian')";
string del_st = "delete from dept where deptno=15";
const char* sql1 = del_st.c_str();
ExecuteSql(mysql, sql1);
//查询数据
mysql_query(&mysql, "select * from dept");
//获取结果集
res = mysql_store_result(&mysql);
//显示数据
//mysql_fetch_row检索结果集的下一行,当没有下一行时返回NULL
while (row = mysql_fetch_row(res))
{
printf("%s", row[0]);
printf("\t%s", row[1]);
printf("\t%s\n", row[2]);
}
//释放结果集
mysql_free_result(res);
//关闭数据库
mysql_close(&mysql);
return 0;
}
执行命令:g++ -o test test.cpp -Ldir_path -lmysqlclient