Data security transmission platform project note-1

Posted by gwolgamott on Wed, 02 Mar 2022 16:21:15 +0100

Project introduction and cryptography knowledge

Overall structure of the project:


Main function: encrypt and decrypt the data of network communication

Basic components:

  1. Data serialization: protobuf
  2. socket communication: thread pool, connection pool
  3. Shared memory IPC
  4. oracle Database: using OCI interface
  5. Data encryption: openssl (Secure Sockets Layer)

Three elements of encryption:

  1. Plaintext and ciphertext
  2. Secret key: a fixed length string
  3. Algorithm: encryption algorithm and decryption algorithm

Common encryption methods:

  1. Symmetric encryption

    The key is short and there is only one. The key used for encryption and decryption is the same. It is your responsibility to generate a random string. The encryption efficiency is high, but the encryption intensity is low, and the key distribution is difficult, so it can not be transmitted directly in the network environment. Represented by AES

  2. Asymmetric encryption

    The key is relatively long. The keys used for encryption and decryption are different. They are generated by special algorithms and are divided into public key (small) and private key (large)
    If you use public key encryption, you must decrypt using the private key
    If you use private key encryption, you must use public key decryption
    Public keys can be distributed directly

In order to give consideration to efficiency and security, asymmetric encryption is usually used to encrypt the key of symmetric encryption, so that symmetric encryption can be carried out safely. The process is as follows:

  • The client generates (reads) the key pair, encapsulates the public key into the request message and signs it with the private key, which is also encapsulated into the request message
  • After receiving the request message and verifying the identity, the server generates a random string, encrypts the public key, encapsulates it in the response message and sends it to the client
  • After receiving the response message, the client uses the private key to decrypt, so that both ends can safely get the symmetric encryption key

Common symmetric encryption algorithms:

DES and 3DES:

AES:

Secret key exchange process:

Hash algorithm (one-way hash function)

  1. No matter how long the original data is, the number of bits of the hash result is the same
  2. The original data are a little different, and the results are completely different
  3. Strong collision resistance (carrying burst)
  4. Irreversible: it is impossible to deduce the original content from the result (which determines that it cannot be an encryption algorithm)

Application scenario:

  1. Data verification: MD5 value will be provided when downloading the file to verify whether the file has been tampered with
  2. Login verification: there is no need to save password plaintext in the system, just save its corresponding hash value
  3. Second transmission function of network disk (Baidu network disk)

The result of hash operation is called: hash value, fingerprint, summary

MD4/MD5: the length of hash value is 16B, and the anti-collision performance has been cracked

SHA-1: hash value length 20B

SHA-2 (224256384512): each subclass name indicates the number of bits it occupies

Message authentication code HMAC

Function: verify whether the data during communication has been tampered with

HMAC is essentially a hash value

HMAC = hash (original data, key)

Disadvantages:

  1. Key distribution difficulty
  2. The owner of the message cannot be distinguished

Digital signature (the process of private key encryption and public key decryption)

Purpose: to prevent tampering with data and ensure the integrity of data

Function: it can identify the owner of the document

Sender digital signature process:

  1. Generate an asymmetric key pair and distribute the public key
  2. Hash the original data with hash function to get the hash value (the hash value is smaller, which is easier to encrypt than the original data)
  3. The hash value is encrypted with the private key to obtain the ciphertext
  4. Send the original data and ciphertext to the receiver

The process of receiver verifying signature:

  1. Public key distributed by the party receiving the signature
  2. Receiving data from sender: original data and signature
  3. Perform the same 0 hash operation on the received original data to obtain the hash value 1
  4. Decrypt the ciphertext with the public key to obtain the hash value of 2
  5. Compare the two hash values. If they are the same, it is proved to be safe

Introduction to OpenSSL:

SSL is the abbreviation of secure sockets layer (Secure Sockets Layer Protocol), which can provide secret transmission on the Internet. While launching the first Web browser, Netscape company proposed the SSL protocol standard. Its goal is to ensure the confidentiality and reliability of the communication between the two applications, which can be supported on the server side and the user side at the same time. It has become an industrial standard for secure communication on the Internet.
SSL can prevent the communication between user / server applications from being eavesdropped by attackers, and always authenticate the server. You can also choose to authenticate the user. SSL protocol is required to be based on reliable transport layer protocol (TCP). The advantage of SSL protocol is that it is independent of the application layer protocol. High level application layer protocols (such as HTTP, FTP, TELNET, etc.) can be transparently established on SSL protocol. SSL protocol has completed the encryption algorithm, communication key negotiation and server authentication before the application layer protocol communication. After that, the data transmitted by the application layer protocol will be encrypted to ensure the privacy of communication.

vs configure openssl:

Set project properties: include the input of directory, library directory and linker

Test demo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>

//#define _CRT_SECURE_NO_WARNINGS

void getMD5(const char* src, char* result) {
	MD5_CTX ctx;
	MD5_Init(&ctx);
	MD5_Update(&ctx, src, strlen(src));
	unsigned char md5[16] = { 0 };
	MD5_Final(md5, &ctx);
	for (int i = 0; i < 16; ++i) {
		sprintf(&result[i * 2], "%02x", md5[i]);
	}
}

int main(int argc, char* argv[]) {
	char result[33] = { 0 };
	getMD5("hello, md5", result);
	printf("md5 value:%s\n", result);
	system("pause");
	return 0;
}

Install linux and test openssl

Check the library version after installation: openssl version -a

If the library is installed but cannot be found:

Find the directory where the library is located: find / - name libcrypto so

Write its path to / etc / LD so. Conf file and make it effective: sudo ldconfig

Add option at compile time - lcrypto

git

Distributed version control system

git's core advantage: it supports branches, and iterative development of version number can be carried out on each branch

git related concepts:

Workspace: a directory created by the user to store the source code

Version Library: manage the submitted code. Creation method: right click under win and use tortoisegGit to create version library

Staging area: temporarily saved, not involved in version management

technological process:

  1. Create a new file in the workspace
  2. The new file is added to the local warehouse and saved in the staging area
  3. Submit the data in the staging area to the version library

Install git and connect gitee on linux:

Installation: sudo apt get install Git

Configure user name and mailbox:

git config --global user.name "daniel187"
git config --global user.name "syc198@qq.com"

Initialize a warehouse: git init

Generate key pair: SSH keygen - t RSA - C“ syc198@qq.com "

Copy the contents of the public key to the input box of gitee website, which is located in ~ / ssh/id_rsa.pub

Add content and submit locally, then submit to remote warehouse:

git add README.md
git commit -m "first commit"
git remote add origin https://gitee.com/daniel187/demo1.git
git push -u origin "master"

Basic usage of tortoiseGit on win:

Add new local file to staging area: right click

Submit files in the staging area to the local version Library: right click

Restore modified files: right click

View submitted historical version information: right click in the workspace - display log

Double click the file to view the contents. The old version is on the left and the new version is on the right

You can export a version: right click the current version directly

Compare differences: right mouse button

Delete file: directly del and then submit

After deletion and submission, if you still want to restore, you can restore from the history node

Set file ignore Directory: select a file, right-click ignore, and submit

Synchronize local warehouse to remote warehouse: right click git synchronization

Generate a key pair under gitbash: SSH keygen - t RSA

Also send the public key to gitee

base64: use 64 characters to represent the original data: a~z, A~Z, +/

It can branch or merge in reverse

Create new branch: right click

Branch switching: right click to switch in and check out

Branch merging: to which branch to merge, you should first switch to which branch, and then right-click merge

If two branches have files with the same name, there will be a problem: the same line has different data, which git cannot judge and needs to be solved manually

How to push a local warehouse to a non empty remote warehouse:

Pull remote warehouse to local: copy the contents of the remote warehouse to a new local branch without merging

Get: directly merge the content in the remote warehouse with the content in the local warehouse

Common git commands: https://www.php.cn/tool/git/469391.html

protobuf deployment

win: first generate vs project with CMake, then compile with VS, and add protobuf when using_ USE_ DLLs macro definition
linux: download the source code, and then

configure
make
make install

UML class diagram

UML=Unified Modeling Language

For a Person class:

class Person {
 public:
  string get_name();
  void set_name(string name);

 protected:
  void play_basketball();
  void pass();

 private:
  string m_name="Jack";
};

Its UML class diagram is described as follows:

  • public: +
  • protected: #
  • private: -

Inheritance: use a solid line with a hollow triangle at one end to point to the base class

Two subclasses of Student and Teacher are derived from the above Person:

class Student : public Person {
 public:
  void study();

 private:
  string stu_no;
};

class Teacher : public Person {
 public:
  void teach();

 private:
  string teacher_no;
};

The inheritance relationship of UML class diagram is shown as follows:

Abstract inheritance relationship:

Classes containing pure virtual functions are called abstract classes and cannot be instantiated. Subclasses must override the pure virtual functions of the parent class

class Link {
 public:
  //Pure virtual function
  virtual void insert() = 0;
  virtual void move() = 0;
  int count();
};

class SingleLink : public Link {
 public:
  void insert() { return; }
  void move() { return; }
};

The UML diagram is described as follows. Note that the font of the class name of the abstract class is inclined, and the font of the name of the pure virtual function is also inclined

Association relationship: the object of one class is used as the member variable of another class


The object of one class acts as a member variable of another class

class Address {};

class Customer {
 private:
  Address addr;
};

The association relationship is represented by - > as follows:


Two way Association, including:

class Product {
 private:
  Customer customer;
};

class Customer {
 private:
  Product product[64];
};


Self association: for example, linked list

class Node {
 private:
  Node* next;
};

Aggregate relationship:

Represents the relationship between whole and part. In the aggregation relationship, the member object is a part of the whole, but the member object can exist independently of the whole object.

In UML, the aggregation relationship is represented by a straight line with a hollow diamond, such as automobile and engine, tire and lamp:

class Wheel {};

class Light {};

class Engine {};

class Car {
 public:
  Car(Wheel w, Light l, Engine e) {
    this->wheel = w;
    this->light = l;
    this->engine = e;
  }

  void dirve() {}

 private:
  Wheel wheel;
  Light light;
  Engine engine;
};

Note that constructors do not have to be written into UML diagrams

Combination relationship:

Composite relationship also represents a relationship between whole and part, but in the composite relationship, the whole object can control the life cycle of member objects. Once the whole object does not exist, the member object does not exist, and there is a relationship of life and death between the whole object and the member object. In UML, it is represented by a straight line with a solid diamond.

class Mouse {};

class Nose {};

class Head {
 public:
  Head() {
    this->mouse = new Mouse;
    this->nose = new Nose;
  }
  void shake() {}

 private:
  Mouse* mouse;
  Nose* nose;
};


About the construction and Deconstruction of member objects in composite relationships:

#include <iostream>
using namespace std;

class Mouse {
 public:
  Mouse() { cout << "A mouse has been constructed" << endl; }
  ~Mouse() { cout << "A mouse has been deconstructed" << endl; }
};

class Nose {
 public:
  Nose() { cout << "A nose has been constructed" << endl; }
  ~Nose() { cout << "A nose has been deconstructed" << endl; }
};

class Head {
 public:
  Head() {
    this->mouse = new Mouse;
    this->nose = new Nose;
  }
  ~Head() {
      delete this->mouse;
      delete this->nose;
  }
  void shake() {}

 private:
  Mouse* mouse;
  Nose* nose;
};

void foo() {
    cout << "h1 in the stack,in the function foo" << endl;
    Head h1;
}

int main() {
    foo();

    cout << "\nh1 in the stack" << endl;
    Head h1;

    cout << "\nh2 in the heap" << endl;
    Head* h2 = new Head;
    delete h2;
    system("pause");
    return 0;
}

Program running results:

h1 in the stack,in the function foo
A mouse has been constructed
A nose has been constructed
A mouse has been deconstructed
A nose has been deconstructed

h1 in the stack
A mouse has been constructed
A nose has been constructed

h2 in the heap
A mouse has been constructed
A nose has been constructed
A mouse has been deconstructed
A nose has been deconstructed

Note that in the composition relationship, the new member object in the constructor must be manually delete d in the destructor. The C + + compiler will not destruct its member object at the same time when destructing the instance of Head

In addition, it is worth noting that when the local variable created on the function call stack exits the function, it will be destructed automatically (the basis of RAII), but it will not be destructed in the main function

Dependency: a widespread usage relationship, connected by dashed lines with arrows in UML diagrams

  1. An object of one class is used as a parameter or return value of a method of another class
  2. Use the object of another class as a local variable of its object in the method of one class
  3. Call a static method of another class in a class method.

Dependency instance:

class Car {
 public:
  void move();
};

class Driver {
 public:
  void drive(Car c) { c.move(); }
};

Protobuf serialization

The overall process of serialization:

Common data serialization methods:

  1. XML
  2. JSON
  3. Protobuf

