当前位置: 首页 > news >正文

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, &para,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利用率计算。

算法:

  1. 获取内核频率Hertz;
   hertz_ = sysconf(_SC_CLK_TCK)
  1. 读取系统运行时间,计算与上次的运行时间的差值et;
   FILE *fp = fopen("/proc/uptime", "r");if (fp) {char line[1024];fgets(line, sizeof(line), fp);fclose(fp);sscanf(line, "%lf", uptime);}
  1. 计算刷新频率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);
  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);}
  1. 时间切片
  2. 重复步骤2、3、4,计算进程的CPU利用率cpu_usage ;
   tics = process.new_time - process.old_time;cpu_usage = tics * etscale_;

2)Window

windows平台的算法与Linux类似。
算法:

  1. 获取当前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;
  1. 遍历所有进程的时间
  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. 重复步骤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. 概要 通常对于一个正在执行的进程而言&#xff0c;我们会关注进程的内存/CPU占用&#xff0c;网络连接&#xff0c;启动参数&#xff0c;映像路径&#xff0c;线程&#xff0c;堆栈等信息。 而通过类似任务管理器&#xff0c;命令行等方式可以轻松获取到这些信息。但是&…...

【Redis从头学-13】Redis哨兵模式解析以及搭建指南

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Re…...

【个人笔记js的原型理解】

在 JavaScript 中&#xff0c;最常见的新建一个对象的方式就是使用花括号的方式。然后使用’ . 的方式往里面添加属性和方法。可见以下代码&#xff1a; let animal {}; animal.name Leo; animal.energe 10;animal.eat function (amount) {console.log(${this.name} is ea…...

Liunx系统编程:信号量

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

大集合按照指定长度进行分割成多个小集合,用于批量多次处理数据

&#x1f4da;目录 拆分案例拆分的核心代码 通常我们对集合的更新或者保存都需要用集合来承载通过插入的效率&#xff0c;但是这个会遇到一个问题就是你不知道那天那个集合的数量可能就超了&#xff0c;虽然我们连接数据库进行批量提交会在配置上配置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实现的电影票购票管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都…...

数据结构--6.0最短路径

目录 一、迪杰斯特拉算法&#xff08;Dijkstra&#xff09; 二、弗洛伊德算法&#xff08;Floyd&#xff09; 在网图和非网图中&#xff0c;最短路径的含义是不同的。 ——网图是两顶点经过的边上的权值之和最少的路径。 …...

Docker进阶:mysql 主从复制、redis集群3主3从【扩缩容案例】

Docker进阶&#xff1a;mysql 主从复制、redis集群3主3从【扩缩容案例】 一、Docker常规软件安装1.1 docker 安装 tomcat&#xff08;默认最新版&#xff09;1.2 docker 指定安装 tomcat8.01.3 docker 安装 mysql 5.7&#xff08;数据卷配置&#xff09;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认证

澳大利亚政府公布了《消费品&#xff08;纽扣/硬币电池&#xff09;安全标准》和《消费品&#xff08;纽扣/硬币电池&#xff09;信息标准》。届时出口纽扣/硬币电池以及含有纽扣/硬币电池产品到澳大利亚的供应商&#xff0c;必须遵守这些标准中的要求。 一、 安全标准及信息标…...

spring高级源码50讲-37-42(springBoot)

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

腾讯云、阿里云、华为云便宜云服务器活动整理汇总

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

L1-055 谁是赢家(Python实现) 测试点全过

前言&#xff1a; {\color{Blue}前言&#xff1a;} 前言&#xff1a; 本系列题使用的是&#xff0c;“PTA中的团体程序设计天梯赛——练习集”的题库&#xff0c;难度有L1、L2、L3三个等级&#xff0c;分别对应团体程序设计天梯赛的三个难度。更新取决于题目的难度&#xff0c;…...

开发一个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&#xff0c;观测文件变化&#xff0c;源码地址&#xff1a;https://github.com/ThomasMonkman/filewatch nlohmann::json&#xff0c;json封装解析&#xff0c;源码地址&#xff1a;https://github.com/nlohmann/json optionparser&#xff0c;解析选项&#xff0c;源…...

Vue:关于声明式导航中的 跳转、高亮、以及两个类名的定制

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

Sharding-JDBC分库分表-自动配置与分片规则加载原理-3

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

E8267D 是德科技矢量信号发生器

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

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…...

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...

GraphRAG优化新思路-开源的ROGRAG框架

目前的如微软开源的GraphRAG的工作流程都较为复杂&#xff0c;难以孤立地评估各个组件的贡献&#xff0c;传统的检索方法在处理复杂推理任务时可能不够有效&#xff0c;特别是在需要理解实体间关系或多跳知识的情况下。先说结论&#xff0c;看完后感觉这个框架性能上不会比Grap…...