Project introduction:
Complete the development of client (control end) + server (controlled end). The client mainly includes: acquisition of disk and file information; File download; Monitor, lock and unlock each other's screen, and the server realizes the function of automatic operation after startup.
Sorting of some codes:
1. Encapsulation of data packet
Package design diagram
CPacket(const BYTE* pData, size_t& nSize) { size_t i = 0; //i: Ruler /*Parse Baotou*/ for (; i < nSize; i++) { if (*(WORD*)(pData + i) == 0xFEFF) { sHead = *(WORD*)(pData + i);//Set the value of the header i += 2;//i move back 2 bytes to process the next data content (length of read data) break; } } /*nSize If it is less than the length of packet header (2) + packet length (4) + command (2) + checksum (2), it indicates that the parsing fails because there is no basic packet length*/ if (i + 4 + 2 + 2 > nSize) { nSize = 0; return; } /*Read the length of the packet data*/ nLength = *(DWORD*)(pData + i); i += 4;//i move back 4 bytes to process the next data content (parsing command) if (nLength + i > nSize) {//If nSize is less than the length of the packet data + (2 bytes of the header + 4 bytes of the written packet data length), the parsing fails because there is no basic packet length nSize = 0; return; } /*Parse command*/ sCmd = *(WORD*)(pData + i); //The two bytes here are commands, i += 2; //i move back 2 bytes to process the next data content (parse data) if (nLength > 4) { strData.resize(nLength - 2 - 2);//Data length = packet data length - command (2) - sum check (2) memcpy((void*)strData.c_str(), pData + i, nLength - 4); //Copy data content to strData i += nLength - 4;//i move nLength-4 bytes backward to process the next data content (parsing and verification) } /*Parsing and verification*/ sSum = *(WORD*)(pData + i); i += 2;//i moves back 2 bytes, and i points to the end of the packet WORD sum = 0; for (size_t j = 0; j < strData.size(); j++) sum += BYTE(strData[j]) & 0xFF;//Sum if (sum == sSum)//Data received successfully { nSize = i; //The size of a complete package return; } /*Parsing failed, return 0*/ nSize = 0; }
2. Analysis of several ways of thread synchronization
a. Mutually exclusive
Under this mechanism, threads need to lock and unlock public variables after use. At this time, if there is a thread that directly accesses the variable without locking, this mechanism is invalid. Therefore, mutual exclusion depends on the programming of developers; Secondly, after thread 1lock variable, if thread 2 also needs to access the variable at this time, thread 2 will block until thread 1lock, that is, the "simultaneous" becomes "queuing", which reduces the efficiency, and the more threads, the more significant the impact on the efficiency.
b. News
WM_XXX parameters WPARAM and LPARAM. In the process of transmission, the value of the variable is copied into the parameter, so there is no problem of lock and unlock. Send message and cross thread PostThreadMessage in the same dialog box. However, the disadvantage of message is that the amount of data that parameters can carry is limited, and it depends on message queue.
c. Network
The stand-alone program also has a network (loopback network, 127.0.0.1, local network). Advantages: high speed; Multiple requests can be served; There is no need to pay attention to the queue. The message queue is processed by software, while the network queue is processed by hardware (network card). The efficiency is completely different, and there is almost no need to pay attention to the latter; Completing port mapping (epoll / IOCP) can continue to improve efficiency.
3. Implement a simple thread safe queue using IOCP
The general process is as follows: ① create a handle to complete the port object (which is taken over by the operating system) and communicate with the kernel through the handle; ② Create a thread that is responsible for processing the queue.
//Operation enumeration value enum { IocpListEmpty=0, IcopListPush=1, IocpListPop=2, } struct IOCP_PARAM { int Opr;//operation std::string strData;//data _beginthread_proc_type cbFun;//Callback IOCP_PARAM() { Opr=-1; } IOCP_PARAM(int inOpr,const char* inData,_beginthread_proc_type inCbFun=NULL) { Opr=inOpr; strData=inData; cbFun=inCbFun; } } void threadMain(HANDLE hIOCP) { std::list<std::string> lstString; DWORD dwTransferred=0; ULONG_PTR CompletionKey=0; OVERLAPPED* pOverlapped=NULL; //Gets the status of the completion port while(GetQueuedComletionStatus(hIOCP,&dwTransferred, &CompletionKey,&pOverlapped,INFINITE)) { if(dwTransferred==0 || CompletionKey==NULL) { printf("Thread is prepare to exit!\r\n"); break; } IOCP_PARAM* parm=(IOCP_PARAM*)CompletionKey; //push operation if(parm->Opr==IocpListPush) lstString.push_back(parm->strData); //pop operation if(parm->Opr==IocpListPop) { std::string* pStr=NULL; if(lstString.size()>0) { pStr=new std::string(lstString.front()); lstString.pop_front(); } if(parm->cbFun) parm->cbFun(pStr); } //empty operation if(parm->Opr==IocpListEmpty) lstString.clear(); delete parm; } } void threadQueueEntry(HANDLE hIOCP) { threadMain(hIOCP); //The code ends so far that the local object cannot call the destructor, resulting in memory leakage _endthread(); } void Fun(void* arg) { std::string* pStr=(std::string*)arg; if(pStr!=NULL) { printf("Pop from list,value is : %s",pStr->c_str()); delete pStr; } else printf("List is empty!"); } int main() { HANDLE hIOCP=INVALID_HANDLE_VALUE; //Create a completion port object hIOCP=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,1);//Because it is a queue, the thread transmits data 1 //Pass completion port object to thread HANDLE hThread=(HANDLE)_beginthread(threadQueueEntry,0,hIOCP); ULONGLONG tick=GetTickCount64(); while(_kbhit()==0)//The completion port separates the request from the implementation { //Delivery status if(GetTickCount64()-tick>1300) { PostQueuedComletionStatus(hIOCP,sizeof(IOCP_PARAM), (ULONG_PTR)new IOCP_PARAM(IocpListPop,"Hello World",Fun),NULL); } if(GetTickCount64()-tick>2000) { PostQueuedComletionStatus(hIOCP,sizeof(IOCP_PARAM), (ULONG_PTR)new IOCP_PARAM(IocpListPush,"Hello World"),NULL); tick=GetTickCount64(); } Sleep(1); } if(hIOCP!=NULL) { PostQueuedComletionStatus(hIOCP,0,NULL,NULL); WaitForSingleObject(hThread,INFINITE); } CloseHandle(hIOCP); }
This implementation is mainly to facilitate the understanding of newly contacted IOCP. The current writing method also has a logic vulnerability. When WaitForSingleObject is executed, hIOCP is still valid. At this time, if a thread delivers data, it will lead to memory leakage.
Reason for calling threadMain separately in threadQueueEntry: when calling_ When endthread, the execution of the code is here, and the subsequent contents will not be executed (or called). Therefore, when the code is written in threadQueueEntry, the exit will lead to memory leakage, because the local object cannot call the destructor. This part of the code is taken out separately as a function. When the function execution ends, the local object will call the destructor, and then the problem of memory leakage is solved.
4. Overlapped structure
General process:
To use the overlapping structure, you need to modify the initialization slightly,
bool Init() { //normal WSADATA data; if( WSAStartup(MAKEWORD(1,1),&data)!=0 ) return false; return true; //unnormal,use overlapped WSADATA data; if( WSAStartup(MAKEWORD(2,0),&data)!=0 ) return false; return true; }
class COverLapped { public: //You have to put it first OVERLAPPED m_overlapped; DWORD m_opr;//Operation value or command char m_buffer[4096]; COverLapped() { m_opr=0; memset(&m_overlapped,0,sizeof(m_overlapped)); memset(&m_buffer,0,sizeof(m_buffer)); } } enum { opr_Accept=1, opr_Send=2, opr_Recv=3, //other opr value... } void testOverLapped() { //Normal usage SOCKET sock_normal=socket(AF_INET,SOCK_STREAM,0); //Use of overlapping structures SOCKET sock_unnoemal=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED); if(sock_unnormal==INVALID_SOCKET) return; HANDLE hIOCP=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,sock_unnormal,4); //Binding IOCP to socket CreateIoCompletionPort((HANDLE)sock_unnormal,hIOCP,0,0); SOCKET client=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED); sockaddr_in addr; addr.sin_family=PF_INET; addr.sin_addr.s_addr=inet_addr("0.0.0.0"); addr.sin_port=htons(9527); bind(sock_unnormal,(sockaddr*)addr,sizeof(addr)); listen(sock_unnormal,5); COverLapped overlapped; overlapped.m_opr=opr_Accept; memset(&overlapped,0,sizeof(OVERLAPPED)); DWORD received=0; if(!AcceptEx(sock_unnormal,client,overlapped.m_buffer,0,sizeof(sockaddr_in)+16,sizeof(sockaddr_in)+16,&received,&overlapped.m_overlapped)) return; //todo: start a thread //Represents a thread while(true) { DWORD dwTransferred=0; DWORD CompletionKey=0; LPOVERLAPPED pOverlapped=NULL; if(GetQueuedCompletionStatus(hIOCP,&dwTransferred,&CompletionKey,&pOverlapped,INFINITY)) { COverLapped* ptrOL = CONTAINING_RECORD(pOverlapped,COverLapped,m_overlapped); switch(ptrOL->m_opr) { case opr_Accept: //todo: handle accept case opr_Send: //todo: process send case opr_Recv: //todo: processing recv } } } }
5. Design and implementation of thread class and thread pool class
Why design thread pools? The main task of the thread mentioned in the code is to continuously Get queuedcompletionstatus, and IOCP can reach tens of thousands of concurrency. If each request is processed in this thread, the significance of using IOCP has been lost. Therefore, it should be correct that when the thread gets to a state, it immediately assigns the work to a new thread to continue to execute the next Get. As for the thread assigned to the work, how to process the business and how long it takes to process the business, this is not what the current main thread needs to pay attention to.
class ThreadFuncBase{}; typedef int (ThreadFuncBase::* FuncType)(); class ThreadWorker { public: ThreadWorker():thiz(NULL),func(NULL) {} ThreadWorker(ThreadFuncBase* obj,FuncType func); ThreadWorker(const ThreadWorker& worker); ThreadWorker& operator=(const ThreadWorker& worker); int operator()() { return (isValid()?(thiz->*func)():-1); } bool isValid() { return (thiz!=NULL)&&(func!=NULL); } private: ThreadFuncBase* thiz;//Pointer to the ThreadFuncBase member object FuncType func;//Pointer to ThreadFuncBase member function } class MyThread { public: MyThread() { m_hThread=NULL; } ~MyThread() { Stop(); } //Start thread true: start succeeded false: start failed bool Start(); //Valid true: valid false: thread exception or terminated bool isValid() { //If the handle is empty, an error will be returned; Wait is returned if the handle has ended_ ABANDONED return WaitForSingleObject(m_hThread,0)==WAIT_TIMEOUT; } //Close thread bool Stop(); //Update work void updateWorker(const ::ThreadWorker& worker=::ThreadWorker()) { m_worker.store(worker); } //Idle true: idle can allocate work false: non idle has been allocated work bool isIdle() { return !m_worker.load().isValid(); } private: void threadWorker() { while(m_bStatus) { ::ThreadWorker worker=m_worker.load(); if(worker.isValid()) { int iRet=worker(); if(iRet!=0) //Print warning log if(iRet<0)//Set an invalid when a problem occurs m_worker.store(::ThreadWorker()); } else Sleep(1); } } static void threadEntry(void* arg) { MyThread* thiz=(MyThread*)arg; if(thiz) thiz->threadWorker(); _endthread(); } private: HANDLE m_hThread; bool m_bStatus;//false: the thread is about to shut down, true: the thread is running std::atomic<::ThreadWorker> m_worker; } void MyThreadPool { public: MyThreadPool(); MyThreadPool(size_t size) { m_threads.resize(size); } ~MyThreadPool(); bool Invoke(); void Stop(); //Assign work return value to assign work to the number of threads, and -1 if all threads are busy int dispatchWorker(const ThreadWorker& worker) { m_lock.lock(); int index=-1; for(size_t i=0;i<m_threads.size(),++i) { if(m_threads[i].isIdle()) { m_threads[i].updateWorker(worker); index=i; break; } } m_lock.unlock(); return index; } private: std::vector<MyThread> m_threads; std::mutex m_lock; }
The meaning of encapsulating a ThreadWoker class separately is that in MyThread, the threadWoreker thread function is separated from the content to be executed. The user inherits the ThreadFuncBase class, and then creates a ThreadWoker object to get the content to be executed through the updateWorker of MyThread class. The advantage of this is that when a thread finishes executing a task, there is no need to close the thread, because it needs to be re created when re allocating work, and it takes time to create a thread. After its execution, it will be empty and still keep it in while. When updateWorker gets the work, it can be executed quickly.
6. UDP penetration
a. Principle
In fact, the principle is not very complex. UDP penetration makes use of the fixed IP of the public network in the public server and the characteristics of UDP protocol that can actively send requests without response.
Under such conditions, you can use UDP to establish a connection with the public server, and establish a connection with another client through the return IP and port of the public server.
In this process, the public server must exist and the connection with the public server cannot be interrupted.
b. Network environment
When ordinary PC machines connect to the Internet, they need to first connect to the domain name server of the Internet through the operator (dial-up), and the domain name server forwards the request to the real machine server, so as to realize the internal and external network connection.
In the intranet (local area network), no matter how many networking devices there are in the LAN at the same time, they are the same temporary IP for the external network; When the devices in the same LAN are connected to the external network, the IP is the same and the ports are different; All networking devices in the same LAN share the same temporary IP in the LAN.
c. Question
Based on the network environment, the following problems will be faced:
① Because the LAN IP is temporary, it will change, so it becomes very difficult for other machines to actively connect to the PC through IP;
② Cat and router do not allow external IP to actively connect to internal network by default;
③ Even if the cat or router can send IP to the public network so that the target machine can connect to you, other machines can also connect to you, which will lead to security problems such as data leakage.
d. Protocol analysis
Based on the problems caused by the network environment, it can be determined that UDP protocol needs to be used after analyzing the communication protocol, because it almost perfectly solves the above problems.
e. Solution
① Problem with temporary IP: a public server in the public network is required to initiate a UDP request to the server through the PC, and establish a connection after obtaining the response packet from the server. The IP and port of the PC end will not change after the connection to the end.
② Firewall of cat and router: when the PC actively initiates a connection request, the firewall will actually leave a channel to receive the server's response packet to establish a connection. However, it should be noted that the PC must actively initiate the request and receive the response packet before confirming that the channel has been opened.
③ Security problem: because of the firewall mechanism, the external network cannot actively initiate requests to the PC, and the firewall will intercept these requests.
④ Connection between master and slave: since both master and slave are connected to the public server, the public server has the IP and port of both master and slave. As long as the server sends the IP and port to each other respectively, the two sides can establish a connection directly. However, even after the connection is established, both parties cannot disconnect from the server, because ① has explained that once the connection with the server is disconnected, the IP and port in the LAN will change.
void udp_server() { SOCKET sock=socket(PF_INET,SOCK_DGRAM,0); if(sock==INVALID_SOCKET) return; sockaddr_in server,client; memset(server,0,sizeof(server)); memset(client,0,sizeof(client)); server.sin_family=AF_INET; server.sin_port=htons(20000); server.sin_addr.s_addr=inet_addr("127.0.0.1"); } void udp_client(bool ishost) { Sleep(2000); sockaddr_in server, client; int len = sizeof(client); server.sin_family = AF_INET; server.sin_port = htons(20000); //The udp port should preferably be between 20000 and 40000 server.sin_addr.s_addr = inet_addr("127.0.0.1"); SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) return; if(ishost) //Primary client code { EBuffer msg = "Hello World!\n"; int ret = sendto(sock, msg.c_str(), msg.size(), 0, (sockaddr*)&server, sizeof(server)); if (ret > 0) { msg.resize(1024); memset((char*)msg.c_str(), 0, msg.size()); //Receive data from the server ret = recvfrom(sock, (char*)msg.c_str(), msg.size(), 0, (sockaddr*)&client, &len); if (ret > 0) //Print the acquired data //Receive data from another client ret = recvfrom(sock, (char*)msg.c_str(), msg.size(), 0, (sockaddr*)&client, &len); if (ret > 0) //Print the acquired data } } else //From client code { std::string msg = "Hello World!\n"; int ret = sendto(sock, msg.c_str(), msg.size(), 0, (sockaddr*)&server, sizeof(server)); if (ret > 0) { msg.resize(1024); memset((char*)msg.c_str(), 0, msg.size()); ret = recvfrom(sock, (char*)msg.c_str(), msg.size(), 0, (sockaddr*)&client, &len); if (ret > 0) { sockaddr_in addr; memcpy(&addr, msg.c_str(), sizeof(addr)); sockaddr_in* paddr = (sockaddr_in*)&addr; msg = "Hello,I am client!\n"; ret = sendto(sock, (char*)msg.c_str(), msg.size(), 0, (sockaddr*)paddr, sizeof(sockaddr_in)); } } } closesocket(sock); } int main(int argc,char* argv[]) { InitSock();//socket initialization if (argc == 1) { char wstrDir[MAX_PATH]; GetCurrentDirectoryA(MAX_PATH, wstrDir); //Gets the path of the current process STARTUPINFOA si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); memset(&pi, 0, sizeof(pi)); std::string strCmd = argv[0];//strCmd is the path of the program strCmd += " 1"; //Add a number 1 after the path to distinguish it, which is used to start a new process BOOL bRet = CreateProcessA(NULL, (LPSTR)strCmd.c_str(), NULL, NULL, FALSE, 0, NULL, wstrDir, &si, &pi);//Create a new process if (bRet) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); TRACE("process ID : %d\n", pi.dwProcessId); TRACE("thread ID : %d\n", pi.dwThreadId); strCmd += " 2"; //Then, on the basis of the above, add a number 2 after the path name to start a new process again bRet = CreateProcessA(NULL, (LPSTR)strCmd.c_str(), NULL, NULL, FALSE, 0, NULL, wstrDir, &si, &pi);//Create a new process if (bRet) //If created successfully { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); TRACE("process ID : %d\n", pi.dwProcessId); TRACE("thread ID : %d\n", pi.dwThreadId); udp_server();// Server code, start the server } } } else if (argc == 2)//Primary client code udp_client(); else udp_client(false);//From client code ClearSock();//Clean socket }
7. Administrator authority and startup
a. Administrator privileges:
vs can be selected during compilation: right click the item → attribute → linker → manifest file → UAC execution level → select Administrator, apply and confirm after selection, and then regenerate the project. The compiled exe file will have a shield icon in the lower right corner, that is, Administrator authority.
b. Power on
b-1: registry modification
HKEY_LOCAL_MACHINE → SOFTWARE → Microsoft → Windows → CurrentVersion → Run. The path records some boot programs.
Note that the path of the program must point to the system directory, generally system32, that is, copy the program to be started to system32. But it is better to use mklink to create a soft link under Windows.
Differences between soft links and shortcuts:
First, the file format is different, and the suffix of the shortcut is lnk;
Secondly, the value of type is also different. The shortcut starts with L and the soft link file starts with MZ. In fact, the soft link file is the original exe file, which is equivalent;
Third, the file size is different;
bool WriteRegisterTable(const CString& strPath) //Functions required to complete startup in registry mode { CString strSubKey = _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); TCHAR sPath[MAX_PATH] = _T(""); GetModuleFileName(NULL, sPath, MAX_PATH); BOOL ret = CopyFile(sPath, strPath, FALSE); if (ret == FALSE) { MessageBox(NULL, _T("File copy failed. Do you have insufficient permissions?\n"), _T("error"), MB_ICONERROR | MB_TOPMOST); return false; } HKEY hKey = NULL; ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, strSubKey, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &hKey); if (ret != ERROR_SUCCESS) { RegCloseKey(hKey); MessageBox(NULL, _T("Failed to set automatic startup after startup. Do you have insufficient permissions?\n Program startup failed!"), _T("error"), MB_ICONERROR | MB_TOPMOST); return false; } ret = RegSetValueEx(hKey, _T("RemoteCtrl"), 0, REG_SZ, (BYTE*)(LPCTSTR)strPath, strPath.GetLength() * sizeof(TCHAR)); if (ret != ERROR_SUCCESS) { RegCloseKey(hKey); MessageBox(NULL, _T("Failed to set automatic startup after startup. Do you have insufficient permissions?\n Program startup failed!"), _T("error"), MB_ICONERROR | MB_TOPMOST); return false; } RegCloseKey(hKey); return true; }
b-2: the second method of startup
Copying the program to the startup folder is simpler than the first method of modifying the registry, but this method needs to wait until the machine is fully started, so the time is slower than the way of modifying the registry.
BOOL WriteStartupDir(const CString& strPath) //Directly copy the file to the startup folder to complete the functions required for startup { TCHAR sPath[MAX_PATH] = _T(""); GetModuleFileName(NULL, sPath, MAX_PATH); return CopyFile(sPath, strPath, FALSE); }