Use steps of protobuf:

  • First, prepare a data, which can be a composite type or a basic type

  • Then create a new file: file proto

  • Then write the data to be serialized to file Proto file (with certain syntax format)

  • Then pass a command, protoc file proto --cpp_ out=./ File Proto file generates a C + + class: corresponding to a header file and a source file

  • Finally, directly use this class, which has an api for reading and writing data

Syntax format of proto file:

syntax="proto3";

message struct_name{
	data_type data_name=data_index;	//start from 1
	...
}

Data types in protobuf:

For example, for such a C + + class:

struct Person{
	int id;
	string name;
	string sex;
	int age;
}

Its protobuf data file is:

syntax="proto3";
message Person{
	int32 id=1;
	string name=2;
	string sex=3;
	int32 age=4;
}

Generate CPP class: Protocol person proto --cpp_ out=./

api naming feature: name is the name of the member variable:

Read: name()
Write: set_name()

Use steps under vs:

  1. Include header and source files
  2. Project attribute configuration header file directory and Library Directory
  3. Add lib library to linker input of project properties
  4. Preprocessor add macro definition PROTOBUF_USE_DLLS

Test code:

#include <iostream>
#include "Person.pb.h"
using namespace std;

int main() {
	Person p;
	p.set_age(20);
	p.set_id(1001);
	p.set_name("daniel");
	p.set_sex("man");

	string output;
	output=p.SerializeAsString();

	cout << "output:" << output << endl;

	Person p2;
	p2.ParseFromString(output);

	cout << "p2.age:" << p2.age() << ",p2.id:" << p2.id() << ",p2.name:" << p2.name() << ",p2.sex:" << p2.sex() << endl;
	system("pause");
	return 0;
}

Use of arrays: use the repeated keyword to modify

syntax="proto3";
message Person{
	int32 id=1;
	repeated string name=2;
	string sex=3;
	int32 age=4;
}

Accordingly, the array subscript shall be indicated in the program:

int main() {
	Person p;
	p.set_age(20);
	p.set_id(1001);

	p.add_name();
	p.set_name(0,"daniel");

	p.add_name();
	p.set_name(1,"anny");

	p.add_name();
	p.set_name(2,"alan");

	p.set_sex("man");

	string output;
	output=p.SerializeAsString();

	cout << "output:" << output << endl;

	Person p2;
	p2.ParseFromString(output);

	cout << "p2.age:" << p2.age() << ",p2.id:" << p2.id() << ",p2.name:" << p2.name(0)<<","<<p2.name(1)<<"," <<p2.name(2) << ",p2.sex:" << p2.sex() << endl;
	system("pause");
	return 0;
}

Enumeration used in protobuf: the syntax is the same as that of C language, but the first enumeration value must be 0

syntax="proto3";
enum Color{
	RED=0;
	GREEN=1;
	BALCK=2;
	PINK=3;
}

message Person{
	int32 id=1;
	repeated string name=2;
	string sex=3;
	int32 age=4;
	Color color=5;
}

Use in program:

//enum
p.set_color(PINK);

Classes in protobuf can have association relationships. Classes and classes can be nested and imported using import

For example, define the Info class and use it in Person:

syntax="proto3";

message Info{
    bytes address=1;
    int32 no=2;
}
syntax="proto3";
import "Info.proto";

enum Color{
	RED=0;
	GREEN=1;
	BALCK=2;
	PINK=3;
}

message Person{
	int32 id=1;
	repeated string name=2;
	string sex=3;
	int32 age=4;
	Color color=5;
	Info info=6;
}

In order to manipulate the Info object, the program also needs to #include "Info.pb.h"

How to use in the program:

First use mutable_info() takes out the pointer of the info object to facilitate the manipulation of info

Then call the set method of info to initialize it

When you need to extract relevant information from info, you should first create a temporary Info object i and then call i's get method to extract the values.

	Info* info=p.mutable_info();
	info->set_address("China");
	info->set_no(23);

	string output;
	output=p.SerializeAsString();

	cout << "output:" << output << endl;

	Person p2;
	p2.ParseFromString(output);

	Info i = p2.info();

	cout << "p2.age:" << p2.age() << ",p2.id:" << p2.id()
		<< ",p2.name:" << p2.name(0)<<","<<p2.name(1)<<"," <<p2.name(2)
		<< ",p2.sex:" << p2.sex() <<",p2.color:"<< p2.color()
		<<"p2.info.address:"<<i.address()<<",p2.info.no:"<<i.no() << endl;

Add namespace in protobuf: use package keyword

syntax="proto3";

package itheima;

message Person{
    bytes address=1;
    int32 no=2;
}
syntax="proto3";
import "Info.proto";

package itcast;

enum Color{
	RED=0;
	GREEN=1;
	BALCK=2;
	PINK=3;
}

message Person{
	int32 id=1;
	repeated string name=2;
	string sex=3;
	int32 age=4;
	Color color=5;
	itheima.Person person=6;
}

Use the corresponding namespace in the program accordingly:

int main() {
	//Person class under itcast namespace
	itcast::Person p;
	p.set_age(20);
	p.set_id(1001);

	p.add_name();
	p.set_name(0,"daniel");

	p.add_name();
	p.set_name(1,"anny");

	p.add_name();
	p.set_name(2,"alan");

	p.set_sex("man");

	//enum
	p.set_color(itcast::PINK);

	//One of its members is the Person class under the itheima namespace
	itheima::Person* ps = p.mutable_person();
	ps->set_address("China");
	ps->set_no(23);

	string output;
	output=p.SerializeAsString();

	cout << "output:" << output << endl;

	itcast::Person p2;
	p2.ParseFromString(output);

	itheima::Person p3 = p2.person();

	cout << "p2.age:" << p2.age() << ",p2.id:" << p2.id()
		<< ",p2.name:" << p2.name(0)<<","<<p2.name(1)<<"," <<p2.name(2)
		<< ",p2.sex:" << p2.sex() <<",p2.color:"<< p2.color()
		<<",p2.person.address:"<<p3.address()<<",p2.person.no:"<<p3.no() << endl;
	system("pause");
	return 0;
}

Business data analysis:

Data sent:

Corresponding proto file:

syntax="proto3";

message RequestMsg{
	int32 cmdType=1;
	bytes clientID=2;
	bytes serverID=3;
	bytes sign=4;
	bytes data=5;
}

Response data:

Corresponding proto file:

syntax="proto3";

message ResponseMsg{
	int32 status=1;
	int32 seckeyID=2;
	bytes clientID=3;
	bytes serverID=4;
	bytes data=5;
}

The corresponding UML class diagrams are as follows:

The problem that all dynamic libraries under win cannot be found: put the directory where the dynamic library is located into the system environment variable

The library file name followed by d is generally used for the debug version during development, and the one without d is generally used for the release version

Codec class diagram:

Generate class files using protoc ol:

syntax="proto3";

message ResponseMsg{
	int32 status=1;
	int32 seckeyID=2;
	bytes clientID=3;
	bytes serverID=4;
	bytes data=5;
}

message RequestMsg{
	int32 cmdType=1;
	bytes clientID=2;
	bytes serverID=3;
	bytes sign=4;
	bytes data=5;
}

Code framework implementation of Codec base class:

Header file:

#ifndef CODEC_H
#define CODEC_H
#include <string>

enum Cmd {
	SecKeyAgree = 1,
	SecKeyCheck,
	SecKeyWriteOff
};

class Codec {
public:
	Codec();
	virtual ~Codec();
	//Pure virtual, the base class is not implemented, and it is handed over to the subclass for implementation
	virtual std::string encodeMsg() = 0;
	virtual void* decodeMsg() = 0;
};
#endif

It should be noted that when using polymorphism technology, the destructor of the base class needs to be implemented as a virtual function

Source file:

#include "Codec.h"

Codec::Codec() {
}

Codec::~Codec() {
}

Let's have a deep understanding of virtual functions in C + +:

https://www.bilibili.com/video/av498280539

First of all, it should be noted that even if it is an empty class, its instantiated objects occupy at least 1B of memory space:

class A {

};

int main(int argc, char* argv[]) {
	A a;
	cout << "sizeof(a):" << sizeof(a) << endl;
	system("pause");
	return 0;
}

The program output is 1

Next, add two ordinary member functions to the class and observe their sizeof value:

class A {
public:
	void func1() {}
	void func2() {}
};

int main(int argc, char* argv[]) {
	A a;
	cout << "sizeof(a):" << sizeof(a) << endl;
	system("pause");
	return 0;
}

The program output is still 1

Note that the ordinary member function of A does not occupy the memory space of class objects

Then add an imaginary function to a and observe the sizeof value of a:

class A {
public:
	void func1() {}
	void func2() {}
	virtual void vfunc() {}
};

int main(int argc, char* argv[]) {
	A a;
	cout << "sizeof(a):" << sizeof(a) << endl;
	system("pause");
	return 0;
}

The output result of the program is the size of a pointer of 8 and 64 bit machines

The reason is that the compiler inserts a virtual function table pointer vptr into a:


In the debugging window of vs, you can observe:


When class A contains at least one virtual function, the compiler will generate a virtual function table vtbl for class A, which will always accompany class A, including its loading into memory

When the virtual function table pointer is assigned: when executing the constructor of a, make the virtual function pointer of the object point to the vtbl of class A


The layout of class instances in memory;

class A {
public:
	void func1() {}
	void func2() {}
	virtual void vfunc() {}
	virtual void vfunc2() {}
	virtual ~A() {}
private:
	int m_a;
	int m_b;
};

int main(int argc, char* argv[]) {
	A a;
	cout << "sizeof(a):" << sizeof(a) << endl;
	system("pause");
	return 0;
}

The output result of the program is 16. Note that ordinary member functions will not occupy the memory space of class instances, and ordinary member variables will occupy the memory space of class instances:

It can also be observed in vs:

The working principle of virtual function and the embodiment of polymorphism (polymorphism must have virtual function, and it is impossible to talk about polymorphism without virtual function):

Use the parent class pointer to point to the child class object

class Base {
public:
	virtual void myvfunc() {}
};

int main(int argc, char* argv[]) {
	Base* b1 = new Base();
	b1->myvfunc();//this is polymorphic

	Base b2;
	b2.myvfunc();//this is not polymorphic

	Base* b3 = &b2;
	b3->myvfunc();//this is polymorphic

	system("pause");
	return 0;
}

Expression of virtual function:

  1. There are both parent classes and subclasses in the program. The parent class must contain virtual functions, and the subclass must override the virtual functions in the parent class
  2. A parent class pointer points to a child class object, or a parent class reference binds a child class object
  3. When you call the overridden virtual function in the subclass through the pointer or reference of the parent class, you can see the expression of polymorphism

Here is the sample code:

class Base {
public:
	virtual void myvfunc() {
		cout << "I'm Base" << endl;
	}
};

class Derive : public Base {
public:
	virtual void myvfunc() {
		cout << "I'm Derive" << endl;
	}
};

int main(int argc, char* argv[]) {
	Derive d1;
	Base* b1 = &d1;
	b1->myvfunc();//this is polymorphic

	Base* b2 = new Derive();
	b2->myvfunc();//this is polymorpohic

	Derive d2;
	Base& b3 = d2;
	b3.myvfunc();//this is polymorphic

	system("pause");
	return 0;
}

It can be expected that all the above calls are polymorphic calls, and the output result of the program also shows this:

I'm Derive
I'm Derive
I'm Derive

Consider the following inheritance relationships and overrides:

class Base {
public:
	virtual void f() {}
	virtual void g() {}
	virtual void h() {}
};

class Derive :public Base {
public:
	virtual void g() {}//rewrite g()
};

int main(int argc, char* argv[]) {
	Base b;
	Derive d;
	system("pause");
	return 0;
}

f() and h() in the subclass inherit from the parent class and point to the same position as the parent class, but g() points to the subclass's own g(). The layout of the virtual function table of the subclass and parent class is as follows:

Debugging in vs also illustrates this:


The implementation of codec class is based on the following UML diagram:


The code implementation is as follows:

RequestCodec.h

#ifndef REQUESTCODEC_H
#define REQUESTCODEC_H
#include <string>

#include "Codec.h"
#include "Message.pb.h"

struct RequestInfo {
	int cmdType;
	std::string clientID;
	std::string serverID;
	std::string sign;
	std::string data;
};

class RequestCodec final : public Codec {
public:
	RequestCodec();
	RequestCodec(const std::string& encstr);
	RequestCodec(const RequestInfo* info);
	void initMessage(const std::string& encstr);
	void initMessage(const RequestInfo* info);
	virtual std::string encodeMsg() override;
	virtual void* decodeMsg() override;
	~RequestCodec();

private:
	std::string m_encStr;  //Initialized during deserialization
	RequestMsg m_msg;	   //Initialized when serializing
};

#endif

RequestCodec.cpp

#include "RequestCodec.h"

//Default construction, manually call initMessage()
RequestCodec::RequestCodec() {
}

