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. 待续

……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/66065.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实现一个简单的控制台版用户登陆程序, 程序启动提示用户输入用户名密码. 如果用户名密码出错, 使用自定义异常的方式来处理

//密码错误异常类 public class PasswordError extends Exception {public PasswordError(String message){super(message);} }//用户名错误异常类 public class UserError extends Exception{public UserError(String message){super(message);} }import java.util.Scanner;pu…

Vue之html中特殊符号的展示

Vue之html中特殊符号的展示 在html中使用特殊字符时直接展示会报错&#xff0c;需要使用实体名称或者实体编号才能展示。 最常用的字符实体 显示结果 描述 实体名称 实体编号空格 < 小于号 < &…

代码随想录 - Day32 - 回溯:组合问题

代码随想录 - Day32 - 回溯&#xff1a;组合问题 39. 组合总和 做题的时候遇到一点疑问&#xff1a; 为什么必须是result.append(path[:])而不能写成result.append(path)呢&#xff1f; 原因&#xff1a; result.append(path)往result中添加的是path这个参数&#xff0c;后续…

java内存溢出问题分析记录

最好找到必现流程在idea-VMOption中输入参数&#xff1a; -XX:HeapDumpOnOutOfMemoryError将会在内存溢出发生时&#xff0c;在项目根目录生成dump文件。 打开jdk自带的分析工具java visualVM&#xff0c;可以打开该文件&#xff0c;定位到内存溢出发生的代码位置。 分析该位…

【CicadaPlayer】seek :SeekInCache(int64_t pos)的实现

SuperMediaPlayer::SeekInCache(int64_t pos) 的实现 seek的pos就是pts值。缓冲是list,那么插入的包是按照到达的顺序插入到list的,也就是无排序的。包的pts 正常应该单调连续,即使不单调连续,缓存也不在意。seek的操作主要是先比较pos与mCurrentPos ,pos 比 mCurrentPos …

WPF数据模板

样式提供了基本的格式化能力&#xff0c;但它们不能消除到目前为止看到的列表的最重要的局限性&#xff1a;不管如何修改ListBoxItem&#xff0c;它都只是ListBoxItem&#xff0c;而不是功能更强大的元素组合。并且因为每个ListBoxItem只支持单个绑定字段&#xff0c;所以不可能…

springboot实现发送短信验证码

目录 一、选择并注册短信服务提供商&#xff1a; 二、添加依赖&#xff1a; 三、配置短信服务信息&#xff1a; 四、编写发送短信验证码的方法&#xff1a; 五、调用发送短信验证码的方法&#xff1a; 一、选择并注册短信服务提供商&#xff1a; 1、选择一个可靠的短信服…

【微服务部署】07-调用链追踪

文章目录 集成SkyWalking实现调用链追踪1. SkyWalking架构图2. 代码集成SkyWalking 集成SkyWalking实现调用链追踪 1. SkyWalking架构图 Receiver是SkyWalking的入口&#xff0c;支持gRPC和HTTP协议。 SkyWalking内部有分析和查询两个部分 存储方面SkyWalking支持Elasticsearc…

跟我学c++中级篇—c++11时间库实现定时器和延时

一、C11时间库 先简单介绍一下C11中有三类时钟&#xff1a; 1、system_clock&#xff1a;可以理解为壁钟&#xff0c;可调整&#xff08;向前或向后&#xff09;&#xff0c;是系统时间。它可以 与C语言风格的时间进行映射。 2、steady_clock&#xff1a;类似于秒表的单调时钟…

Windows下Git Bash调用rsync

rsync 提供了补充只需要在git安装目录下放入对应的文件即可。 需要将这个三个文件放到git的bin目录下 如果是默认安装路径是如下&#xff1a; C:\Program Files\Git\usr\bin 然后大功告成。

【Vue2】 axios库

网络请求库-axios库 认识Axios库为什么选择Axios库安装Axios axios发送请求常见的配置选项简单请求可以给Axios设置公共的基础配置发送多个请求 axios创建实例为什么要创建axios的实例 axios的拦截器请求拦截器响应拦截器 axios请求封装 认识Axios库 为什么选择Axios库 在游览…

105. 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 思路&#xff1a;题目给出了先序遍历和中序遍历的结果&#xff0c;因为先序遍历遵循根–>左–>…

21 自定义miniweb框架|闭包装饰器|log输出

文章目录 前情知识介绍WSIG-miniWeb框架服务器动态资源请求浏览器请求动态页面的全流程WSGIWSGI接口的定义 静态服务器回顾以及改造web 服务器 和 逻辑处理代码 分离 web动态服务器的基本实现带参数的web动态服务器 闭包装饰器闭包闭包的基础使用函数、匿名函数、闭包、对象修改…

python相关

1、更改用户名之后&#xff0c;C盘下的文件夹下名称没有改&#xff1f;这样设置 https://blog.csdn.net/qq_56088882/article/details/127470766 2、安装python和pycharm 链接 3、vscod中import requests出错&#xff1a;亲测有效&#xff1a; 链接

【Nginx21】Nginx学习:FastCGI模块(三)缓冲区与响应头

Nginx学习&#xff1a;FastCGI模块&#xff08;三&#xff09;缓冲区与响应头 缓存相关的内容占了 FastCGI 模块将近一小半的内容&#xff0c;当然&#xff0c;用过的人可能不多。而今天的内容说实话&#xff0c;我平常也没怎么用过。第一个是缓冲区相关的知识&#xff0c;其实…

Nat. Commun.2023 | AI-Bind+:提高蛋白质配体结合预测的通用性

论文标题&#xff1a;Improving the generalizability of protein-ligand binding predictions with AI-Bind 论文地址&#xff1a;Improving the generalizability of protein-ligand binding predictions with AI-Bind | Nature Communications 代码&#xff1a; Barabasi…

华为数通方向HCIP-DataCom H12-821题库(单选题:181-200)

第181题 某管理员需要创建AS Path过滤器(ip as-path-iter),允许AS_Path中包含65001的路由通过,那么以下哪一项配置是正确的? A、​​ip as-path-filter 1 permit 65001​​ B、​​ip as-path-filter 1 permit "65001​​ C、​​ip as-path-filter 1 permit *6500…

wxWidgets从空项目开始Hello World

前文回顾 接上篇&#xff0c;已经是在CodeBlocks20.03配置了wxWidgets3.0.5&#xff0c;并且能够通过项目创建导航创建一个新的工程&#xff0c;并且成功运行。 那么上一个是通过CodeBlocks的模板创建的&#xff0c;一进去就已经是2个头文件2个cpp文件&#xff0c;总是感觉缺…

Pygame中Trivia游戏解析6-3

3.3 Trivia类的show_question()函数 Trivia类的show_question()函数的作用是显示题目。主要包括显示题目框架、显示题目内容和显示题目选项等三部分。 3.3.1 显示题目的框架 在show_question()函数中&#xff0c;通过以下代码显示题目的框架。 print_text(font1, 210, 5, &q…

docker-compose 部署nacos 整合 postgresql 为DB

标题docker-compose 部署nacos 整合 postgresql 为DB 前提&#xff1a; 已经安装好postgresql数据库 先创建好一个数据库 nacos&#xff0c;执行以下sql: /** Copyright 1999-2018 Alibaba Group Holding Ltd.** Licensed under the Apache License, Version 2.0 (the "…