TCP の情報とプロセス ID、プロセス名を取得する
サーバーを管理していて、ネットワークのトラブルが発生してしまった場合など、TCP の状態を確認すると良い場合があります。
TCP の状態を確認する場合、netstat コマンド等で TCP の統計情報を取得することが出来ます。
しかしながら、netstat -ano 等では、プロセス名を確認することが出来ないのがちょっと不便です。 自前のサーバーであれば TCPView などの GUI ツールをインストールして情報確認するのも良いですが、手早く情報を採取したい場合にはコマンドの方が簡単ということも少なくないでしょう。
この資料では、IP ヘルパー API である、GetExtendedTcpTable を利用して TCP の情報を取得して、さらにそこで取得できるプロセス ID から、 さらにプロセス名を表示するようなプログラムを作成してみましょう。
GetExtendedTcpTable IP ヘルパー API
GetExtendedTcpTable を利用すると、TCP の様々な情報を取得することが出来ます。
第5引数に TCP_TABLE_OWNER_PID_ALL を指定すると、全ての状態の TCP に関わる情報を、そのオーナーとなるプロセスの PID 付きで返します。
コード例
以下のコード例では、GetExtendedTcpTable を利用して TCP の状態を、PID 付きで取得しています。
実行中のプロセスリストは、当サイトの記事「プロセスの列挙」 で紹介した方法で作成しています。
列挙したプロセス名とプロセス ID のペアを、PROCESS_ENTRY という名前の構造体の配列に格納しています。
コードを単純化するために固定長の配列の中に保存していますので、PROCESS_MAX_NUM で決めた数以上のプロセスが実行されている状況では、 プロセス名が正しく表示されません。必要に応じて直してください。
PID からプロセス名のマッピングは、単純に配列の中を毎回探すことによって実施しています。
また、エラー処理はほとんど何も行っていません。エラーが起きた場合は assert でプログラムを停止させています。 それが推奨のコーディング方法、というわけでは全くありませんので、初心者の人はコードを見るときに気をつけてください。
#include <windows.h> #include <iphlpapi.h> #include <tlhelp32.h> #include <stdio.h> #include <assert.h> ///////////////////////////////////////////////////////////////////////////// #define PROCESS_MAX_NUM (1024) typedef struct _PROCESS_ENTRY { DWORD dwProcessID; char szProcessName[MAX_PATH]; } PROCESS_ENTRY, *PPROCESS_ENTRY; ///////////////////////////////////////////////////////////////////////////// PROCESS_ENTRY g_ProcessTable[PROCESS_MAX_NUM]; DWORD g_cProcess = 0; ///////////////////////////////////////////////////////////////////////////// void InitializeProcessTable (){ HANDLE hProcessSnap = NULL; PROCESSENTRY32 pe32; BOOL bRet = FALSE; DWORD i = 0; ZeroMemory ( &pe32, sizeof (pe32) ); hProcessSnap = CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS, 0); assert( INVALID_HANDLE_VALUE != hProcessSnap ); pe32.dwSize = sizeof ( PROCESSENTRY32 ); bRet = Process32First ( hProcessSnap, &pe32 ); if ( bRet ) { do { sprintf( g_ProcessTable[i].szProcessName, "%s", pe32.szExeFile ); g_ProcessTable[i].dwProcessID = pe32.th32ProcessID; i++; } while ( Process32Next ( hProcessSnap, &pe32 ) && i < PROCESS_MAX_NUM ); g_cProcess = i; } CloseHandle ( hProcessSnap ); } ///////////////////////////////////////////////////////////////////////////// char* GetProcessName ( DWORD dwProcessID ) { for( DWORD i=0; i<g_cProcess; i++ ) { if ( g_ProcessTable[i].dwProcessID == dwProcessID ) { return g_ProcessTable[i].szProcessName; } } return ""; } ///////////////////////////////////////////////////////////////////////////// char* GetTcpState ( DWORD dwState) { switch(dwState){ case MIB_TCP_STATE_CLOSED: return "CLOSED"; case MIB_TCP_STATE_LISTEN: return "LISTEN"; case MIB_TCP_STATE_SYN_SENT: return "SYN_SENT"; case MIB_TCP_STATE_SYN_RCVD: return "SYN_RCVD"; case MIB_TCP_STATE_ESTAB: return "ESTAB"; case MIB_TCP_STATE_FIN_WAIT1: return "FIN_WAIT1"; case MIB_TCP_STATE_FIN_WAIT2: return "FIN_WAIT2"; case MIB_TCP_STATE_CLOSE_WAIT: return "CLOSE_WAIT"; case MIB_TCP_STATE_CLOSING: return "CLOSING"; case MIB_TCP_STATE_LAST_ACK: return "LAST_ACK"; case MIB_TCP_STATE_TIME_WAIT: return "TIME_WAIT"; case MIB_TCP_STATE_DELETE_TCB: return "DELETE_TCB"; } return "(unknown)"; } ///////////////////////////////////////////////////////////////////////////// int main (int argc, char* argv[]) { PMIB_TCPTABLE_OWNER_PID pTcpTable = NULL; DWORD dwSize = 0; BOOL bOrder = TRUE; ULONG ulAf = AF_INET; MIB_TCPROW_OWNER_PID row; DWORD dwRet; // // プロセステーブル(配列)の初期化 // InitializeProcessTable(); // // バッファサイズを求める // dwRet = GetExtendedTcpTable ( NULL, &dwSize, bOrder, ulAf, TCP_TABLE_OWNER_PID_ALL, 0); assert(dwRet == ERROR_INSUFFICIENT_BUFFER); // // バッファを割り当てる // pTcpTable = (PMIB_TCPTABLE_OWNER_PID) malloc ( dwSize ); assert(pTcpTable); // // TCP テーブルの取得 // dwRet = GetExtendedTcpTable ( pTcpTable, &dwSize, bOrder, ulAf, TCP_TABLE_OWNER_PID_ALL, 0); assert( dwRet == NO_ERROR ); // // 結果の表示 // for(DWORD i=0; i<pTcpTable->dwNumEntries; i++) { in_addr addr_l, addr_r; row = pTcpTable->table[i]; addr_l.S_un.S_addr = row.dwLocalAddr; addr_r.S_un.S_addr = row.dwRemoteAddr; printf("%-11s %15s:%-8d %15s:%-8d %-6u %s\n", GetTcpState(row.dwState), inet_ntoa(addr_l), ntohs(row.dwLocalPort), inet_ntoa(addr_r), ntohs(row.dwRemotePort), row.dwOwningPid, GetProcessName (row.dwOwningPid) ); } // // メモリブロックの解放 // free ( pTcpTable ); return 0; }
上記のコードを portpid.cpp として保存してください。
makefile は次のようになります。
TARGETNAME=portpid OUTDIR=.\chk LINK32=link.exe ALL : "$(OUTDIR)\$(TARGETNAME).exe" CPPFLAGS=\ /nologo\ /MT\ /W4\ /Fo"$(OUTDIR)\\"\ /Fd"$(OUTDIR)\\"\ /c\ /Zi\ /EHsc\ /D_WIN32_WINNT=0x0600 LINK32_FLAGS=\ Iphlpapi.lib\ Ws2_32.lib\ /nologo\ /subsystem:console\ /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\ /machine:x64\ /out:"$(OUTDIR)\$(TARGETNAME).exe"\ /DEBUG\ /RELEASE LINK32_OBJS= \ "$(OUTDIR)\$(TARGETNAME).obj" "$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS) $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS) "$(OUTDIR)" : if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" .c{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $< .cpp{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $<
作成された EXE を実行すると、次のような結果を得ます。
> portpid.exe LISTEN 0.0.0.0:80 0.0.0.0:0 4 System LISTEN 0.0.0.0:135 0.0.0.0:0 880 svchost.exe LISTEN 0.0.0.0:443 0.0.0.0:0 1264 Skype.exe LISTEN 0.0.0.0:445 0.0.0.0:0 4 System LISTEN 0.0.0.0:3200 0.0.0.0:0 804 java.exe LISTEN 0.0.0.0:3306 0.0.0.0:0 1912 mysqld.exe LISTEN 0.0.0.0:7005 0.0.0.0:0 1896 javaservice.exe LISTEN 0.0.0.0:7443 0.0.0.0:0 1896 javaservice.exe LISTEN 0.0.0.0:12807 0.0.0.0:0 1264 Skype.exe LISTEN 0.0.0.0:17080 0.0.0.0:0 1896 javaservice.exe LISTEN 0.0.0.0:17090 0.0.0.0:0 1896 javaservice.exe LISTEN 0.0.0.0:28080 0.0.0.0:0 804 java.exe ...
左側のカラムから、TCP の状態、ローカルアドレス、ポート、リモートアドレス、ポート、PID そして最後がプロセス名です。