//For deserialization
RequestCodec::RequestCodec(const std::string& encstr) {
	initMessage(encstr);
}

//For serialization
RequestCodec::RequestCodec(const RequestInfo* info) {
	initMessage(info);
}

//For deserialization
void RequestCodec::initMessage(const std::string& encstr) {
	this->m_encStr = encstr;
}

//For serialization
void RequestCodec::initMessage(const RequestInfo* info) {
	this->m_msg.set_cmdtype(info->cmdType);
	this->m_msg.set_clientid(info->clientID);
	this->m_msg.set_serverid(info->serverID);
	this->m_msg.set_sign(info->sign);
	this->m_msg.set_data(info->data);
}

//Serialize to output and return
std::string RequestCodec::encodeMsg() {
	std::string output;
	this->m_msg.SerializeToString(&output);
	return output;
}

//Deserialize and store the information in RequestInfo and return it
void* RequestCodec::decodeMsg() {
	this->m_msg.ParseFromString(this->m_encStr);
	RequestInfo* reqinfo = new RequestInfo;
	reqinfo->cmdType = m_msg.cmdtype();
	reqinfo->clientID = m_msg.clientid();
	reqinfo->serverID = m_msg.serverid();
	reqinfo->sign = m_msg.sign();
	reqinfo->data = m_msg.data();
	return reqinfo;
}

RequestCodec::~RequestCodec() {
}

ResponseCodec.h

#ifndef RESPONSECODEC_H
#define RESPONSECODEC_H
#include <string>

#include "Codec.h"
#include "Message.pb.h"

struct ResponseInfo {
	int status;
	int seckeyid;
	std::string clientID;
	std::string serverID;
	std::string data;
};

class ResponseCodec final : public Codec {
public:
	ResponseCodec();
	ResponseCodec(const std::string& encstr);
	ResponseCodec(const ResponseInfo* info);
	void initMessage(const std::string& encstr);
	void initMessage(const ResponseInfo* info);
	virtual std::string encodeMsg() override;
	virtual void* decodeMsg() override;
	~ResponseCodec();

private:
	std::string m_encstr;
	ResponseMsg m_msg;
};

#endif

ResponseCodec.cpp

#include "ResponseCodec.h"

//Default construction, manually call initMessage()
ResponseCodec::ResponseCodec() {
}

//For deserialization
ResponseCodec::ResponseCodec(const std::string& encstr) {
	initMessage(encstr);
}

//For serialization
ResponseCodec::ResponseCodec(const ResponseInfo* info) {
	initMessage(info);
}

//For deserialization
void ResponseCodec::initMessage(const std::string& encstr) {
	this->m_encstr = encstr;
}

//For serialization
void ResponseCodec::initMessage(const ResponseInfo* info) {
	this->m_msg.set_status(info->status);
	this->m_msg.set_seckeyid(info->seckeyid);
	this->m_msg.set_clientid(info->clientID);
	this->m_msg.set_serverid(info->serverID);
	this->m_msg.set_data(info->data);
}

//Will m_ The information in MSG is serialized into string and returned
std::string ResponseCodec::encodeMsg() {
	std::string output;
	this->m_msg.SerializeToString(&output);
	return output;
}

//Will m_ The information in encstr is deserialized into ResponseInfo and its pointer is returned
void* ResponseCodec::decodeMsg() {
	ResponseInfo* respinfo = new ResponseInfo;
	this->m_msg.ParseFromString(this->m_encstr);
	respinfo->status = m_msg.status();
	respinfo->seckeyid = m_msg.seckeyid();
	respinfo->clientID = m_msg.clientid();
	respinfo->serverID = m_msg.serverid();
	respinfo->data = m_msg.data();
	return respinfo;
}

ResponseCodec::~ResponseCodec() {
}

Parent Codec of both:

Codec.h

#ifndef CODEC_H
#define CODEC_H
#include <string>

enum Cmd {
	SecKeyAgree = 1,
	SecKeyCheck,
	SecKeyWriteOff
};

class Codec {
public:
	Codec();
	virtual ~Codec();
	//Pure virtual, the base class is not implemented, and it is handed over to the subclass for implementation
	virtual std::string encodeMsg() = 0;
	virtual void* decodeMsg() = 0;
};
#endif

Codec.cpp

#include "Codec.h"

Codec::Codec() {
}

Codec::~Codec() {
}

Factory mode

Try not to modify the source code when adding new functions, and polymorphism is bound to be used

Simple factory mode: only one factory class is required

Factory: use a separate class to create instances

Simple factory: put the creation of objects into a factory class and create different objects through parameters

Disadvantages: every time you add an object, you need to modify the simple factory (although it is not to delete the code, it is only to add a switch case, but it still violates the principle of not changing the code of the class)

Advantages: it eliminates the dependence on specific products and is simple to implement

Use process:

  1. Create a new class
  2. Add a public member function to this class to produce objects (implement polymorphic subclass objects) and return the address of the object, which is called factory function

Pseudo code Description:

class RequestCodec : public Codec;
class ResponseCodec : public Codec;

class Factory {
public:
	Factory();
	~Factory();

	Codec* createObj(int flag) {
		Codec* c = NULL;

		switch (flag) {
		case 1:
			c = new RequestCodec();
			break;
		case 2:
			c = new ResponseCodec();
			break;
		}
		return c;
	}
};

When there are many conditions to judge, the use of switch is more efficient, but note that the flag should have strong continuity

Use of simple factory class:

//Create factory
Factory* f = new Factory();
//Production object
Codec* c=f->createObj(1);
//Use object
c->encodeMsg();

Factory mode: each product is created by a factory, one factory saves a new, and two layers of polymorphism will be used

Features: basically perfect, completely following the principle of "no code change"

Use process:

  1. Create the base class of a factory class, specify a factory method and set it as a virtual function
  2. Each subclass object to be created corresponds to a subclass
  3. Implement the virtual function of the parent class in each child factory class

Pseudo code Description:

class RequestCodec : public Codec;
class ResponseCodec : public Codec;
class TestCodec : public Codec;

class BaseFactory {
public:
	BaseFactory();
	~BaseFactory();
	virtual Codec* createObj() = 0;
};

class RequestFactory : public BaseFactory {
public:
	RequestFactory();
	~RequestFactory();
	Codec* createObj() {
		return new RequestCodec();
	}
};

class ResponseFactory : public BaseFactory {
public:
	ResponseFactory();
	~ResponseFactory();
	Codec* createObj() {
		return new ResponseCodec();
	}
};

class TestFactory : public BaseFactory {
public:
	TestFactory();
	~TestFactory();
	Codec* createObj() {
		return new TestCodec();
	}
};

Use of factory mode:

//Create a RequestFactory object, which is pointed by the parent class. Polymorphism is realized here
BaseFactory* bf = new RequestFactory;
//bf produces an instance of RequestCodec, which is pointed by its parent Codec. Polymorphism is also realized here
Codec* c = bf->createObj();
//Use c
c->encodeMsg();

Overall UML description after using factory pattern:

Client server communication

Multi thread and multi process selection:

  • Priority multithreading
  • When you need to start another executable file on disk, you need to use multiple processes

It can also use IO multiplexing and single thread to process multi client connections, but its efficiency is not high because all client requests are processed in sequence

The most efficient way: multithreading + IO multiplexing

Pseudo code description of epoll:

void acceptConn(void* arg){
	int fd=accept();
	//Add fd to epoll tree
	epoll_ctl(*arg);
}

void connClient(void* arg){
	read();
	write();
	//If the connection is disconnected
	epoll_ctl(epfd,epoll_ctl_del, fd, NULL);
}

int main(){
	int lfd=socket();
	bind();
	listen();
	int epfd=epoll_create(x);
	epoll_ctl(epfd, epoll_ctl_add, ev);
	
	struct epoll_event avsp1024];
	while(1){
		int num=epoll_wait(epfd, evs, 1024, NULL);
		for(int i=0;i<num;++i){
			int curfd=evs[i].fata.fd;
			if(curfd==lfd){
				//accept();
				pthread_create(&tid, NULL, acceptConn, &epfd);
			}else{
				//read();	write();
				//The address of curfd cannot be directly transmitted here
				//In addition, considering disconnection, epfd needs to be passed in together
				pthread_create(&tid, NULL, connClient, &curfd);
			}
		}
	}
}

Thread pool:

  • A collection of multiple threads, which can recycle exhausted threads
  • There is no need to create and destroy threads frequently, which avoids the system overhead

Number of threads in thread pool:

  • If the business logic is computationally intensive and requires a lot of CPU time, the efficiency is the highest when the number of threads is consistent with the number of CPU cores
  • If the business logic is IO intensive, the number of threads = 2 times the number of CPU cores

Optimization of client efficiency: connection pool

  • Before business communication, create all the required connections and put them into a container
  • When you want to communicate, take out a connection (fd) from the container and communicate with the server
  • After the communication is completed, put the connection back into the container

Pseudo code Description:

class ConnectionPopl{
public:
	ConnectionPool(int N){
		for(int i=0;i<N;++i){
			int fd=socket();
			connect();
			this->m_connections.push(fd);
		}
	}
	int getConnection(){
		if(this->m_connections.size()>0){
			int fd=this->m_connections.head();
			this->m_connections.pop();
			return fd;
		}
		return -1;
	}
	int putConnection(int fd){
		//If the connection is valid
		this->m_connections.push(fd);
	}
	~ConnectionPool(){
		//Close all connections
	}

private:
	queue<int> m_connections;
};

Client class encapsulation of socket communication

class TCPClient{
public:
	TCPClient();
	~TCPClient();
	int connectHost(string ip, unsigned short port, int connectTime);
	int disConnect();
	int sendMsg(string msg,int timeout=1000);
	string receiveMsg(int timeout=1000);

private:
	int m_connfd;
};

The server class is encapsulated. The server is not responsible for communication, but only for monitoring. If the communication uses the client class

Changing the above TCPClient to TCPSocket for communication greatly simplifies the design of the server class:

class TCPSocket{
public:
	TCPSocket(){
		this->m_connfd=socket(AF_INET, SOCK_STREAM, 0);
	}
	TCPSocket(int fd){
		this->m_connfd=fd;
	}
	~TCPSocket();
	int connectHost(string ip, unsigned short port, int connectTime){
		connect(m_connfd, &serverAddress, &len);
	}
	int disConnect();
	int sendMsg(string msg,int timeout=1000){
		send(m_connfd, data, datalen, 0);
	}
	string receiveMsg(int timeout=1000){
		recv(m_connfd, buf, size, 0);
		return string(buf);
	}

private:
	int m_connfd;
};

class TCPServer{
public:
	TCPServer();
	~TCPServer();
	TPCSocket* acceptConn(int timeout){
		int fd=accept(m_listenFd, &address, &len);
		TCPSocket* t=new TCPSocket(fd);
		return t;
	}
	int setListen(unsigned short port);
		
private:
	int m_listenFd;
};

Communication flow pseudo code description

Server:

void* callback(void* arg){
	TCPSocket* s=(TCPSocket*)arg;
	s->recvMsg();
	s->sendMsg();
	s->disconnect();
	delete s;
	return NULL;
}

int main(){
	TCPServer* server=new TCPServer();
	while(1){
		TCPSocket* sck=server->acceptConn();
		ptherad_create(&tid, NULL, callback, sck);	
	}
	delete server;
	return 0;
}

client:

int main(){
	TCPSocket* tcp=new TCPSocket();
	TCPSocket->connectHost(ip, port, timeout);
	tcp->sendMsg();
	tcp->recvMsg();
	tcp->disConnect();
	delete tcp;
	return 0;
}

Class diagram description:

Functions that will block in socket communication:

//Blocking waiting for client connections
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

//write will also block when the sliding window is full
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

//Only returned when the connection is successfully established
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Processing idea: set a timeout to force the thread to switch to handle other tasks when the time comes

The IO multiplexer function is used to entrust the kernel to detect the state of fds, including read, write and exception. The last parameter of these functions is to set the timeout length. During the blocking time, if the fd state changes, the function returns directly

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval* timeout);
        
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

accpet timeout processing: just check the read buffer status of listenFd. Here, select() is used for multi-channel IO switching

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

