Project introduction and cryptography knowledge
Overall structure of the project:
Main function: encrypt and decrypt the data of network communication
Basic components:
- Data serialization: protobuf
- socket communication: thread pool, connection pool
- Shared memory IPC
- oracle Database: using OCI interface
- Data encryption: openssl (Secure Sockets Layer)
Three elements of encryption:
- Plaintext and ciphertext
- Secret key: a fixed length string
- Algorithm: encryption algorithm and decryption algorithm
Common encryption methods:
-
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
-
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)
- No matter how long the original data is, the number of bits of the hash result is the same
- The original data are a little different, and the results are completely different
- Strong collision resistance (carrying burst)
- Irreversible: it is impossible to deduce the original content from the result (which determines that it cannot be an encryption algorithm)
Application scenario:
- Data verification: MD5 value will be provided when downloading the file to verify whether the file has been tampered with
- Login verification: there is no need to save password plaintext in the system, just save its corresponding hash value
- 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:
- Key distribution difficulty
- 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:
- Generate an asymmetric key pair and distribute the public key
- 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)
- The hash value is encrypted with the private key to obtain the ciphertext
- Send the original data and ciphertext to the receiver
The process of receiver verifying signature:
- Public key distributed by the party receiving the signature
- Receiving data from sender: original data and signature
- Perform the same 0 hash operation on the received original data to obtain the hash value 1
- Decrypt the ciphertext with the public key to obtain the hash value of 2
- 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:
- Create a new file in the workspace
- The new file is added to the local warehouse and saved in the staging area
- 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
- An object of one class is used as a parameter or return value of a method of another class
- Use the object of another class as a local variable of its object in the method of one class
- 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:
- XML
- JSON
- 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:
- Include header and source files
- Project attribute configuration header file directory and Library Directory
- Add lib library to linker input of project properties
- 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:
- 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
- A parent class pointer points to a child class object, or a parent class reference binds a child class object
- 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:
- Create a new class
- 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:
- Create the base class of a factory class, specify a factory method and set it as a virtual function
- Each subclass object to be created corresponds to a subclass
- 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:
- Set the file descriptor of the connect function operation to non blocking
- Call connect
- Use select detection: you need to use getsockopt to judge
- 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:
- Modify the configuration file: sudo VIM / etc / LD so. conf
- Absolute path to write to dynamic library
- 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.
- Privatize its constructor to prevent the outside world from creating objects of singleton classes;
- Use the private static pointer variable of the class to point to the unique instance of the class;
- 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