C++ 获取进程信息
1. 概要
通常对于一个正在执行的进程而言,我们会关注进程的内存/CPU占用,网络连接,启动参数,映像路径,线程,堆栈等信息。
而通过类似任务管理器,命令行等方式可以轻松获取到这些信息。但是,这些信息究竟是从何而来呢?
- linux
由于linux平台未直接提供进程信息的访问接口,而是通过/proc文件系统向用户展示相关业务信息。虽然也可以通过neklink通信直接从内核获取我们想要的信息,编码复杂性相对较高,因此我们选择使用/proc文件系统的接口。 - Window
windows平台原生提供了大量操作系统相关的接口,但是每个接口的功能相对独立,因此需要找到对应的接口查询数据,最后将数据组装起来。
2. 进程列表
1)linux
/proc/[PID]/
:这是每个正在运行的进程都有一个对应的目录,其中[PID]
是进程的ID号。
DIR *proc = opendir("/proc");
if (proc) {struct dirent *entry;while ((entry = readdir(proc)) != NULL) {if (entry->d_type == DT_DIR) {std::string dir_name(entry->d_name);if (dir_name.find_first_not_of("0123456789") == std::string::npos) {int32_t pid = atoi(entry->d_name);}}}closedir(proc);
}
2)Window
windows平台获取进程列表的方式比较多,只需从中选择任意一种即可。
- CreateToolhelp32Snapshot与Process32First等结合使用;
- EnumProcesses枚举所有进程ID;
- NtQuerySystemInformation查询SystemProcessInformation信息
- ……
在这里,采用第一种方式遍历进程。
当然,由于CreateToolhelp32Snapshot底层实际上同样是通过NtQuerySystemInformation实现的。因此,若追求高效率的话,可以使用第三种方案。
HANDLE hSnapshot =CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
if (hSnapshot) {PROCESSENTRY32 pe32;pe32.dwSize = sizeof(pe32);if (Process32First(hSnapshot, &pe32)) {do {std::string path;W2A(pe32.szExeFile, &path);Proc detail_info;detail_info.pid = pe32.th32ProcessID;detail_info.name = path;// GetDetailInfoproc_map->emplace(pe32.th32ProcessID, detail_info);} while (Process32Next(hSnapshot, &pe32));}CloseHandle(hSnapshot);
}
另附第三种方案实现:
typedef NTSTATUS(NTAPI *P_NT_QUERY_SYSTEM_INFORMATION)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation,ULONG SystemInformationLength, PULONG ReturnLength);HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
P_NT_QUERY_SYSTEM_INFORMATION pfnNtQueryInformationProcess = NULL;
if (hNtdll) {pfnNtQueryInformationProcess =reinterpret_cast<P_NT_QUERY_SYSTEM_INFORMATION>(GetProcAddress(hNtdll, "NtQuerySystemInformation"));ULONG length = 0;PUCHAR pInfo = NULL;do {DWORD result = pfnNtQueryInformationProcess(SystemProcessInformation, pInfo,length, &length);
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004if (result != 0) {if (result == STATUS_INFO_LENGTH_MISMATCH) {pInfo = new UCHAR[length];continue;}break;}PSYSTEM_PROCESS_INFORMATION _ProcessInfo;ULONG Offset = 0;do {_ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)&pInfo[Offset];int64_t pid = HandleToLong(_ProcessInfo->UniqueProcessId);// ToDoOffset += _ProcessInfo->NextEntryOffset;} while (_ProcessInfo->NextEntryOffset);break;} while (true);if (pInfo) {delete pInfo;}
}
SYSTEM_THREAD_INFORMATION具体结构请参考:SYSTEM_PROCESS_INFORMATION
3. 参数列表
1)linux
/proc/[PID]/cmdline
:这个文件包含了启动该进程的命令行参数。参数之间使用null字符(‘\0’)分隔。
char cmd_path[PROCPATHLEN];
sprintf(cmd_path, "/proc/%d/cmdline", pid);FILE *fp = fopen(cmd_path, "r");
if (fp) {char line[4096];if (fgets(line, sizeof(line), fp)) {imagepath->assign(line);startparamater->assign(line);int32_t offset = startparamater->size() + 1;while (line[offset]) {startparamater->append(" ");startparamater->append(line + offset);offset += strlen(line + offset) + 1;}}fclose(fp);
}
2)Window
windows平台没有直接提供获取进程启动参数的接口,但是可以通过解析进程的PEB(进程环境块)地址,获取信息。
typedef NTSTATUS(NTAPI *NT_QUERY_INFORMATION_PROCESS)(HANDLE, PROCESSINFOCLASS,PVOID, ULONG, PULONG);HMODULE hNtdll = GetModuleHandle(L"Ntdll");
if (hNtdll) {NT_QUERY_INFORMATION_PROCESS pNtQueryInformationProcess =(NT_QUERY_INFORMATION_PROCESS)GetProcAddress(hNtdll,"NtQueryInformationProcess");if (pNtQueryInformationProcess) {// 只读打开进程句柄HANDLE hProcess =OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);if (hProcess) {PWSTR buffer = NULL;do {PROCESS_BASIC_INFORMATION pbi = {0};// 读取进程基本信息RTL_USER_PROCESS_PARAMETERSif (pNtQueryInformationProcess(hProcess, ProcessBasicInformation, (PVOID)&pbi,sizeof(PROCESS_BASIC_INFORMATION), NULL)) {break;}if (NULL == pbi.PebBaseAddress) {break;}PEB peb;SIZE_T dwDummy;// 从PEB地址中读取PEB结构if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb),&dwDummy)) {break;}RTL_USER_PROCESS_PARAMETERS para;// 从参数结构地址读取RTL_USER_PROCESS_PARAMETERS结构if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶,sizeof(para), &dwDummy)) {break;}// 从映像文件地址读取映像文件路径LPVOID lpAddress = para.ImagePathName.Buffer;DWORD dwSize = para.ImagePathName.Length;buffer = new WCHAR[dwSize / sizeof(WCHAR) + 1];buffer[dwSize / sizeof(WCHAR)] = 0x00;if (!ReadProcessMemory(hProcess, lpAddress, buffer, dwSize, &dwDummy)) {break;}W2A(buffer, imagepath);delete[] buffer;buffer = NULL;// 从参数列表地址读取参数列表lpAddress = para.CommandLine.Buffer;dwSize = para.CommandLine.Length;buffer = new WCHAR[dwSize / sizeof(WCHAR) + 1];buffer[dwSize / sizeof(WCHAR)] = 0x00;if (!ReadProcessMemory(hProcess, lpAddress, buffer, dwSize, &dwDummy))break;W2A(buffer, startparamater);delete[] buffer;buffer = NULL;result = true;} while (false);if (buffer) {delete[] buffer;}CloseHandle(hProcess);}}
}
4. 动态库
1)linux
/proc/[PID]/maps
:这个文件包含了进程的内存映射信息,显示了进程所使用的内存地址范围及其对应的权限。
char map_path[PROCPATHLEN];
sprintf(map_path, "/proc/%d/maps", pid);
FILE *fp = fopen(map_path, "r");
if (fp) {char line[1024];char filename[1024];std::unordered_set<std::string> module_sets;while (fgets(line, sizeof(line), fp)) {sscanf(line, "%*s %*s %*s %*s %*ld %s", filename);if (filename[0] == '/') {module_sets.emplace(filename);}}fclose(fp);for (std::unordered_set<std::string>::const_iterator itr =module_sets.begin();itr != module_sets.end(); itr++) {ptable->push_back(*itr);}
}
2)Window
windows平台可以直接使用Module32First族函数遍历所有进程加载的模块。
HANDLE hSnapshot =CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
if (hSnapshot) {MODULEENTRY32 md32;md32.dwSize = sizeof(md32);if (Module32First(hSnapshot, &md32)) {do {std::string filepath;W2A(md32.szExePath, &filepath);ptable->push_back(filepath);} while (Module32Next(hSnapshot, &md32));}CloseHandle(hSnapshot);
}
5. CPU利用率
1)linux
进程的Cpu利用率无法直接从系统获取,而需要通过时间片算法来计算。在这里,我们参考top命令实现简单的进程Cpu利用率计算。
算法:
- 获取内核频率Hertz;
hertz_ = sysconf(_SC_CLK_TCK)
- 读取系统运行时间,计算与上次的运行时间的差值et;
FILE *fp = fopen("/proc/uptime", "r");if (fp) {char line[1024];fgets(line, sizeof(line), fp);fclose(fp);sscanf(line, "%lf", uptime);}
- 计算刷新频率Frame_etscale
float et = uptime_cur - uptime_save_;if (et < 0.01) {et = 0.005;}uptime_save_ = uptime_cur;frame_etscale_ = 100.0f / ((float)hertz_ * (float)et * 1);
- 遍历读取进程的用户态u_time和内核态时间s_time;
char stat_path[PROCPATHLEN];sprintf(stat_path, "/proc/%d/stat", pid);FILE *fp = fopen(stat_path, "r");if (fp) {char line[1024];if (fgets(line, sizeof(line), fp)) {int64_t u_time, s_time, wait_u_time, wait_s_time, start_time;sscanf(line,"%*d %*s %*c %*d %*d %*d %*d %*d ""%*lu %*lu %*lu %*lu %*lu""%llu %llu %llu %llu""%*ld %*ld ""%*d ""%*ld ""%llu ", /* start_time */&u_time, &s_time, &wait_u_time, &wait_s_time, &start_time);cpu_time->s_time = s_time;cpu_time->u_time = u_time;cpu_time->start_time = start_time;}fclose(fp);}
- 时间切片
- 重复步骤2、3、4,计算进程的CPU利用率cpu_usage ;
tics = process.new_time - process.old_time;cpu_usage = tics * etscale_;
2)Window
windows平台的算法与Linux类似。
算法:
- 获取当前CPU时间
FILETIME idleTime, kernelTime, userTime;if (!GetSystemTimes(&idleTime, &kernelTime, &userTime)) {return;}ULARGE_INTEGER cpu_time;cpu_time.LowPart = kernelTime.dwLowDateTime + userTime.dwLowDateTime;cpu_time.HighPart = kernelTime.dwHighDateTime + userTime.dwHighDateTime;time = cpu_time.QuadPart;
- 遍历所有进程的时间
HANDLE hProcess =OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);if (hProcess) {FILETIME start_time, exit_time, s_time, u_time;if (!GetProcessTimes(hProcess, &start_time, &exit_time, &s_time, &u_time)) {return false;}FileTimeToInt64(s_time, cpu_time->s_time);FileTimeToInt64(u_time, cpu_time->u_time);FileTimeToInt64(start_time, cpu_time->start_time);CloseHandle(hProcess);}
- 时间切片
- 重复步骤1、2,计算进程的CPU利用率cpu_usage ;
*cpu_usage = (process.new_time - process.old_time) * 1000 /(system_time_.new_time - system_time_.old_time);
6. 内存占用
1)linux
/proc/[PID]/status
:这个文件包含了有关进程状态的各种信息,如进程ID、父进程ID、运行状态、内存使用情况等。
// 获取物理内存总量,单位Byte
const char *meminfo_path = "/proc/meminfo";
FILE *fp = fopen(meminfo_path, "r");
if (fp) {char line[4096];while (fgets(line, sizeof(line), fp)) {if (strncmp(line, "MemTotal:", 9) == 0) {sscanf(line, "%*s:%d", sys_mem_size_);break;}}fclose(fp);
}//获取进程物理内存占用,单位KB
char status_path[PROCPATHLEN];
sprintf(status_path, "/proc/%d/status", pid);
FILE *fp = fopen(status_path, "r");
if (fp) {char line[4096];while (fgets(line, sizeof(line), fp)) {if (strncmp(line, "VmRSS:", 6) == 0) {sscanf(line, "%*s:%d", mem_used_size);break;}}fclose(fp);
}
2)Window
MEMORYSTATUSEX mem_info;
mem_info.dwLength = sizeof(mem_info);
// 获取物理内存总量,单位Byte
if (GlobalMemoryStatusEx(&mem_info)) {sys_mem_size_ = mem_info.ullTotalPhys;
}SYSTEM_INFO si;
GetSystemInfo(&si);
HANDLE hProcess =OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess) {PSAPI_WORKING_SET_INFORMATION workset_info = NULL;// 查询进程内存工具集if (!QueryWorkingSet(hProcess, &workset_info, sizeof(workset_info))) {if (GetLastError() == ERROR_BAD_LENGTH) {// 计算所需内存,由于工具集长度动态变化,因此在此多申请64个工具集空间。size_t length =sizeof(PSAPI_WORKING_SET_INFORMATION) +sizeof(PSAPI_WORKING_SET_BLOCK) * (workset_info.NumberOfEntries + 64);PPSAPI_WORKING_SET_INFORMATION p_workset_info =(PPSAPI_WORKING_SET_INFORMATION) new char[length];if (QueryWorkingSet(hProcess, p_workset_info, length)) {*mem_used_size = 0;for (int i = 0; i < p_workset_info->NumberOfEntries; i++) {// 判断工具集是否共享if (p_workset_info->WorkingSetInfo[i].Flags &&p_workset_info->WorkingSetInfo[i].Shared == 0) {*mem_used_size += si.dwPageSize;}}}delete[] (char *)p_workset_info;} else {return false;}}// Byte单位转换KB单位*mem_used_size = (*mem_used_size / 1024);return true;
}
7. 用户名
1)linux
/proc/[PID]/status
char status_path[PROCPATHLEN];
sprintf(status_path, "/proc/%d/status", pid);FILE *fp = fopen(status_path, "r");
if (fp) {char line[4096];while (fgets(line, sizeof(line), fp)) {if (strncmp(line, "Uid:", 4) == 0) {int32_t uid = 0;sscanf(line, "%*s:%d", &uid);struct passwd *_passwd;_passwd = getpwuid(uid);if (_passwd) {username = _passwd->pw_name;}}}fclose(fp);
}
2)Window
HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (handle != 0x00) {HANDLE token;// 打开进程令牌if (OpenProcessToken(handle, TOKEN_QUERY, &token)) {DWORD token_size = 0;PTOKEN_USER p_token_user = NULL;SID_NAME_USE sn;// 从进程令牌中获取用户令牌if (!GetTokenInformation(token, TokenUser, p_token_user, token_size,&token_size)) {if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {p_token_user = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), 0, token_size);if (p_token_user) {if (GetTokenInformation(token, TokenUser, p_token_user, token_size,&token_size)) {TCHAR szUserName[MAX_PATH] = {0};DWORD dwUserSize = MAX_PATH;TCHAR szDomain[MAX_PATH] = {0};DWORD dwDomainSize = MAX_PATH;// 根据用户令牌查询用户名if (LookupAccountSid(NULL, ((PTOKEN_USER)p_token_user)->User.Sid,szUserName, &dwUserSize, szDomain,&dwDomainSize, &sn)) {W2A(szUserName, username);ret = true;}}HeapFree(GetProcessHeap(), 0, p_token_user);}}}CloseHandle(token);}CloseHandle(handle);
}
8. 网络连接
1)linux
/proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6
: 提供了当前 TCP /TCP6/UDP/UDP6套接字的详细信息。
/proc/[PID]/fd/
:这是一个文件夹,包含了进程当前打开的文件描述符列表。
- 首先,解析套接字信息,包括套接字的inode号;
const char *tcp_file[] = {"/proc/net/tcp", "/proc/net/tcp6"};
for (int i = 0; i < 2; i++) {FILE *fp = fopen(tcp_file[i], "r");if (!fp) {return;}char line[1024];while (fgets(line, sizeof(line), fp)) {unsigned long rxq, txq, time_len, retr, inode;int num, local_port, remote_port, d, state, uid, timer_run, timeout;char rem_addr[128], local_addr[128];// 解析TCP信息num = sscanf(line,"%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX ""%X:%lX %lX %d %d %lu %*s\n",&d, local_addr, &local_port, rem_addr, &remote_port, &state,&txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout,&inode);if (num < 11) {continue;}// ipv6地址if (strlen(local_addr) > 8) {char addr6[INET6_ADDRSTRLEN];struct in6_addr in6;sscanf(local_addr, "%08X%08X%08X%08X", &in6.s6_addr32[0],&in6.s6_addr32[1], &in6.s6_addr32[2], &in6.s6_addr32[3]);// 端口序转换inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));sscanf(rem_addr, "%08X%08X%08X%08X", &in6.s6_addr32[0], &in6.s6_addr32[1],&in6.s6_addr32[2], &in6.s6_addr32[3]);inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));} else {// ipv4地址char addr[INET_ADDRSTRLEN];struct in_addr in;sscanf(local_addr, "%X", &in.s_addr);inet_ntop(AF_INET, &in, addr, sizeof(addr));sscanf(rem_addr, "%X", &in.s_addr);inet_ntop(AF_INET, &in, addr, sizeof(addr));}}fclose(fp);
}const char *udp_file[] = {"/proc/net/udp", "/proc/net/udp6"};
for (int i = 0; i < 2; i++) {FILE *fp = fopen(udp_file[i], "r");if (!fp) {return false;}char line[1024];while (fgets(line, sizeof(line), fp)) {unsigned long rxq, txq, time_len, retr, inode;int num, local_port, remote_port, d, state, uid, timer_run, timeout;char rem_addr[128], local_addr[128];// 解析UDP信息num = sscanf(line,"%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX ""%X:%lX %lX %d %d %lu %*s\n",&d, local_addr, &local_port, rem_addr, &remote_port, &state,&txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout,&inode);if (num < 10) {continue;}if (strlen(local_addr) > 8) {// ipv6地址char addr6[INET6_ADDRSTRLEN];struct in6_addr in6;sscanf(local_addr, "%08X%08X%08X%08X", &in6.s6_addr32[0],&in6.s6_addr32[1], &in6.s6_addr32[2], &in6.s6_addr32[3]);inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));} else {// ipv4地址char addr[INET_ADDRSTRLEN];struct in_addr in;sscanf(local_addr, "%X", &in.s_addr);inet_ntop(AF_INET, &in, addr, sizeof(addr));}}fclose(fp);
}
- 遍历所有进程的fd文件夹,匹配inode号。
2)WIndows
直接使用GetExtendedTcpTable和GetExtendedUdpTable函数查询TCP和UDP信息。
DWORD bufferSize;
MIB_TCPTABLE_OWNER_PID *net_table = NULL;WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {return;
}do {SOCKADDR_IN v4 = {AF_INET};SOCKADDR_IN6 v6 = {AF_INET6};int32_t ip[] = {AF_INET6, AF_INET};char ipaddress[INET6_ADDRSTRLEN];DWORD ipaddress_length = 0;for (int i = 0; i < 2; i++) {bufferSize = 0;if (GetExtendedTcpTable(NULL, &bufferSize, FALSE, ip[i],TCP_TABLE_OWNER_PID_ALL,0) == ERROR_INSUFFICIENT_BUFFER) {BYTE *net_table = new BYTE[bufferSize];if (net_table != NULL) {if (GetExtendedTcpTable(net_table, &bufferSize, FALSE, ip[i],TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {if (i == 0) {MIB_TCP6TABLE_OWNER_PID *net_table_v6 =(MIB_TCP6TABLE_OWNER_PID *)net_table;for (DWORD i = 0; i < net_table_v6->dwNumEntries; i++) {MIB_TCP6ROW_OWNER_PID row = net_table_v6->table[i];// local addr infoipaddress_length = INET6_ADDRSTRLEN;memcpy(v6.sin6_addr.s6_addr, row.ucLocalAddr,sizeof(row.ucLocalAddr));if (!WSAAddressToStringA((LPSOCKADDR)&v6, sizeof(v6), NULL,ipaddress, &ipaddress_length)) {local_host = ipaddress;}local_port = ntohs(row.dwLocalPort);// remote addr infoipaddress_length = INET6_ADDRSTRLEN;memcpy(v6.sin6_addr.s6_addr, row.ucRemoteAddr,sizeof(row.ucRemoteAddr));if (!WSAAddressToStringA((LPSOCKADDR)&v6, sizeof(v6), NULL,ipaddress, &ipaddress_length)) {remote_host = ipaddress;}remote_port = ntohs(row.dwRemotePort);}} else {MIB_TCPTABLE_OWNER_PID *net_table_v4 =(MIB_TCPTABLE_OWNER_PID *)net_table;for (DWORD i = 0; i < net_table_v4->dwNumEntries; i++) {MIB_TCPROW_OWNER_PID row = net_table_v4->table[i];// local addr infoipaddress_length = INET_ADDRSTRLEN;v4.sin_addr.s_addr = row.dwLocalAddr;if (!WSAAddressToStringA((LPSOCKADDR)&v4, sizeof(v4), NULL,ipaddress, &ipaddress_length)) {local_host = ipaddress;}local_port = ntohs(row.dwLocalPort);// remote addr infoipaddress_length = INET_ADDRSTRLEN;v4.sin_addr.s_addr = row.dwRemoteAddr;if (!WSAAddressToStringA((LPSOCKADDR)&v4, sizeof(v4), NULL,ipaddress, &ipaddress_length)) {remote_host = ipaddress;}remote_port = ntohs(row.dwRemotePort);}}}delete[] net_table;}}}for (int i = 0; i < 2; i++) {bufferSize = 0;if (GetExtendedUdpTable(NULL, &bufferSize, FALSE, ip[i],UDP_TABLE_OWNER_PID,0) == ERROR_INSUFFICIENT_BUFFER) {BYTE *net_table = new BYTE[bufferSize];if (net_table != NULL) {if (GetExtendedUdpTable(net_table, &bufferSize, FALSE, ip[i],UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {if (i == 0) {MIB_UDP6TABLE_OWNER_PID *net_table_v6 =(MIB_UDP6TABLE_OWNER_PID *)net_table;for (DWORD i = 0; i < net_table_v6->dwNumEntries; i++) {MIB_UDP6ROW_OWNER_PID row = net_table_v6->table[i];// local addr netInfoipaddress_length = INET6_ADDRSTRLEN;memcpy(v6.sin6_addr.s6_addr, row.ucLocalAddr,sizeof(row.ucLocalAddr));if (!WSAAddressToStringA((LPSOCKADDR)&v6, sizeof(v6), NULL,ipaddress, &ipaddress_length)) {local_host = ipaddress;}local_port = ntohs(row.dwLocalPort);}} else {MIB_UDPTABLE_OWNER_PID *net_table_v4 =(MIB_UDPTABLE_OWNER_PID *)net_table;for (DWORD i = 0; i < net_table_v4->dwNumEntries; i++) {MIB_UDPROW_OWNER_PID row = net_table_v4->table[i];// local addr netInfoipaddress_length = INET_ADDRSTRLEN;v4.sin_addr.s_addr = row.dwLocalAddr;if (!WSAAddressToStringA((LPSOCKADDR)&v4, sizeof(v4), NULL,ipaddress, &ipaddress_length)) {local_host = ipaddress;}local_port = ntohs(row.dwLocalPort);}}}delete[] net_table;}}}
} while (false);
WSACleanup();
9. 待续
……
相关文章:

C++ 获取进程信息
1. 概要 通常对于一个正在执行的进程而言,我们会关注进程的内存/CPU占用,网络连接,启动参数,映像路径,线程,堆栈等信息。 而通过类似任务管理器,命令行等方式可以轻松获取到这些信息。但是&…...

【Redis从头学-13】Redis哨兵模式解析以及搭建指南
🧑💻作者名称:DaenCode 🎤作者简介:啥技术都喜欢捣鼓捣鼓,喜欢分享技术、经验、生活。 😎人生感悟:尝尽人生百味,方知世间冷暖。 📖所属专栏:Re…...

【个人笔记js的原型理解】
在 JavaScript 中,最常见的新建一个对象的方式就是使用花括号的方式。然后使用’ . 的方式往里面添加属性和方法。可见以下代码: let animal {}; animal.name Leo; animal.energe 10;animal.eat function (amount) {console.log(${this.name} is ea…...

Liunx系统编程:信号量
一. 信号量概述 1.1 信号量的概念 在多线程场景下,我们经常会提到临界区和临界资源的概念,如果临界区资源同时有多个执行流进入,那么在多线程下就容易引发线程安全问题。 为了保证线程安全,互斥被引入,互斥可以保证…...

大集合按照指定长度进行分割成多个小集合,用于批量多次处理数据
📚目录 拆分案例拆分的核心代码 通常我们对集合的更新或者保存都需要用集合来承载通过插入的效率,但是这个会遇到一个问题就是你不知道那天那个集合的数量可能就超了,虽然我们连接数据库进行批量提交会在配置上配置allowMultiQueriestrue,但是…...

ELK日志收集系统集群实验(5.5.0版)
目录 前言 一、概述 二、组件介绍 1、elasticsearch 2、logstash 3、kibana 三、架构类型 四、ELK日志收集集群实验 1、实验拓扑 2、在node1和node2节点安装elasticsearch 3、启动elasticsearch服务 4、在node1安装elasticsearch-head插件 5、测试输入 6、node1服…...

基于java swing和mysql实现的电影票购票管理系统(源码+数据库+运行指导视频)
一、项目简介 本项目是一套基于java swing和mysql实现的电影票购票管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含:项目源码、项目文档、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都…...

数据结构--6.0最短路径
目录 一、迪杰斯特拉算法(Dijkstra) 二、弗洛伊德算法(Floyd) 在网图和非网图中,最短路径的含义是不同的。 ——网图是两顶点经过的边上的权值之和最少的路径。 …...

Docker进阶:mysql 主从复制、redis集群3主3从【扩缩容案例】
Docker进阶:mysql 主从复制、redis集群3主3从【扩缩容案例】 一、Docker常规软件安装1.1 docker 安装 tomcat(默认最新版)1.2 docker 指定安装 tomcat8.01.3 docker 安装 mysql 5.7(数据卷配置)1.4 演示--删除mysql容器…...

遗传算法决策变量降维的matlab实现
1.案例背景 1.1遗传算法概述 遗传算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。它最初由美国Michigan大学的J. Holland教授提出,1967年, Holland 教授的学生 Bagley在其博士论文中首次提出了“遗传…...

基于Open3D和PyTorch3D读取三维数据格式OBJ
本节将讨论另一种广泛使用的3D数据文件格式,即OBJ文件格式。OBJ文件格式最初由Wavefront Technologies Inc.开发。与PLY文件格式类似,OBJ格式也有ASCII版本和二进制版本。二进制版本是专有的且未记录文档。本章主要讨论ASCII版本。 与之前类似,将通过示例来学习文件格式。第…...

带纽扣电池产品出口澳洲安全标准,纽扣电池IEC 60086认证
澳大利亚政府公布了《消费品(纽扣/硬币电池)安全标准》和《消费品(纽扣/硬币电池)信息标准》。届时出口纽扣/硬币电池以及含有纽扣/硬币电池产品到澳大利亚的供应商,必须遵守这些标准中的要求。 一、 安全标准及信息标…...

spring高级源码50讲-37-42(springBoot)
Boot 37) Boot 骨架项目 如果是 linux 环境,用以下命令即可获取 spring boot 的骨架 pom.xml curl -G https://start.spring.io/pom.xml -d dependenciesweb,mysql,mybatis -o pom.xml也可以使用 Postman 等工具实现 若想获取更多用法,请参考 curl …...

腾讯云、阿里云、华为云便宜云服务器活动整理汇总
云服务器的选择是一个很重要的事情,避免产生不必要的麻烦,建议选择互联网大厂提供的云计算服务,腾讯云、阿里云、华为云就是一个很不错的选择,云服务器稳定性、安全性以及售后各方面都更受用户认可,下面小编给大家整理…...

L1-055 谁是赢家(Python实现) 测试点全过
前言: {\color{Blue}前言:} 前言: 本系列题使用的是,“PTA中的团体程序设计天梯赛——练习集”的题库,难度有L1、L2、L3三个等级,分别对应团体程序设计天梯赛的三个难度。更新取决于题目的难度,…...

开发一个npm包
1 注册一个npm账号 npm https://www.npmjs.com/ 2 初始化一个npm 项目 npm init -y3编写一段代码 function fn(){return 12 }exports.hellofn;4发布到全局node_module npm install . -g5测试代码 创建一个text文件 npm link heath_apisnode index.js6登录(我默认的 https…...

介绍几种使用工具
FileWatch,观测文件变化,源码地址:https://github.com/ThomasMonkman/filewatch nlohmann::json,json封装解析,源码地址:https://github.com/nlohmann/json optionparser,解析选项,源…...

Vue:关于声明式导航中的 跳转、高亮、以及两个类名的定制
声明式导航-导航链接 文章目录 声明式导航-导航链接router-link的两大特点(能跳转、能高亮)声明式导航-两个类名定制两个高亮类名 实现导航高亮,实现方式其实,css,JavaScript , Vue ,都可以实现。其实关于路由导航&…...

Sharding-JDBC分库分表-自动配置与分片规则加载原理-3
Sharding JDBC自动配置的原理 与所有starter一样,shardingsphere-jdbc-core-spring-boot-starter也是通过SPI自动配置的原理实现分库分表配置加载,spring.factories文件中的自动配置类shardingsphere-jdbc-core-spring-boot-starter功不可没,…...

E8267D 是德科技矢量信号发生器
描述 最先进的微波信号发生器 安捷伦E8267D PSG矢量信号发生器是业界首款集成式微波矢量信号发生器,I/Q调制最高可达44 GHz,典型输出功率为23 dBm,最高可达20 GHz,对于10 GHz信号,10 kHz偏移时的相位噪声为-120 dBc/…...

Git git fetch 和 git pull 区别
git pull和git fetch的作用都是用于从远程仓库获取最新代码,但它们之间有一些区别。 git pull会自动执行两个操作:git fetch和git merge。它从远程仓库获取最新代码,并将其合并到当前分支中。 示例:运行git pull origin master会从…...

软件UI工程师工作的岗位职责(合集)
软件UI工程师工作的岗位职责1 职责: 1.负责产品的UI视觉设计(手机软件界面 网站界面 图标设计产品广告及 企业文化的创意设计等); 2.负责公司各种客户端软件客户端的UE/UI界面及相关图标制作; 3.设定产品界面的整体视觉风格; 4.参与产品规划构思和创意过程&…...

Mac系统Anaconda环境配置Python的json库
本文介绍在Mac电脑的Anaconda环境中,配置Python语言中,用以编码、解码、处理JSON数据的json库的方法;在Windows电脑中配置json库的方法也是类似的,大家可以一并参考。 JSON(JavaScript Object Notation)是一…...

Python数据分析与数据挖掘:解析数据的力量
引言: 随着大数据时代的到来,数据分析和数据挖掘已经成为许多行业中不可或缺的一部分。在这个信息爆炸的时代,如何从大量的数据中提取有价值的信息,成为了企业和个人追求的目标。而Python作为一种强大的编程语言,提供…...

我的私人笔记(安装hive)
1.hive下载:Index of /dist/hive/hive-1.2.1 或者上传安装包至/opt/software:rz或winscp上传 2.解压 cd /opt/software tar -xzvf apache-hive-1.2.1-bin.tar.gz -C /opt/servers/ 3.重命名 mv apache-hive-1.2.1-bin hive 4.配置环境变量 vi /etc/…...

【kubernetes】k8s部署APISIX及在KubeSphere使用APISIX
Apache APISIX https://apisix.apache.org/ 功能比nginx-ingress更强 本文采用2.5.0版本 https://apisix.apache.org/zh/docs/apisix/2.15/getting-started/ 概述内容来源于官方,学习于马士兵云原生课程 概述 Apache APISIX 是什么? Apache APISIX 是 …...

串口接收数据-控制LED灯
目标 通过串口接收数据,对数据分析,控制8个LED灯按照设定时间闪烁。 8个LED灯可以任意设计,是否闪烁。闪烁时间按ms计算,通过串口发送,可设置1~4,294,967,296ms,也就是4字节数据协议自拟,有数…...

python面试题合集(一)
python技术面试题 1、Python中的幂运算 在python中幂运算是由两个 **星号运算的,实例如下: >>> a 2 ** 2 >>> a 4我们可以看到2的平方输出结果为4。 那么 ^指的是什么呢?我们用代码进行演示: >>>…...

论文浅尝 | 利用对抗攻击策略缓解预训练语言模型中的命名实体情感偏差问题...
笔记整理:田家琛,天津大学博士,研究方向为文本分类 链接:https://ojs.aaai.org/index.php/AAAI/article/view/26599 动机 近年来,随着预训练语言模型(PLMs)在情感分类领域的广泛应用,…...

springboot web开发springmvc自动配置原理
前言 我们也知道springboot启用springmvc基本不用做什么配置可以很方便就使用了但是不了解原理,开发过程中遇到点问题估计就比较头疼,不管了解的深不深入,先巴拉一番再说… 下面我们先看看官网…我的版本是2.3.2版本,发现官网改动也比较大…不同版本自己巴拉下吧,结构虽然变化…...