//About timeval
struct timeval {
	time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

//Call instance
struct timeval tval{10,0};
fd_set rdset;
FD_ZERO(&rdset);
FD_SET(listenFd, &rdset);
int ret=select(listenFd+1, &rset, NULL, NULL, &tval);
if(ret==0){
	//timeout
}else if(ret==1){
	//New connection
	int ra=accept(listenFd, &clientAddr, &clientAddrLen);
}else{
	//- 1 was returned with an error
}

Specific code implementation:

TCPSocket* TCPServer::acceptConnect(int waitSeconds) {
  int ret = 0;

  if (waitSeconds > 0) {
    fd_set acceptFdSet;
    FD_ZERO(&acceptFdSet);
    FD_SET(this->m_listenFd, &acceptFdSet);

    struct timeval tval = {waitSeconds, 0};
    do {
      ret = select(m_listenFd + 1, &acceptFdSet, NULL, NULL, &tval);
    } while (ret < 0 && errno == EINTR);  //If interrupted by the signal, enter the cycle again
    if (ret <= 0) {
      return NULL;
    }
  }
  // RET > 0, the call to accept will not block
  struct sockaddr_in clientAddr;
  socklen_t clientAddrLen = sizeof(clientAddr);
  int connectFd = accept(m_listenFd, (sockaddr*)&clientAddr, &clientAddrLen);
  if (connectFd == -1) {
    return NULL;
  }
  return new TCPSocket(connectFd);
}

Read timeout processing: also use select() to detect the read buffer status of fd:

Pseudo code Description:

struct timeval tval{10,0};
fd_set rdset;
FD_ZERO(&rdset);
FD_SET(fd, &rdset);
int ret=select(connFd+1, &rset, NULL, NULL, &tval);
if(ret==0){
	//timeout
}else if(ret==1){
	//New connection
	read()/recv();	//New data sent by the other party has arrived
}else{
	//- 1 was returned with an error
}

Specific implementation code:

std::string TCPSocket::recvMsg(int timeout) {
  int ret = readTimeout(timeout);
  if (ret != 0) {
    if (ret == -1 && errno == ETIMEDOUT) {
      printf("readTimeout(timeout) error:TimeoutError\n");
      return std::string();
    } else {
      printf("readTimeout(timeout) error:%d\n", errno);
      return std::string();
    }
  }

  int netDataLen = 0;
  ret = readn(&netDataLen, 4);
  if (ret == -1) {
    printf("func readn() error:%d\n", ret);
  } else if (ret < 4) {
    printf("func readn() error, peer closed%d\n", ret);
  }

  int dataLen = ntohl(netDataLen);
  char *buf = (char *)malloc(dataLen);
  ret = readn(buf, dataLen);
  if (ret == -1) {
    printf("func readn() error:%d\n", ret);
  } else if (ret < dataLen) {
    printf("func readn() error, peer closed%d\n", ret);
  }

  free(buf);
  return std::string(buf, dataLen);
}

Write timeout processing: after the local write buffer is full, write() will block, so just check the write buffer of write() function

Pseudo code Description:

struct timeval tval{10,0};
fd_set wrset;
FD_ZERO(&wrset);
FD_SET(fd, &wrset);
int ret=select(connFd+1, &wrset, NULL, NULL, &tval);
if(ret==0){
	//timeout
}else if(ret==1){
	//New connection
	write()/send();	//New data sent by the other party has arrived
}else{
	//- 1 was returned with an error
}

Specific implementation code:

int TCPSocket::sendMsg(std::string str, int timeout) {
  int ret = writeTimeout(timeout);
  if (ret == 0) {
    int dataLen = str.size() + 4;
    char *netData = (char *)malloc(dataLen);
    if (netData == NULL) {
      ret = MallocError;
      printf("func sendMsg malloc error\n");
      return ret;
    }
    int netLen = htonl(str.size());
    memcpy(netData, &netLen, 4);
    memcpy(netData + 4, str.data(), str.size());

    int writed = this->writen(netData, dataLen);
    if (writed < dataLen) {  //fail in send
      if (netData != NULL) {
        free(netData);
      }
    }
  } else {  //overtime
    if (ret == -1 && errno == ETIMEDOUT) {
      ret = TimeoutError;
      printf("func sendMsg TimeoutError:%d\n", ret);
    }
  }

  return ret;
}

Concet timeout processing idea

Const() non blocking:

The client calls the connect() function to connect to the server, which has blocking characteristics

The function returns 0 to indicate that the connection is successful, and - 1 to indicate that the connection failed

By default, this function has a timeout, which will be returned after a period of connection failure. However, the timeout is too long and needs to be handled manually:

  1. Set the file descriptor of the connect function operation to non blocking
  2. Call connect
  3. Use select detection: you need to use getsockopt to judge
  4. Set the file descriptor of the connect function operation to blocking: state restore

Judge socket status:

Code implementation:

int TCPSocket::connectHost(std::string IP, unsigned short port, int timeout) {
  int ret = 0;
  if (port < 0 || port > 65535 || timeout < 0) {
    ret = ParamError;
    return ret;
  }

  //Create a socket for communication and check for errors
  this->m_connFd = socket(AF_INET, SOCK_STREAM, 0);
  if (m_connFd < 0) {
    ret = errno;
    printf("func socket() error:%d\n", ret);
    return ret;
  }

  struct sockaddr_in serverAddr;
  memset(&serverAddr, 0, sizeof(serverAddr));
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_port = htons(port);
  serverAddr.sin_addr.s_addr = inet_addr(IP.data());

  ret = this->connectTimeout(&serverAddr, timeout);
  if (ret < 0) {
    if (ret == -1 && errno == ETIMEDOUT) {
      ret = TimeoutError;
    } else {
      printf("func connectTimeout() error:ret=%d,errno=%d\n", ret, errno);
      ret = errno;
    }
  }

  return ret;
}

int TCPSocket::connectTimeout(struct sockaddr_in *addr, uint waitSeconds) {
  setNonBlock(this->m_connFd);

  //At this time, calling connect() will not block and wait for the connection to be established successfully
  int ret = connect(this->m_connFd, (const sockaddr *)addr, sizeof(*addr));
  if (ret < 0 && errno == EINPROGRESS) {  //Connecting
    //Set select listening
    struct timeval tval = {waitSeconds, 0};
    fd_set wrset;
    FD_ZERO(&wrset);
    FD_SET(this->m_connFd, &wrset);
    do {
      ret = select(this->m_connFd + 1, NULL, &wrset, NULL, &tval);
    } while (ret < 0 && errno == EINTR);
  }

  if (ret == 0) {
    //overtime
    ret = -1;
    errno = ETIMEDOUT;
  } else if (ret == 1) {
    //If the connection is successful within the timeout, obtain the status of the file descriptor and detect whether the connection is successful
    int opt = 0;
    socklen_t optLen = sizeof(opt);
    int gr = getsockopt(this->m_connFd, SOL_SOCKET, SO_ERROR, &opt, &optLen);
    if (gr == -1) {
      //connection failed
      ret = -2;
    }
    if (opt == 0) {
      //Connection successful
      ret = 0;
    } else {
      errno = opt;
      ret = -3;
    }
  } else {
    //abnormal
    ret = -4;
  }
  setBlock(this->m_connFd);
  return ret;
}

Sticking packets in TCP communication

Considering the characteristics of network delay and kernel buffer, the receiver may receive multiple pieces of data at one time due to the low frequency of receiving data

Solution:

  • Force flush buffer when sending data
  • When sending data, add a packet header to each packet and store the metadata of the data, such as who the data belongs to and how large it is, so that the TCP data flow can be reasonably split according to the metadata
  • Add end tag

Treatment of sticking package in the project:

int TCPSocket::sendMsg(std::string str, int timeout) {
  int ret = writeTimeout(timeout);

  if (ret == 0) {
    int dataLen = str.size() + 4;
    char *netData = (char *)malloc(dataLen);
    if (netData == NULL) {
      printf("func sendMsg() malloc error\n");
      return MallocError;
    }

    int netLen = htonl(str.size());
    memcpy(netData, &netLen, 4);
    memcpy(netData + 4, str.data(), str.size());

    int writed = this->writen(netData, dataLen);
    if (writed < dataLen) {  //fail in send
      if (netData != NULL) {
        free(netData);
      }
      return -1;
    } else {
		free(netData);
	}
    return 0;  //Sent successfully
  } else {     // writeTimeout() timed out
    if (ret == -1 && errno == ETIMEDOUT) {
      printf("func sendMsg TimeoutError:%d\n", ret);
      return TimeoutError;
    }
  }
}

std::string TCPSocket::recvMsg(int timeout) {
  int ret = readTimeout(timeout);

  if (ret != 0) {
    if (ret == -1 && errno == ETIMEDOUT) {
      printf("readTimeout(timeout) error:TimeoutError\n");
      return std::string();
    } else {
      printf("readTimeout(timeout) error:%d\n", errno);
      return std::string();
    }
  }

  int netDataLen = 0;
  ret = readn(&netDataLen, 4);
  if (ret == -1) {
    printf("func readn() error:%d\n", ret);
    return std::string();
  } else if (ret < 4) {
    printf("func readn() error, peer closed%d\n", ret);
    return std::string();
  }

  int dataLen = ntohl(netDataLen);
  char *buf = (char *)malloc(dataLen + 1);
  if (buf == NULL) {
    printf("func recvMsg malloc error:%d\n", MallocError);
    return std::string();
  }
  memset(buf, 0, dataLen + 1);

  ret = readn(buf, dataLen);
  if (ret == -1) {
    printf("func readn() error:%d\n", ret);
    free(buf);
    return std::string();
  } else if (ret < dataLen) {
    printf("func readn() error, peer closed%d\n", ret);
    free(buf);
    return std::string();
  }

  std::string str(buf);
  free(buf);
  return str;
}

Solve the problem that the dynamic library is installed but cannot be found:

  1. Modify the configuration file: sudo VIM / etc / LD so. conf
  2. Absolute path to write to dynamic library
  3. Make configuration file effective: sudo ldconfig -v

Shared memory

Use process:

Use process:

Header file:

#include <sys/ipc.h>
#include <sys/shm.h>

Function prototype:

int shmget(key_t key, size_t size, int shmflg);

Function: create a piece of shared memory. If it already exists, open the shared memory

Parameters:

  • Key: record the location of shared memory in the kernel through key, which must be > 0
  • Size: specify the size when creating. If you open an existing shared memory, size=0
  • shmflg: used when creating shared memory, similar to the flag of the open function:
    • IPC_CREAT: to create shared memory, you need to specify permission: IPC_CREAT|0664
    • IPC_CREAT|IPC_EXCL: check whether the shared memory exists. If it exists, return - 1. If it does not exist, return 0

Return value:

On success, a valid shared memory identifier is returned. On error, -1 is returned, and errno is set to indicate the error.

Application:

//Create a shared memory
int shmID=shmget(100, 4096, IPC_CREAT|0664);
//Open a block of shared memory
int shmID2=shmget(100, 0, 0);

Associate the current process with shared memory:

Function prototype:

void* shmat(int shmid, const void *shmaddr, int shmflg);

Parameters:

  • shmid: the return value of shmget(), which is equivalent to the handle
  • shmaddr: Specifies the location of shared memory in the kernel. Generally, NULL is passed and specified by the kernel
  • shmflg: operation permission for shared memory after successful association: SHM_RDONLY means read-only, and 0 means read-write

Return value:

On success, shmat() returns the address of the attached shared memory segment; on error, (void *) -1 is returned, and errno is set toindicate the cause of the error.

Application:

void* ptr=shmat(shmID, NULL, 0);

Separate the current process from shared memory:

Function prototype:

int shmdt(const void* shmaddr);

Parameter: shared memory first address

Return value: 0 for success and - 1 for failure

Shared memory operation function:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

Parameters:

  • shmid: operation handle
  • cms: operation type:
    • IPC_STAT: get the status of shared memory
    • IPC_SET: set shared memory status
    • IPC_RMID: Mark shared memory to be destroyed
  • buf: serves the second parameter, which depends on the operation type specified in the second parameter. NULL is generally passed when deleting shared memory

Return value: 0 for success and - 1 for failure

Application instance: delete shared memory:

int ret=shmctl(shmid, IPC_RMID, NULL);

Examples of interprocess communication using shared memory:

Written by:

#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
  //Request a piece of shared memory
  int shmid = shmget(100, 4096, IPC_CREAT | 0664);

  //Associate the shared memory pointed to by shmid with the current process
  void* ptr = shmat(shmid, NULL, 0);
  const char* str = "hello,world\n";
  //Write data to shared memory
  memcpy(ptr, str, strlen(str));

  printf("blocking...\n");
  getchar();

  //Disassociate
  shmdt(ptr);
  //Mark deletion
  shmctl(shmid, IPC_RMID, NULL);
  return 0;
}

readers:

#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
  //Open a shared memory
  int shmid = shmget(100, 0, 0);

  //Associate the shared memory pointed to by shmid with the current process
  void* ptr = shmat(shmid, NULL, 0);
  printf("%s", (char*)ptr);

  printf("blocking...\n");
  getchar();

  //Disassociate
  shmdt(ptr);
  //Mark deletion
  shmctl(shmid, IPC_RMID, NULL);
  return 0;
}

Thinking questions:


ipcs view the communication of the process:

ipcs -m: view the progress of communication using shared memory

ipcs -s: view the process of using semaphore communication

ipcs -a: view the progress of all communications

When using ipcs -m to view the shared memory status, if key==0, the shared memory has been deleted, but there are processes associated with it

