WHOIS (RFC 3912) 是基於 TCP 埠 43 的純文字查詢協議。客戶端連線至 WHOIS 伺服器,發送域名 + \r\n,伺服器回傳註冊資訊後關閉連線。各頂級域 (TLD) 有各自的權威 WHOIS 伺服器。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WhoisQuery.aspx.cs" Inherits="Demo.WhoisQuery" %>
域名 WHOIS 查詢
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
namespace Demo
{
public partial class WhoisQuery : Page
{
// 常見 TLD 對應的 WHOIS 伺服器
private static readonly Dictionary WhoisServers = new(StringComparer.OrdinalIgnoreCase)
{
{ "com", "whois.verisign-grs.com" },
{ "net", "whois.verisign-grs.com" },
{ "org", "whois.pir.org" },
{ "info", "whois.afilias.net" },
{ "biz", "whois.biz" },
{ "tw", "whois.twnic.net.tw" },
{ "cn", "whois.cnnic.cn" },
{ "jp", "whois.jprs.jp" },
{ "kr", "whois.kr" },
{ "hk", "whois.hkirc.hk" },
{ "io", "whois.nic.io" },
{ "ai", "whois.nic.ai" },
{ "dev", "whois.nic.google" },
{ "app", "whois.nic.google" },
};
protected void Page_Load(object sender, EventArgs e) { }
protected void btnQuery_Click(object sender, EventArgs e)
{
lblError.Visible = false;
litResult.Text = "";
string domain = txtDomain.Text.Trim().ToLowerInvariant();
if (string.IsNullOrEmpty(domain))
{
ShowError("請輸入域名");
return;
}
// 簡單驗證域名格式
if (!Regex.IsMatch(domain, @"^[a-z0-9.-]+\.[a-z]{2,}$"))
{
ShowError("域名格式不正確");
return;
}
try
{
string server = GetWhoisServer(domain);
string result = QueryWhois(server, domain);
litResult.Text = Server.HtmlEncode(result); // 防 XSS
}
catch (Exception ex)
{
ShowError($"查詢失敗:{ex.Message}");
// 實務上記錄完整堆疊追蹤
}
}
private string GetWhoisServer(string domain)
{
// 取得 TLD (最後一段)
string tld = domain.Split('.').Last();
if (WhoisServers.TryGetValue(tld, out string server))
return server;
// 兩字元 TLD 通常歸 RIPE NCC (歐洲)
if (tld.Length == 2)
return "whois.ripe.net";
// 預設回落
return "whois.iana.org"; // IANA 根伺服器會轉介
}
private string QueryWhois(string server, string domain)
{
using (var client = new TcpClient())
{
client.ReceiveTimeout = 10000;
client.SendTimeout = 10000;
client.Connect(server, 43); // WHOIS 標準埠
using (var stream = client.GetStream())
using (var writer = new StreamWriter(stream, Encoding.ASCII) { AutoFlush = true })
using (var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false))
{
// 發送查詢指令
writer.WriteLine(domain);
// 讀取完整回應 (伺服器關閉連線時結束)
return reader.ReadToEnd();
}
}
}
private void ShowError(string message)
{
lblError.Text = message;
lblError.Visible = true;
}
}
}
whois.iana.org 可查詢任何 TLD 的權威伺服器位址 (參考回應中的 refer: 欄位)Encoding.UTF8 讀取,失敗時嘗試 Encoding.GetEncoding("iso-8859-1")
private async Task QueryWhoisAsync(string server, string domain)
{
using (var client = new TcpClient())
{
await client.ConnectAsync(server, 43);
using (var stream = client.GetStream())
using (var writer = new StreamWriter(stream, Encoding.ASCII) { AutoFlush = true })
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
await writer.WriteLineAsync(domain);
return await reader.ReadToEndAsync();
}
}
}
// 按鈕事件改為 async void (Web Forms) 或 async Task (MVC/Core)
protected async void btnQuery_Click(object sender, EventArgs e)
{
// ... 驗證 ...
string result = await QueryWhoisAsync(server, domain);
litResult.Text = Server.HtmlEncode(result);
}
// 簡單記憶體快取 (生產環境建議 Redis/MemoryCache)
private static readonly MemoryCache WhoisCache = MemoryCache.Default;
private string GetCachedOrQuery(string domain)
{
string cacheKey = $"whois_{domain}";
if (WhoisCache.Contains(cacheKey))
return (string)WhoisCache.Get(cacheKey);
string result = QueryWhois(GetWhoisServer(domain), domain);
// 快取 1 小時 (WHOIS 資料不常變動)
WhoisCache.Set(cacheKey, result, DateTimeOffset.Now.AddHours(1));
return result;
}
public class WhoisRecord
{
public string DomainName { get; set; }
public string Registrar { get; set; }
public DateTime? CreationDate { get; set; }
public DateTime? ExpiryDate { get; set; }
public string NameServers { get; set; }
public string Status { get; set; }
public string RawText { get; set; }
}
public WhoisRecord ParseWhois(string raw)
{
var record = new WhoisRecord { RawText = raw };
// 使用 Regex 擷取關鍵欄位 (各伺服器格式略有不同)
record.DomainName = Extract(raw, @"Domain Name:\s*(.+)");
record.Registrar = Extract(raw, @"Registrar:\s*(.+)");
// ... 其他欄位
return record;
}
private string Extract(string text, string pattern)
{
var m = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
return m.Success ? m.Groups[1].Value.Trim() : null;
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 連線逾時 | 伺服器回應慢/防火牆阻擋輸出 43 埠 | 增加逾時、檢查伺服器防火牆/雲端安全群組 |
| 回應亂碼 | 編碼不符 (中文 TLD 常見) | 嘗試多種編碼讀取、或保留原始位元組讓前端處理 |
| 查無資料 / 格式異常 | 伺服器回應格式變更/限制頻率 | 加入快取、降低查詢頻率、支援多伺服器輪詢 |
| 被 IP 封鎖 | 短時間大量查詢 | 快取、速率限制、使用代理池、申請商業 API |
whois 命令列工具:Linux/Windows 內建,適合伺服器端腳本整合WHOIS 查詢雖屬經典協議,但在域名監控、品牌保護、網路資產管理仍不可或缺。掌握 TCP 43 埠通訊、編碼處理、伺服器選擇策略與快取機制,即可建立穩健的查詢系統。現代專案建議優先評估 RDAP 或商業 API,降低維護成本並獲得更豐富的結構化資料。