HTTP 協定本質是「請求-回應」模式,瀏覽器發起請求,伺服器回應。ServerPush(長連接)透過保持連接開啟,模擬伺服器「主動推送」資料給客戶端。
T_Msgs 表public class ServerPush1 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";
string action = context.Request["action"];
if (action == "send") // 發送訊息
{
string me = context.Request["me"];
string toUserName = context.Request["toUserName"];
string msg = context.Request["msg"];
SQLHpler.ExecuteNonQuery(
"INSERT INTO T_Msgs(FromUserName,ToUserName,Msg) VALUES(@FromUserName,@ToUserName,@Msg)",
new SqlParameter("@FromUserName", me),
new SqlParameter("@ToUserName", toUserName),
new SqlParameter("@Msg", msg)
);
context.Response.Write(new JavaScriptSerializer().Serialize(new { Status = "ok" }));
}
else if (action == "receive") // 長連接接收
{
string me = context.Request["me"];
while (true) // 持續輪詢
{
DataTable dt = SQLHpler.ExecuteQuery(
"SELECT TOP 1 * FROM T_Msgs WHERE ToUserName=@ToUserName",
new SqlParameter("@ToUserName", me)
);
if (dt.Rows.Count <= 0)
{
Thread.Sleep(500); // 降低資料庫壓力與 CPU 佔用
continue;
}
else
{
DataRow row = dt.Rows[0];
long id = (long)row["Id"];
string fromUserName = (string)row["FromUserName"];
string msg = (string)row["Msg"];
// 必須刪除,否則陷入死循環重複推送
SQLHpler.ExecuteNonQuery(
"DELETE FROM T_Msgs WHERE Id=@Id",
new SqlParameter("@Id", id)
);
var data = new { FromUserName = fromUserName, Msg = msg, Id = id };
string json = new JavaScriptSerializer().Serialize(data);
context.Response.Write(json);
break; // 回應後結束此次長連接
}
}
}
else
{
throw new Exception("action錯誤");
}
}
}
<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>ServerPush Demo</title>
<script src="jquery-1.8.3.min.js"></script>
<script type="text/javascript">
var rev = function () {
var mine = $('#me').val();
$.ajax({
type: 'post',
url: 'serverPush1.ashx',
data: { action: 'receive', me: mine },
success: function (data) {
$('#ulMsg').append($('<li>' + data.FromUserName + '對我說:' + data.Msg + '</li>'));
rev(); // 收到訊息後立即發起下一次請求
},
error: function () {
rev(); // 網路失敗也重試
}
});
};
$(function () {
// 發送訊息
$('#btnSend').click(function () {
var myName = $('#me').val();
var toUserName = $('#toUserName').val();
var msg = $('#msgContext').val();
$.ajax({
type: 'post',
url: 'serverPush1.ashx',
data: { action: 'send', me: myName, toUserName: toUserName, msg: msg },
success: function (data) {
if (data.Status == 'ok') {
$('#ulMsg').append($('<li>我對' + toUserName + '說:' + msg + '</li>'));
$('#msgContext').val('');
} else {
alert('發送出錯');
}
},
error: function () { alert('發送出錯'); }
});
});
// 登入開始接收
$('#btnLogin').click(function () {
rev();
$(this).attr("disabled", "disabled");
});
});
</script>
</head>
<body>
發送人:<input type="text" id="me" /><input type="button" id="btnLogin" value="登陸" /><br />
接收人:<input type="text" id="toUserName" /><br />
輸入訊息:<input type="text" id="msgContext" /><input type="button" id="btnSend" value="發送" /><br />
聊天記錄:<br />
<ul id="ulMsg"></ul>
</body>
</html>
| 環節 | 策略 | 目的 |
|---|---|---|
| 輪詢間隔 | Thread.Sleep(500) | 平衡即時性與資料庫負載 |
| 訊息處理 | 讀取後立即刪除 | 避免重複推送、資料堆積 |
| 前端重連 | 成功/失敗皆呼叫 rev() | 維持長連接存活、網路異常自動恢復 |
| 連接管理 | 登入按鈕啟用後禁用 | 防止重複建立多條長連接 |
此 ServerPush 實作展示了在無 WebSocket/SignalR 環境下,利用長輪詢模擬伺服器推送的經典模式,理解其原理有助於把握即時通訊技術演進脈絡。