Use the command to delete shared memory: ipcrm -M shmkey or ipcrm -m shmid

ftok function:

key_t ftok(const char *pathname, int proj_id);

Call instance:

key_t ftok("/home/",100);
int shmid = shmget(key_t, 4096, IPC_CREAT | 0664);

Difference between mmap and shm:

Encapsulation of shared memory class:

The code implementation is as follows:

class BaseShm {
 public:
  BaseShm(int key);
  BaseShm(std::string path);
  BaseShm(int key, int size);
  BaseShm(std::string path, int size);
  ~BaseShm();
  void* mapShm();
  int unmapShm();
  int delShm();

 private:
  int m_shmid;

 protected:
  void* m_ptr;
};

/*
Open a piece of shm according to the key
*/
BaseShm::BaseShm(int key) { this->m_shmid = shmget(key, 0, 0); }

/*
Open a shm according to the path
*/
BaseShm::BaseShm(std::string path) {
  key_t key = ftok(path.c_str(), 100);
  this->m_shmid = shmget(key, 0, 0);
}

/*
Create a shm based on key and size
*/
BaseShm::BaseShm(int key, int size) {
  this->m_shmid = shmget(key, size, IPC_CREAT | 0664);
}

/*
Create a shm based on path and size
*/
BaseShm::BaseShm(std::string path, int size) {
  key_t key = ftok(path.c_str(), 100);
  this->m_shmid = shmget(key, size, IPC_CREAT | 0664);
}

BaseShm::~BaseShm() {
  unmapShm();
  delShm();
}

/*
Function: associate the current process to the shm created / opened
 Return value: the first address of shm
*/
void* BaseShm::mapShm() {
  this->m_ptr = shmat(this->m_shmid, NULL, 0);
  return this->m_ptr;
}

/*
Function: disassociate the current process from the shm created / opened
 Return value: 0 for success and - 1 for failure
*/
int BaseShm::unmapShm() { return shmdt(this->m_ptr); }

/*
Function: Mark delete created / opened shm
 Return value: 0 for success and - 1 for failure
*/
int BaseShm::delShm() { return shmctl(this->m_shmid, IPC_RMID, NULL); }

C + + syntax problem: when the parent class does not have a default constructor, the subclass needs to explicitly call a constructor of the parent class, such as the following form:

SecureKeyShm::SecureKeyShm(int key) : BaseShm(key) {}
SecureKeyShm::SecureKeyShm(std::string name) : BaseShm(name) {}

If the constructor of the parent class has default parameters, you don't have to call it explicitly

openssl encryption

Common hash algorithms:


Hash value length of MD5: 16B

Hash value length of sha1: 20B

MD5 api:

#define MD5_DIGEST_LENGTH 16 	// Indicates the size of the buffer that should be opened up for storing results
int MD5_Init(MD5_CTX *c);
int MD5_Update(MD5_CTX *c, const void *data, size_t len);
int MD5_Final(unsigned char *md, MD5_CTX *c);
unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md);

api Description:


api of SH1:

# define SHA_DIGEST_LENGTH 20
int SHA1_Init(SHA_CTX *c);
int SHA1_Update(SHA_CTX *c, const void *data, size_t len);
int SHA1_Final(unsigned char *md, SHA_CTX *c);
unsigned char *SHA1(const unsigned char *d, size_t n, unsigned char *md);

api of SH256:

int SHA224_Init(SHA256_CTX *c);
int SHA224_Update(SHA256_CTX *c, const void *data, size_t len);
int SHA224_Final(unsigned char *md, SHA256_CTX *c);
unsigned char *SHA224(const unsigned char *d, size_t n, unsigned char *md);
int SHA256_Init(SHA256_CTX *c);
int SHA256_Update(SHA256_CTX *c, const void *data, size_t len);
int SHA256_Final(unsigned char *md, SHA256_CTX *c);
unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md);

The usage is the same as MD5, and so are the rest

Hash value length:

# define SHA224_DIGEST_LENGTH    28
# define SHA256_DIGEST_LENGTH    32
# define SHA384_DIGEST_LENGTH    48
# define SHA512_DIGEST_LENGTH    64

sha1 test:

void SHA_test() {
  SHA_CTX ctx;
  SHA1_Init(&ctx);
  // SHA1_Update(&ctx, "hello, world", strlen("hello, world"));
  SHA1_Update(&ctx, "hello", strlen("hello"));
  SHA1_Update(&ctx, ", world", strlen(", world"));

  unsigned char* md = new unsigned char[SHA_DIGEST_LENGTH];
  SHA1_Final(md, &ctx);
  //format conversion
  char* res = new char[SHA_DIGEST_LENGTH * 2 + 1];
  for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) {
    sprintf(res + i * 2, "%02x", md[i]);
  }

  std::cout << "sha1:" << res << std::endl;
  delete res;
  delete md;
  return;
}

Encapsulation of hash class:

Code implementation:

enum HashType { H_MD5 = 0, H_SHA1, H_SHA224, H_SHA256, H_SHA384, H_SHA512 };

class Hash {
 public:
  Hash(HashType hashtype);
  ~Hash();
  void addData(const std::string& str);
  std::string result();
  std::string str2hash(const std::string& str);

 private:
  HashType m_hashtype;
  void* m_ctx;
  unsigned char* m_digest;
  char* m_str;
};

Hash::Hash(HashType hashtype) {
  this->m_hashtype = hashtype;
  switch (hashtype) {
    case H_MD5:
      m_digest = new unsigned char[MD5_DIGEST_LENGTH];
      m_str = new char[MD5_DIGEST_LENGTH * 2 + 1];
      m_ctx = new MD5_CTX;
      MD5_Init(static_cast<MD5_CTX*>(m_ctx));
      break;

    case H_SHA1:
      m_digest = new unsigned char[SHA_DIGEST_LENGTH];
      m_str = new char[SHA_DIGEST_LENGTH * 2 + 1];
      m_ctx = new SHA_CTX;
      SHA1_Init(static_cast<SHA_CTX*>(m_ctx));
      break;

    case H_SHA224:
      m_digest = new unsigned char[SHA224_DIGEST_LENGTH];
      m_str = new char[SHA224_DIGEST_LENGTH * 2 + 1];
      m_ctx = new SHA256_CTX;
      SHA224_Init(static_cast<SHA256_CTX*>(m_ctx));
      break;

    case H_SHA256:
      m_digest = new unsigned char[SHA256_DIGEST_LENGTH];
      m_str = new char[SHA256_DIGEST_LENGTH * 2 + 1];
      m_ctx = new SHA256_CTX;
      SHA256_Init(static_cast<SHA256_CTX*>(m_ctx));
      break;

    case H_SHA384:
      m_digest = new unsigned char[SHA384_DIGEST_LENGTH];
      m_str = new char[SHA384_DIGEST_LENGTH * 2 + 1];
      m_ctx = new SHA512_CTX;
      SHA384_Init(static_cast<SHA512_CTX*>(m_ctx));
      break;

    case H_SHA512:
      m_digest = new unsigned char[SHA512_DIGEST_LENGTH];
      m_str = new char[SHA512_DIGEST_LENGTH * 2 + 1];
      m_ctx = new SHA512_CTX;
      SHA512_Init(static_cast<SHA512_CTX*>(m_ctx));
      break;

    default:
      m_ctx = nullptr;
      break;
  }
}

Hash::~Hash() {
  if (m_ctx != nullptr) {
    switch (m_hashtype) {
      case H_MD5:
        delete static_cast<SHA_CTX*>(m_ctx);
        break;
      case H_SHA1:
        delete static_cast<SHA_CTX*>(m_ctx);
        break;
      case H_SHA224:
        delete static_cast<SHA256_CTX*>(m_ctx);
        break;
      case H_SHA256:
        delete static_cast<SHA256_CTX*>(m_ctx);
        break;
      case H_SHA384:
        delete static_cast<SHA512_CTX*>(m_ctx);
        break;
      case H_SHA512:
        delete static_cast<SHA512_CTX*>(m_ctx);
        break;
      default:
        break;
    }
  }
  delete m_digest;
  delete m_str;
}

void Hash::addData(const std::string& m_str) {
  switch (m_hashtype) {
    case H_MD5:
      MD5_Update(static_cast<MD5_CTX*>(m_ctx), m_str.c_str(), m_str.size());
      break;
    case H_SHA1:
      SHA1_Update(static_cast<SHA_CTX*>(m_ctx), m_str.c_str(), m_str.size());
      break;
    case H_SHA224:
      SHA224_Update(static_cast<SHA256_CTX*>(m_ctx), m_str.c_str(),
                    m_str.size());
      break;
    case H_SHA256:
      SHA256_Update(static_cast<SHA256_CTX*>(m_ctx), m_str.c_str(),
                    m_str.size());
      break;
    case H_SHA384:
      SHA384_Update(static_cast<SHA512_CTX*>(m_ctx), m_str.c_str(),
                    m_str.size());
      break;
    case H_SHA512:
      SHA512_Update(static_cast<SHA512_CTX*>(m_ctx), m_str.c_str(),
                    m_str.size());
      break;
    default:
      break;
  }
}

std::string Hash::result() {
  std::string ret;
  switch (m_hashtype) {
    case H_MD5: {
      MD5_Final(m_digest, static_cast<MD5_CTX*>(m_ctx));
      for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
        sprintf(m_str + i * 2, "%02x", m_digest[i]);
      }
      ret = std::string(m_str);
    } break;

    case H_SHA1: {
      SHA1_Final(m_digest, static_cast<SHA_CTX*>(m_ctx));
      for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) {
        sprintf(m_str + i * 2, "%02x", m_digest[i]);
      }
      ret = std::string(m_str);
    } break;

    case H_SHA224: {
      SHA224_Final(m_digest, static_cast<SHA256_CTX*>(m_ctx));
      for (int i = 0; i < SHA224_DIGEST_LENGTH; ++i) {
        sprintf(m_str + i * 2, "%02x", m_digest[i]);
      }
      ret = std::string(m_str);
    } break;

    case H_SHA256: {
      SHA256_Final(m_digest, static_cast<SHA256_CTX*>(m_ctx));
      for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        sprintf(m_str + i * 2, "%02x", m_digest[i]);
      }
      ret = std::string(m_str);
    } break;

    case H_SHA384: {
      SHA512_Final(m_digest, static_cast<SHA512_CTX*>(m_ctx));
      for (int i = 0; i < SHA384_DIGEST_LENGTH; ++i) {
        sprintf(m_str + i * 2, "%02x", m_digest[i]);
      }
      ret = std::string(m_str);
    } break;

    case H_SHA512: {
      SHA512_Final(m_digest, static_cast<SHA512_CTX*>(m_ctx));
      for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
        sprintf(m_str + i * 2, "%02x", m_digest[i]);
      }
      ret = std::string(m_str);
    } break;

    default:
      break;
  }
  return ret;
}

std::string Hash::str2hash(const std::string& m_str) {
  this->addData(m_str);
  return this->result();
}

Features and application scenarios of asymmetric encryption:


Generate RSA key pair:

RSA *RSA_new(void);
BIGNUM *BN_new(void);
int BN_set_word(BIGNUM *a, BN_ULONG w);
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);


Generate key pair instance (in memory):

void generateRSAKsy() {
  RSA* rsa = RSA_new();
  BIGNUM* bn = BN_new();
  BN_set_word(bn, 12345);
  RSA_generate_key_ex(rsa, 1024, bn, NULL);
  return;
}

Write key pair to disk:

Code implementation:

void generateRSAKsy() {
  RSA* rsa = RSA_new();
  BIGNUM* bn = BN_new();
  BN_set_word(bn, 12345);
  RSA_generate_key_ex(rsa, 1024, bn, NULL);

  FILE* fp = fopen("public.pem", "w");
  PEM_write_RSAPublicKey(fp, rsa);
  fclose(fp);

  fp = fopen("private.pem", "w");
  PEM_write_RSAPrivateKey(fp, rsa, NULL, NULL, 0, NULL, NULL);
  fclose(fp);

  return;
}

Errors may be reported under vs:


Use the bio method to write the key pair to the disk. The usage is basically the same as above:

BIO* bio = BIO_new_file("public_bio.pem", "w");
PEM_write_bio_RSAPublicKey(bio, rsa);
BIO_free(bio);

bio = BIO_new_file("private_bio.pem", "w");
PEM_write_bio_RSAPrivateKey(bio, rsa, NULL, NULL, 0, NULL, NULL);
BIO_free(bio);

Remove the public or private key from the memory RSA object:

RSA encryption and decryption function:

//Public key encryption
int RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);
//Private key encryption
int RSA_private_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);
//Public key decryption
int RSA_public_decrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);
//Private key decryption
int RSA_private_decrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);

Parameter Description:


The return value is the ciphertext length;

Example of public key encryption code:

Read public or private key from disk:


If the return value is empty, the reading fails

Code implementation:

string encryptPublicKey() {
  string msg = "hello, world";
  //RSA must be here_ new()
  RSA* publicKey = RSA_new();
  FILE* fp = fopen("public.pem", "r");
  PEM_read_RSAPublicKey(fp, &publicKey, NULL, NULL);
  fclose(fp);

  // The size of buf can be 128B if it is consistent with the key length
  // The length of the ciphertext is the same as that of the key
  //You can use RSA_size() calculate key length
  int keyLen = RSA_size(publicKey);
  unsigned char* buf = new unsigned char[keyLen];
  int strLen = RSA_public_encrypt(msg.size(), (const unsigned char*)msg.c_str(),
                                  buf, publicKey, RSA_PKCS1_PADDING);

  string ret = string((char*)buf, strLen);
  delete buf;
  return ret;
}

string decryptPrivateKey(const string& msg) {
  RSA* privateKey = RSA_new();
  FILE* fp = fopen("private.pem", "r");
  PEM_read_RSAPrivateKey(fp, &privateKey, NULL, NULL);
  fclose(fp);

  unsigned char* to = new unsigned char[128];

  int strLen = RSA_private_decrypt(msg.size(), (unsigned char*)msg.data(), to,
                                   privateKey, RSA_PKCS1_PADDING);
  string ret = string((char*)to, strLen);
  delete to;
  return ret;
}

RSA signature and verification signature:

Signature function:

int RSA_sign(int type, const unsigned char *m, unsigned int m_length, unsigned char *sigret, unsigned int *siglen, RSA *rsa);


Check signature function:

int RSA_verify(int type, const unsigned char *m, unsigned int m_length, const unsigned char *sigbuf, unsigned int siglen, RSA *rsa);


Return value: 1 for success and not equal to 1 for failure

Signature and verification signature code implementation:

void RSASignVerify_test() {
  //File information to be signed
  string s = "hello, world";
  //Read private key
  FILE* fp = fopen("private.pem", "r");
  RSA* privateKey = RSA_new();
  PEM_read_RSAPrivateKey(fp, &privateKey, NULL, NULL);
  fclose(fp);
  //Opening up the signature buffer sing
  int len = RSA_size(privateKey);
  unsigned char* sign = new unsigned char[len];
  unsigned int outLen;
  //Generate signature
  RSA_sign(NID_sha1, (uchar*)s.data(), s.size(), sign, &outLen, privateKey);

  //Read public key
  fp = fopen("public.pem", "r");
  RSA* publicKey = RSA_new();
  PEM_read_RSAPublicKey(fp, &publicKey, NULL, NULL);
  fclose(fp);
  len = RSA_size(publicKey);
  //Verify signature
  int ret =
      RSA_verify(NID_sha1, (uchar*)s.data(), s.size(), sign, outLen, publicKey);
  cout << "ret:" << ret << endl;

  //Tamper with the highest bit of sign[2], test
  sign[2] = (sign[2] & 0x80) ? (sign[2] & 0x7f) : (sign[2] | 0x80);
  ret =
      RSA_verify(NID_sha1, (uchar*)s.data(), s.size(), sign, outLen, publicKey);
  cout << "ret:" << ret << endl;
}

Encapsulation of RSA class:

AES symmetric encryption:

CBC (cipher block chaining) ciphertext grouping link mode:

  • An initialization vector is required, which can be a random string with the same length as the grouping length
  • This initialization vector is required for encryption and decryption, and remains the same
#include <openssl/aes.h>
# define AES_BLOCK_SIZE 16

int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);

Encryption and decryption function:

//Encryption and decryption use the same function
# define AES_ENCRYPT     1
# define AES_DECRYPT     0
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, size_t length, const AES_KEY *key, unsigned char *ivec, const int enc);

AES encryption and decryption code implementation:

void AES_test() {
    //Data to be encrypted
    const char *msg = "hello, world";
    int msgLen = strlen((char *)msg) + 1;
    //The length must be an integral multiple of 16
    int length = 0;
    if (msgLen % 16 != 0) {
        length = (msgLen / 16 + 1) * 16;
    } else {
        length = msgLen;
    }
    //Ciphertext storage space
    char *ciphertext = new char[length];

    //User specified password
    const char *userKey = "0123456776543210";
    //Initialization vector
    unsigned char ivec[AES_BLOCK_SIZE];
    memset(ivec, 9, sizeof(ivec));
    //encryption key 
    AES_KEY enkey;
    AES_set_encrypt_key((uchar *)userKey, 128, &enkey);

    // cbc encryption
    AES_cbc_encrypt((uchar *)msg, (uchar *)ciphertext, length, &enkey, ivec, AES_ENCRYPT);

    //decrypt
    AES_KEY dekey;
    AES_set_decrypt_key((uchar *)userKey, 128, &dekey);
    unsigned char *data = new unsigned char[length];
    // ivec may be changed, and memset needs to be reused
    memset(ivec, 9, sizeof(ivec));
    AES_cbc_encrypt((uchar *)ciphertext, data, msgLen, &dekey, ivec, AES_DECRYPT);
    printf("data:%s\n", data);
    delete[] ciphertext;
    delete[] data;
}

openssl memory release:

RSA* RSA_new(void);
void RSA_free(RSA* rsa);

BIGNUM* BN_new(void);
void BN_free(BIGNUM* bignum);

Use of jsoncpp

json organizes data in two ways: arrays and objects, which can be nested

Array: it is also represented by [], but the data types can be different, such as array a=[int, double, float, char, string, char*, json array, json object]

Object: represented by {} and key value pair. key: value must be a string and value is arbitrary

example:

{
    "name":"Daniel",
    "age":22,
    "sex":man,
    "married":false,
    "family":["father", "mother", "sister"],
    "asset":{
        "car":"BMW",
        "house":"BeiJing"
    }
}

Precautions for writing json files: the outermost node is an array or object, and there can only be one root node

Generate jsoncpp Library under VS: https://blog.csdn.net/qq_43469158/article/details/112172292

Install jsoncpp for linux:

sudo apt-get install libjsoncpp-dev
#Header file directory
/usr/include/jsoncpp/json
#Dynamic library directory
/usr/lib/x86_64-linux-gnu

Library name: libjsoncpp So, link options: - ljsoncpp

Value class in jsoncpp:

Value(ValueType type = nullValue);
Value(Int value);
Value(UInt value);
Value(Int64 value);
Value(UInt64 value);
Value(double value);
Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.)
Value(const char* begin, const char* end); ///< Copy all, incl zeroes.

Judge the type of stored object:

bool isNull() const;
bool isBool() const;
bool isInt() const;
bool isInt64() const;
bool isUInt() const;
bool isUInt64() const;
bool isIntegral() const;
bool isDouble() const;
bool isNumeric() const;
bool isString() const;
bool isArray() const;
bool isObject() const;

Convert Value object to actual data type:

Int asInt() const;
UInt asUInt() const;
Int64 asInt64() const;
UInt64 asUInt64() const;
LargestInt asLargestInt() const;
LargestUInt asLargestUInt() const;
float asFloat() const;
double asDouble() const;
bool asBool() const;

Convert the Value object to a string (with line breaks, suitable for viewing):

String toStyledString() const;

Reader class: parse json string into Value object using parse():

bool parse(const std::string& document, Value& root, bool collectComments = true);
bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true);
//Is is a file stream object. Use this stream object to open a disk file
bool parse(IStream& is, Value& root, bool collectComments = true);

FasterWriter class:

std::string write(const Value& root) JSONCPP_OVERRIDE;

Example of organizing json data to write disk code:

void json_write_test() {
    Value root;
    // JSON:: Value & JSON:: Value:: append (const JSON:: Value & value). Implicit type conversion is performed here
    root.append(12);
    root.append(3.14);
    root.append("hello, world");
    root.append(true);

    Value sub;
    sub.append(1);
    sub.append(2);
    sub.append(false);
    root.append(sub);

    Value obj;
    obj["name"] = "daniel";
    obj["age"] = 22;
    obj["sex"] = "man";
    root.append(obj);

    //Formatted string
    // string json_str = root.toStyledString();

    //Unformatted string
    FastWriter w;
    string json_str = w.write(root);
    cout << json_str << endl;

    ofstream ofs("test.json");
    ofs << json_str;
    ofs.close();
}

Example of reading json data code:

void json_read_test() {
    ifstream ifs("test.json");
    Value root;
    Reader r;
    r.parse(ifs, root);
    if (root.isArray()) {
        for (unsigned int i = 0; i < root.size(); ++i) {
            if (root[i].isInt()) {
                cout << root[i].asInt() << endl;
            } else if (root[i].isDouble()) {
                cout << root[i].asDouble() << endl;
            } else if (root[i].isBool()) {
                cout << root[i].asBool() << endl;
            } else if (root[i].isString()) {
                cout << root[i].asString() << endl;
            } else if (root[i].isArray()) {
                cout << "oh,it's fucking array" << endl;
            } else if (root[i].isObject()) {
                cout << root[i]["age"].asString() << endl;
                cout << root[i]["name"].asString() << endl;
                cout << root[i]["sex"].asString() << endl;
            }
        }
    }
    ifs.close();
}

Requirement analysis of key agreement client

Client initiated request:

  • Key agreement
  • Key verification
  • Key logoff

The client needs to provide the function of interacting with users

The client needs to carry data when communicating with the server:

  • Communication service data
  • Data to identify the client: the ID of the client
  • The server ID that communicates with the client
  • The implementation agrees on a mark cmdType, and the server judges the request type of the client according to this mark
  • Provide signature to the other party to judge whether the data block has been modified

Encapsulate the above contents into a structure:

struct RequestInfo {
    int cmdType;
    std::string clientID;
    std::string serverID;
    std::string sign;	//Signature of data
    std::string data;	//Business data varies according to different cmdtypes
};

Key negotiation client operation process:

Service data analysis of key agreement server:

struct ResponseInfo {
    int status;		//Processing status of client request
    int seckeyid;	//It is only useful when key negotiation generates a new key
    std::string clientID;
    std::string serverID;
    std::string data;	//Actual business data
};

The server only needs to passively accept the client request without interacting with the user. The daemon can be separated from the terminal

Accept and process the client's request and reply data to the client:

  • Processing status of the request
  • Business data obtained from business logic processing

Key agreement server business process:

Properties to be set for writing linux projects using VS:

Add the library dependency in the linker input of the project attribute, and add the name of the library, that is, the - l parameter in gcc, such as pthread

Although not required, you can also add a header file directory to facilitate code prompt

Language selection - std=gnu++11

Key agreement process

Processing logic of client main function:

int main(int argc, char* argv[]) {
	while (1) {
		int select = input();
		switch (select) {
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 0:
			break;
		default:
			break;
		}
	}
	printf("\nbye\n");
	return 0;
}

int input() {
	int select = -1;
	printf("\n***************");
	printf("\n***1.Key agreement***");
	printf("\n***2.Key verification***");
	printf("\n***3.Key logoff***");
	printf("\n***4.Key view***");
	printf("\n***0.Exit the system***");
	scanf("%d", &select);
	while (getchar() != '\n')
		;
	return select;
}

Encapsulation of client operations:

enum Cmd {
	SecKeyAgree = 1,
	SecKeyCheck,
	SecKeyWriteOff
};

class ClientOP {
public:
	explicit ClientOP(const std::string& configFilePath);
	~ClientOP();

	bool secKeyAgree();
	void secKeyCheck();
	void secKeyWriteOff();

private:
	std::string m_clientID;
	std::string m_clientIP;
	std::string m_clientPort;

	std::string m_serverID;
	std::string m_serverIP;
	std::string m_serverPort;
};

Use of smart pointer: STD:: shared_ ptr<TCPSocket> tcp(new TCPSocket());

Implementation of client key agreement:

bool ClientOP::secKeyAgree(bool generateKey, const std::string& publicKeyPath = "", const std::string& privateKeyPath = "") {
	//Request data
	RequestInfo reqInfo;
	reqInfo.cmdType = SecKeyAgree;
	reqInfo.clientID = this->m_clientID;
	reqInfo.serverID = this->m_serverID;
	reqInfo.data = "";
	reqInfo.sign = "";
	//Encryption and decryption object pointer
	RSACrypto* rsa;

	if (generateKey) {	//Generate key pair
		rsa = new RSACrypto();
		//Read public key
		//No error checking
		std::ifstream ifs("public.pem");
		std::stringstream strs;
		strs << ifs.rdbuf();
		std::string pubKey = strs.str();
		reqInfo.data = pubKey;
		//Generate signature for public key data
		reqInfo.sign = rsa->sign(pubKey);
		ifs.close();
	} else {  //Do not generate, read key pair
		rsa = new RSACrypto(publicKeyPath, privateKeyPath);
		//Read public key
		//No error checking
		std::ifstream ifs(publicKeyPath);
		std::stringstream strs;
		strs << ifs.rdbuf();
		std::string pubKey = strs.str();
		reqInfo.data = pubKey;
		reqInfo.sign = rsa->sign(pubKey);
		ifs.close();
	}

	CodecFactory* reqcFactory = new RequestFactory(&reqInfo);
	Codec* reqc = reqcFactory->createCodec();
	//serialize
	std::string encstr = reqc->encodeMsg();
	delete reqcFactory;
	delete reqc;

	//Socket object for communication
	std::shared_ptr<TCPSocket> tcp(new TCPSocket());
	int ret = tcp->connectHost(this->m_serverIP, std::stoi(m_serverPort));
	std::string response;
	if (ret != 0) {
		//No error log written
		return false;
	} else {
		ret = tcp->sendMsg(encstr);
		if (ret != 0) {
			//No error log written
			return false;
		}
		response = tcp->recvMsg();
	}

	CodecFactory* respcFactory = new ResponseFactory(response);
	Codec* respc = respcFactory->createCodec();
	//Deserialize to get the response data structure
	ResponseInfo* respinfo;
	respinfo = (ResponseInfo*)respc->decodeMsg();

	delete respcFactory;
	delete respc;

	if (respinfo->status != 0) {  //Invalid server reply
		return false;			  //No error log written
	} else {
		//Decrypt the data with the private key to obtain the AES key
		this->m_AESKey = rsa->decryptByPrivateKey(respinfo->data);
		return true;
	}

	delete respinfo;
	delete rsa;
}

Using pthrea in C + +_ The create() callback must be of the following three types:

  • Static member function of class
  • Friend function of class
  • General global function

For example:

void ServerOP::start() {
	std::shared_ptr<TCPServer> s(new TCPServer);
	while (1) {
		std::shared_ptr<TCPSocket> tcp(s->acceptConnect());
		if (tcp == nullptr) {
			continue;
		}
		pthread_t tid;
		pthread_create(&tid, NULL, working, NULL);
	}
}

The working() function is written in the following three ways:

Use static functions:

class ServerOP {
public:
	explicit ServerOP(const std::string& configFilePath);
	~ServerOP();
	void start();
	//The callback function of the child thread must be static and does not depend on any instance
	static void* working(void* arg);

private:
	std::string m_serverID;
	std::string m_serverIP;
	std::string m_serverPort;
};

Friend function implementation is not recommended because it destroys the encapsulation of the class:

class ServerOP {
public:
	explicit ServerOP(const std::string& configFilePath);
	~ServerOP();
	void start();
	friend void* working(void* arg);

private:
	std::string m_serverID;
	std::string m_serverIP;
	std::string m_serverPort;
};

void* working(void* arg) {
}

General global function implementation, omitted

Implementation of server-side key agreement:

void ServerOP::start() {
	m_server->setListen(stoi(this->m_serverPort));

	while (1) {
		TCPSocket* tcp = m_server->acceptConnect();
		if (tcp == nullptr) {
			continue;
		}
		pthread_t tid;
		//The following two operations require thread synchronization
		pthread_rwlock_wrlock(&m_tid2tcp_lock);
		pthread_create(&tid, NULL, working, (void*)this);
		this->m_tid2tcp.insert(std::make_pair(tid, tcp));
		pthread_rwlock_unlock(&m_tid2tcp_lock);
	}
}

void* ServerOP::working(void* arg) {
	ServerOP* self = (ServerOP*)arg;
	//Get the pointer of the communication socket from the map
	pthread_rwlock_rdlock(&(self->m_tid2tcp_lock));
	TCPSocket* tcp = self->m_tid2tcp[pthread_self()];
	pthread_rwlock_unlock(&(self->m_tid2tcp_lock));

	std::string msg = tcp->recvMsg();

	//Produce a request decoding class and decode it to get reqinfo
	CodecFactory* reqFactory = new RequestFactory(msg);
	Codec* reqc = reqFactory->createCodec();
	RequestInfo* reqinfo = (RequestInfo*)reqc->decodeMsg();
	delete reqFactory;
	delete reqc;

	//Different processing according to client selection
	switch (reqinfo->cmdType) {
	case SecKeyAgree:
		self->secKeyAgree(reqinfo);
		break;
	case SecKeyCheck:
		break;
	default:
		break;
	}
	delete reqinfo;
	return nullptr;
}

void ServerOP::secKeyAgree(RequestInfo* reqinfo) {
	//Write public key to file
	std::ofstream ofs1("public.pem");
	ofs1 << reqinfo->data;
	ofs1.close();

	ResponseInfo respinfo;
	RSACrypto rsa("public.pem", "");
	if (!rsa.verify(reqinfo->data, reqinfo->sign)) {
		//No error log was printed
		respinfo.status = -1;
	} else {
		std::string key = getRandKeyString(Len16);
		std::string cipherKey = rsa.encryptByPublicKey(key);

		respinfo.clientID = reqinfo->clientID;
		respinfo.data = cipherKey;
		//TODO needs to read the database
		respinfo.seckeyid = 0;
		respinfo.serverID = m_serverID;
		respinfo.status = 0;
	}
	CodecFactory* respFactory = new ResponseFactory(&respinfo);
	Codec* respc = respFactory->createCodec();
	std::string msg = respc->encodeMsg();
	delete respFactory;
	delete respc;

	int ret = this->m_tid2tcp[pthread_self()]->sendMsg(msg);
	if (ret == 0) {	 //Sent successfully
		this->m_tid2tcp[pthread_self()]->~TCPSocket();
		this->m_tid2tcp.erase(pthread_self());
	} else {  //fail in send
		//TODO error handling
	}
}

Generate random key:

std::string ServerOP::getRandKeyString(KeyLen keylen) {
	srand(time(NULL));
	std::string ret(keylen, '\0');
	int flag = 0;
	const char* specialChara = "~!@#$%^&*(){}|:<>?";
	for (int i = 0; i < keylen; ++i) {
		flag = rand() % 4;
		switch (flag) {
		case 0:
			ret[i] = 'a' + rand() % 26;
			break;
		case 1:
			ret[i] = 'A' + rand() % 26;
			break;
		case 2:
			ret[i] = '0' + rand() % 10;
			break;
		case 3:
			ret[i] = specialChara[rand() % strlen(specialChara)];
			break;
		default:
			break;
		}
	}
	return ret;
}

Troubleshooting methods of openssl Library:

#include <openssl/err.h>
std::cout << "error:" << std::hex << ERR_get_error() << std::endl;
//In shell:
openssl errstr HEXNUM

Note that in the signature and verification signature function provided in the RSACrypto encryption tool class, the signature data should not be too long. In order to solve this problem, Mr. msg should be converted into a hash value, and then the hash value should be signed and verified:

std::string RSACrypto::sign(const std::string& msg, int hashType) {
	int len = RSA_size(m_privateKey);
	unsigned char* sign = new unsigned char[len];
	unsigned int signLen = 0;
	//Generate signature: hash first, and then sign digest
	Hash h;
	std::string digest = h.str2hash(msg);
	int RSA_signRet = RSA_sign(hashType, (uchar*)digest.data(), digest.size(), sign, &signLen, m_privateKey);
	if (RSA_signRet != 1) {
		std::cout << "\nERR_get_error:" << std::hex << ERR_get_error() << std::endl
				  << "RSA_signRet:" << RSA_signRet << std::endl
				  << "signLen:" << signLen << std::endl;
	}
	std::string ret = std::string((char*)sign, (int)signLen);
	delete[] sign;
	return ret;
}

bool RSACrypto::verify(const std::string& msg, const std::string& signature, int hashType) {
	Hash h;
	std::string digest = h.str2hash(msg);
	//Check signature: hash first, and then check the signature of digest
	int ret = RSA_verify(hashType, (uchar*)digest.data(), digest.size(), (uchar*)signature.data(), signature.size(), m_publicKey);
	if (ret == 1) {
		return true;
	} else {
		return false;
	}
}

base64

Solve the problem of \ 0 during transmission: use base64 encoding

alphabet:

base64 application scenario:

base64 algorithm:


After encoding, the data becomes longer. Every three bytes are a group, and one byte is added to each group after encoding

The = of the encoded data represents the filling data 0

Working mode of BIO chain in openssl:

api corresponding to bio:

bio chain is similar to the concept of pipeline. After connecting nodes with different functions in series, a series of operations can be completed

base64 encoding and decoding using bio of openssl:

code:

string bio_base64_encode_demo(const string &s) {
	const char *str = s.data();
	BIO *b64 = BIO_new(BIO_f_base64());
	BIO *mem = BIO_new(BIO_s_mem());
	b64 = BIO_push(b64, mem);
	BIO_write(b64, str, strlen(str));
	BIO_flush(b64);
	BUF_MEM *ptr;
	BIO_get_mem_ptr(b64, &ptr);
	char *buf = new char[ptr->length];
	memcpy(buf, ptr->data, ptr->length);

	string ret(buf);
	BIO_free_all(b64);
	delete[] buf;
	return ret;
}

decode:

string bio_base64_decode_demo(const string &s) {
	BIO *b64 = BIO_new(BIO_f_base64());
	BIO *mem = BIO_new_mem_buf(s.data(), s.length());
	mem = BIO_push(b64, mem);

	char *buf = new char[s.length()];
	BIO_read(mem, buf, s.length());
	string ret(buf);
	BIO_free_all(b64);
	delete[] buf;
	return ret;
}

Log class and singleton mode

Singleton mode: ensure that a class has only one instance, and provide a global access point to access it. This instance is shared by all program modules.

  1. Privatize its constructor to prevent the outside world from creating objects of singleton classes;
  2. Use the private static pointer variable of the class to point to the unique instance of the class;
  3. Use a public static method to get the instance.
class Logger final {
public:
	enum Type {
		CONSOLE,
		FILE
	};
	enum Level {
		DEBUG,
		INFO,
		WARRING,
		ERROR,
		CRITICAL
	};
	static Logger* getInstanse();
	void Log(std::string text, std::string file, int line, Level level = INFO);
	void setEnableLevel(Level level);
	void setDevice(Type device);
	~Logger();

private:
	Logger();
	Logger(const Logger& logger);
	Type m_device;
	std::ofstream m_writer;
	static Logger m_log;
	Level m_level;
	Type m_device;
};

The starving Han singleton mode is adopted: declare the constructor private, save a class instance of Logger as a global member variable, and provide a static method to obtain the instance

Connection pool code implementation:

class ConnectionPool final {
public:
	ConnectionPool(std::string IP, unsigned short port, int capacity);
	TCPSocket* getConnection();
	void putConnection(TCPSocket* tcp, bool isValid = true);
	bool isEmpty();
	~ConnectionPool();

private:
	void createConnection();
	std::string m_serverIP;
	unsigned short m_port;
	int m_capacity;
	int m_nodeNum;
	std::queue<TCPSocket*> m_queue;
	pthread_mutex_t m_lock;
};

ConnectionPool::ConnectionPool(std::string IP, unsigned short port, int capacity)
	: m_serverIP(IP),
	  m_port(port),
	  m_capacity(capacity),
	  m_nodeNum(capacity) {
	pthread_mutex_init(&m_lock, NULL);
	createConnection();
}

TCPSocket* ConnectionPool::getConnection() {
	if (m_queue.empty()) {
		std::cout << "getConnection() error:queue is empty" << std::endl;
		return NULL;
	}
	pthread_mutex_lock(&m_lock);
	TCPSocket* tcp = m_queue.front();
	m_queue.pop();
	pthread_mutex_unlock(&m_lock);

	std::cout << "get a connection,current queue size:" << m_queue.size() << std::endl;
	return tcp;
}

void ConnectionPool::putConnection(TCPSocket* tcp, bool isValid = true) {
	if (isValid) {
		pthread_mutex_lock(&m_lock);
		m_queue.push(tcp);
		pthread_mutex_unlock(&m_lock);
		std::cout << "put a valid connection,current queue size:" << m_queue.size() << std::endl;
	} else {
		tcp->disconnect();
		delete tcp;
		//Create a new connection
		pthread_mutex_lock(&m_lock);
		m_nodeNum = m_queue.size() + 1;
		pthread_mutex_unlock(&m_lock);
		createConnection();
	}
}

bool ConnectionPool::isEmpty() {
	return m_queue.empty();
}

ConnectionPool::~ConnectionPool() {
	pthread_mutex_destroy(&m_lock);
	while (m_queue.size() > 0) {
		TCPSocket* tcp = m_queue.front();
		m_queue.pop();
		delete tcp;
	}
}

void ConnectionPool::createConnection() {void ConnectionPool::createConnection() {
	std::cout << "current queue size:" << m_queue.size() << " nodeNum:" << m_nodeNum << std::endl;
	if (m_queue.size() >= m_nodeNum) {
		return;
	}
	TCPSocket* tcp = new TCPSocket();
	int ret = tcp->connectHost(m_serverIP, m_port);
	if (ret == 0) {
		m_queue.push(tcp);
	} else {
		delete tcp;
		std::cout << "createConnection() connectHost() failed,ret:" << ret << std::endl;
	}
	createConnection();	 //Recursive progress
}

C + + tips: inline member functions must be implemented at the same time as the declaration and cannot be separated

gdb tips: complete the current cycle: until

time function: seconds elapsed from 1970.1.1 to now

Because the server will connect multiple different clients, it needs multiple different keys; The client only needs one key

In order to support high concurrency and clustering, the shared memory of the server can be changed to redis

Shared memory

Definition of data structure for storing shared memory:

struct NodeShmInfo {
	NodeShmInfo()
		: status(0), seckeyID(0) {
		memset(clientID, 0, 12 + 12 + 128);
	}
	int status;
	int seckeyID;
	char clientID[12];
	char serverID[12];
	char seckey[128];
};

Shared memory initialization and read / write functions:

void SecureKeyShm::shmInit() {
	if (this->m_ptr) {
		memset(this->m_ptr, 0, m_maxNode * sizeof(NodeShmInfo));
	}
}

int SecureKeyShm::shmWrite(NodeShmInfo* pnsmi) {
	NodeShmInfo* nodeArr = static_cast<NodeShmInfo*>(mapShm());
	if (nodeArr == nullptr || pnsmi == nullptr) {
		return -1;
	}
	for (int i = 0; i < m_maxNode; ++i) {
		if (strcmp(pnsmi->clientID, nodeArr[i].clientID) == 0 && strcmp(pnsmi->serverID, nodeArr[i].serverID) == 0) {
			memcpy(&nodeArr[i], pnsmi, sizeof(NodeShmInfo));
			unmapShm();
			return 0;
		}
		if (nodeArr[i].status == 0) {
			memcpy(&nodeArr[i], pnsmi, sizeof(NodeShmInfo));
			unmapShm();
			return 0;
		}
	}
	return -2;
}
NodeShmInfo SecureKeyShm::shmRead(const std::string& clientID, const std::string& serverID) {
	NodeShmInfo* nodeArr = static_cast<NodeShmInfo*>(mapShm());
	if (nodeArr == nullptr) {
		return NodeShmInfo();
	}
	const char* s1 = clientID.data();
	const char* s2 = serverID.data();
	for (int i = 0; i < m_maxNode; ++i) {
		if (nodeArr[i].status == 1 && strcmp(s1, nodeArr[i].clientID) == 0 && strcmp(s2, nodeArr[i].serverID) == 0) {
			NodeShmInfo ret(nodeArr[i]);
			unmapShm();
			return ret;
		}
	}
	return NodeShmInfo();
}

occi

Oracle C++ Call Interface (OCCI)

su - root, - indicates that the environment variables are switched together

View firewall status: systemctl status firewalld

Start firewall: systemctl start firewalld

Turn off the firewall: systemctl stop firewalld

Permanently start the firewall: systemctl enable firewalld

Permanently close the firewall: systemctl disable firewalld

Start oracle Database

sqlplus / as sysdba
startup
#close
shutdown immediate
#Turn on Remote
lsnrctl start

Steps for using occi:

Initialize environment:

Environment *env = Environment::createEnvironment();
Environment::terminateEnvironment(env);

connect:

  virtual Connection * createConnection(
    const OCCI_STD_NAMESPACE::string &userName,
    const OCCI_STD_NAMESPACE::string &password, 
    const OCCI_STD_NAMESPACE::string &connectString = "") = 0;

Connection string: IP:Port/orcl

Data table (mysql):

create database if not exists secmng character set utf8;

use secmng;

create table secnode(
  id             VARCHAR(7),
  node_name      VARCHAR(128) not null,
  node_desc      VARCHAR(512),
  create_time    datetime,
  auth_code      int,
  node_state     int
);
alter table secnode add constraint PK_SECNODE primary key(id);

insert into secnode values('hz_s001','Online banking center1','HangZhou','2022-02-03 12:32:09',187,0);
insert into secnode values('hz_c001','HangZhou user1','HangZhou','2022-08-03 14:32:48',173,0);
insert into secnode values('hz_s002','Online banking center2','HangZhou','2022-09-03 12:32:09',188,0);
insert into secnode values('hz_c002','HangZhou user2','HangZhou','2022-02-23 14:32:48',174,0);
insert into secnode values('hz_s003','Online banking center3','HangZhou','2022-02-03 12:32:09',189,0);
insert into secnode values('hz_c003','HangZhou user3','HangZhou','2022-02-13 14:32:48',175,0);

insert into secnode values('gz_s001','GuangDong sub center','GuangDong','2012-04-09 12:32:34',198,0);
commit;

create table seckeyinfo(
    clientid        VARCHAR(7),
    serverid        VARCHAR(7),
    keyid           int,
    create_time     datetime,
    key_state       int,
    seckey          VARCHAR(512)
);
alter table seckeyinfo add constraint PK_SECKEYINFO primary key(keyid);
alter table seckeyinfo add constraint FK_SECKEYINFO_CLIENTID foreign key(clientid) references secnode(id);
alter table seckeyinfo add constraint FK_SECKEYINFO_SERVERID foreign key(serverid) references secnode(id);
commit;

create table keysn(
  ikeysn  int primary key
);
insert into keysn values(1);
commit;

In order to ensure data security, you can create multiple secondary users with different permissions, such as creating users who can only query

Add mysql database class:

#ifndef MYSQLOP_H
#define MYSQLOP_H
#include <mysql/mysql.h>

#include <iostream>

#include "Logger.h"
#include "SecureKeyShm.h"

class MySQLOP final {
public:
	MySQLOP(std::string host, std::string username, std::string pwd, std::string db);
	MySQLOP(const MySQLOP& m) = delete;
	MySQLOP operator=(const MySQLOP& m) = delete;
	~MySQLOP();
	int getKeyID();
	bool updateKeyID(int keyID);
	bool writeSecKey(const NodeShmInfo* pnode);
	std::string getCurrTime();

private:
	MYSQL* m_mysql;
};

MySQLOP::MySQLOP(std::string host, std::string username, std::string pwd, std::string db) {
	m_mysql = mysql_init(NULL);
	if (m_mysql == nullptr) {
		// TODO
	}
	m_mysql = mysql_real_connect(m_mysql, host.data(), username.data(), pwd.data(), db.data(), 0, NULL, 0);
	if (m_mysql == nullptr) {
		// TODO
		std::cout << "connect to mysql error" << std::endl;
	}
	//Set auto submit
	mysql_query(m_mysql, "set autocommit=1");
	std::cout << "connected to mysql" << std::endl;
}

MySQLOP::~MySQLOP() {
	if (m_mysql) {
		mysql_close(m_mysql);
	}
}

int MySQLOP::getKeyID() {
	const char* sql = "select ikeysn from keysn";
	mysql_query(m_mysql, sql);
	MYSQL_RES* res = mysql_store_result(m_mysql);
	MYSQL_ROW rows = mysql_fetch_row(res);
	if (rows[0] != nullptr) {
		return atoi(rows[0]);
	} else {
		return -1;
	}
}

bool MySQLOP::updateKeyID(int keyID) {
	std::string sql = "update keysn set ikeysn=";
	sql += std::to_string(keyID);
	return 0 == mysql_query(m_mysql, sql.data());
}

bool MySQLOP::writeSecKey(const NodeShmInfo* pnode) {
	char query_str[1024] = {0};
	memset(query_str, 0, sizeof(query_str));
	// insert into seckeyinfo values('hz_c001','hz_s001',187,date_format('2008-08-08 22:23:01', '%Y%m%d%H%i%s'),1,'randomstringtest');
	sprintf(query_str, "insert into seckeyinfo values('%s','%s',%d,date_format('%s','%%Y%%m%%d%%H%%i%%s'),%d,'%s')", pnode->clientID, pnode->serverID, pnode->seckeyID, getCurrTime().data(), pnode->status, pnode->seckey);
	// TODO error handling
	return 0 == mysql_query(m_mysql, query_str);
}

std::string MySQLOP::getCurrTime() {
	time_t t = time(NULL);
	char ch[64] = {0};
	char result[100] = {0};
	strftime(ch, sizeof(ch) - 1, "%Y-%m-%d %H:%M:%S", localtime(&t));
	sprintf(result, "%s", ch);
	return std::string(result);
}

#endif

Configuration management terminal: use qt to realize crud of secnode. Skip

Outreach interface

Provide users with encryption and decryption APIs instead of keys

Profile:

Provide dynamic library or static library:

AES symmetric encryption class:

#ifndef AESCRYPTO_H
#define AESCRYPTO_H
#include <openssl/aes.h>
#include <string.h>

#include <string>

#include "base64.h"

class AESCrypto final {
public:
	AESCrypto(std::string key);
	~AESCrypto();
	std::string AES_CBC_Entrypt(std::string text);
	std::string AES_CBC_Decrypt(std::string ciphertext);

private:
	std::string m_key;
	AES_KEY m_encKey;
	AES_KEY m_decKey;
};

AESCrypto::AESCrypto(std::string key) {
	size_t len = key.size();
	if (len == 16 || len == 24 || len == 32) {
		const unsigned char* aeskey = (const unsigned char*)key.data();
		AES_set_encrypt_key(aeskey, len * 8, &m_encKey);
		AES_set_decrypt_key(aeskey, len * 8, &m_decKey);
		m_key = key;
	}
}

AESCrypto::~AESCrypto() {
}

std::string AESCrypto::AES_CBC_Entrypt(std::string text) {
	int len = 0;
	if (text.size() % 16 != 0) {
		len = (text.size() / 16 + 1) * 16;
	} else {
		len = text.size();
	}
	unsigned char* cipherdata = new unsigned char[len];
	unsigned char ivec[AES_BLOCK_SIZE];
	memset(ivec, 9, sizeof(ivec));
	//	AES_cbc_encrypt((uchar *)msg, (uchar *)ciphertext, length, &enkey, ivec, AES_ENCRYPT);
	AES_cbc_encrypt((unsigned char*)text.data(), cipherdata, len, &m_encKey, ivec, AES_ENCRYPT);
	char* out = new char[BASE64_ENCODE_OUT_SIZE(len)];
	base64_encode(cipherdata, len, out);
	std::string ret(out);
	delete[] out;
	delete[] cipherdata;
	return ret;
}
std::string AESCrypto::AES_CBC_Decrypt(std::string ciphertext) {
	unsigned char* cipherdata = new unsigned char[BASE64_DECODE_OUT_SIZE(ciphertext.size())];
	int len = base64_decode(ciphertext.data(), ciphertext.size(), cipherdata);
	unsigned char* out = new unsigned char[len];
	unsigned char ivec[AES_BLOCK_SIZE];
	memset(ivec, 9, sizeof(ivec));
	AES_cbc_encrypt(cipherdata, out, len, &m_decKey, ivec, AES_DECRYPT);
	std::string ret((char*)out);
	delete[] out;
	delete[] cipherdata;
	return ret;
}
#endif

External interface class:

#ifndef CRYPTOINTERFACE_H
#define CRYPTOINTERFACE_H
#include <jsoncpp/json/json.h>

#include <fstream>
#include <string>

#include "AESCrypto.h"
#include "BaseShm.h"
#include "SecureKeyShm.h"

class CryptoInterface {
public:
	CryptoInterface(std::string configure);
	~CryptoInterface();
	std::string encryptoData(std::string text);
	std::string decryptoData(std::string ciphertext);

private:
	std::string m_key;
};

CryptoInterface::CryptoInterface(std::string configure) {
	std::ifstream ifs(configure);
	Json::Value root;
	Json::Reader r;
	r.parse(ifs, root);
	std::string key = root["shmKey"].asString();
	std::string serverID = root["serverID"].asString();
	std::string clientID = root["clientID"].asString();
	int maxNode = root["maxNode"].asInt();
	SecureKeyShm shm(key, maxNode);
	NodeShmInfo node = shm.shmRead(clientID, serverID);
	m_key = node.seckey;
	ifs.close();
}

CryptoInterface::~CryptoInterface() {
}

std::string CryptoInterface::encryptoData(std::string text) {
	AESCrypto aes(m_key);
	return aes.AES_CBC_Entrypt(text);
}

std::string CryptoInterface::decryptoData(std::string ciphertext) {
	AESCrypto aes(m_key);
	return aes.AES_CBC_Decrypt(ciphertext);
}

#endif

Encapsulate the extranet interface as a library:

g++ -c *.cpp -fPIC
g++ -shared *.o -o libname.so

Key verification and key logoff

Key verification: the client hashes the local key and sends the hash value to the server for comparison

Key logout: the client modifies the local key status and notifies the server that the key of this ID is discarded. The server marks the key in the shared memory as unavailable through this ID and updates the database

Key viewing: the client entrusts the server to check the database

Topics: C++ network Container security https