建立Socket連接至少需要一對套接字,其中一個運行於客戶端,稱為ClientSocket ,另一個運行於服務器端,稱為ServerSocket 。
套接字之間的連接過程分為三個步驟:服務器監聽,客戶端請求,連接確認。
服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求。
客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。
連接確認:當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。
認識socket相關的PHP函數
socket_accept() # 接受一個Socket連接
socket_bind() # 把socket綁定在一個IP地址和端口上
socket_clear_error() # 清除socket的錯誤或者最后的錯誤代碼
socket_close() # 關閉一個socket資源
socket_connect() # 開始一個socket連接
socket_create_listen() # 在指定端口打開一個socket監聽
socket_create_pair() # 產生一對沒有區別的socket到一個數組里
socket_create() # 產生一個socket,相當於產生一個socket的數據結構
socket_get_option() # 獲取socket選項
socket_getpeername() # 獲取遠程類似主機的ip地址
socket_getsockname() # 獲取本地socket的ip地址
socket_iovec_add() # 添加一個新的向量到一個分散/聚合的數組
socket_iovec_alloc() # 這個函數創建一個能夠發送接收讀寫的iovec數據結構
socket_iovec_delete() # 刪除一個已經分配的iovec
socket_iovec_fetch() # 返回指定的iovec資源的數據
socket_iovec_free() # 釋放一個iovec資源
socket_iovec_set() # 設置iovec的數據新值
socket_last_error() # 獲取當前socket的最后錯誤代碼
socket_listen() # 監聽由指定socket的所有連接
socket_read() # 讀取指定長度的數據
socket_readv() # 讀取從分散/聚合數組過來的數據
socket_recv() # 從socket里結束數據到緩存
socket_recvfrom() # 接受數據從指定的socket,如果沒有指定則默認當前socket
socket_recvmsg() # 從iovec里接受消息
socket_select() # 多路選擇
socket_send() # 這個函數發送數據到已連接的socket
socket_sendmsg() # 發送消息到socket
socket_sendto() # 發送消息到指定地址的socket
socket_set_block() # 在socket里設置為塊模式
socket_set_nonblock() # socket里設置為非塊模式
socket_set_option() # 設置socket選項
socket_shutdown() # 這個函數允許你關閉讀、寫、或者指定的socket
socket_strerror() # 返回指定錯誤號的詳細錯誤
socket_write() # 寫數據到socket緩存
socket_writev() # 寫數據到分散/聚合數組
創建一個socket
產生一個Socket,你需要三個變量:一個協議、一個socket類型和一個公共協議類型。產生一個socket有三種協議供選擇,繼續看下面的內容來獲取詳細的協議內容。
定義一個公共的協議類型是進行連接一個必不可少的元素。下面的表我們看看有那些公共的協議類型。
表一:協議
| 名字/常量 | 描述 |
|---|---|
| AF_INET | 這是大多數用來產生socket的協議,使用TCP或UDP來傳輸,用在IPv4的地址 |
| AF_INET6 | 與上面類似,不過是來用在IPv6的地址 |
| AF_UNIX | 本地協議,使用在Unix和Linux系統上,它很少使用,一般都是當客戶端和服務器在同一台及其上的時候使用 |
表二:Socket類型
| 名字/常量 | 描述 |
|---|---|
| SOCK_STREAM | 這個協議是按照順序的、可靠的、數據完整的基於字節流的連接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。 |
| SOCK_DGRAM | 這個協議是無連接的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的連接。 |
| SOCK_SEQPACKET | 這個協議是雙線路的、可靠的連接,發送固定長度的數據包進行傳輸。必須把這個包完整的接受才能進行讀取。 |
| SOCK_RAW | 這個socket類型提供單一的網絡訪問,這個socket類型使用ICMP公共協議。(ping、traceroute使用該協議) |
| SOCK_RDM | 這個類型是很少使用的,在大部分的操作系統上沒有實現,它是提供給數據鏈路層使用,不保證數據包的順序 |
表三:公共協議
| 名字/常量 | 描述 |
|---|---|
| ICMP | 互聯網控制消息協議,主要使用在網關和主機上,用來檢查網絡狀況和報告錯誤信息 |
| UDP | 用戶數據報文協議,它是一個無連接,不可靠的傳輸協議 |
| TCP | 傳輸控制協議,這是一個使用最多的可靠的公共協議,它能保證數據包能夠到達接受者那兒,如果在傳輸過程中發生錯誤,那么它將重新發送出錯數據包。 |
現在你知道了產生一個socket的三個元素,那么我們就在php中使用socket_create()函數來產生一個socket。這個socket_create()函數需要三個參數:一個協議、一個socket類型、一個公共協議。socket_create()函數運行成功返回一個包含socket的資源類型,如果沒有成功則返回false
socket通訊演示

服務器端:server.php
<?php
//確保在連接客戶端時不會超時
set_time_limit(0);
$ip = '127.0.0.1';
$port = 1935;
/*
+-------------------------------
* @socket通信整個過程
+-------------------------------
* @socket_create
* @socket_bind
* @socket_listen
* @socket_accept
* @socket_read
* @socket_write
* @socket_close
+--------------------------------
*/
/*---------------- 以下操作都是手冊上的 -------------------*/
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) { // 創建一個Socket鏈接
echo "socket_create() 失敗的原因是:" . socket_strerror($sock) . "\n";
}
if (($ret = socket_bind($sock, $ip, $port)) < 0) { //綁定Socket到端口
echo "socket_bind() 失敗的原因是:" . socket_strerror($ret) . "\n";
}
if (($ret = socket_listen($sock, 4)) < 0) { // 開始監聽鏈接鏈接
echo "socket_listen() 失敗的原因是:" . socket_strerror($ret) . "\n";
}
$count = 0;
do {
if (($msgsock = socket_accept($sock)) < 0) { //堵塞等待另一個Socket來處理通信
echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n";
break;
} else {
//發送消息到客戶端
$msg = "測試成功!\n";
socket_write($msgsock, $msg, strlen($msg));
//接收客戶端消息
echo "測試成功了啊\n";
$buf = socket_read($msgsock, 8192); // 獲得客戶端的輸入
$talkback = "收到的信息:$buf\n";
echo $talkback;
if (++$count >= 5) {
break;
};
}
//echo $buf;
socket_close($msgsock);
} while (true);
socket_close($sock);
?>
然后php server.php,發現1935端口已經處於被監聽狀態;接下來我們只要運行客戶端程序即可連接上。

這樣就完成第一步服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求。
接下來就第二步客戶端請求:
<?php
error_reporting(E_ALL);
set_time_limit(0);
echo "<h2>TCP/IP Connection</h2>\n";
$port = 1935;
$ip = "127.0.0.1";
/*
+-------------------------------
* @socket連接整個過程
+-------------------------------
* @socket_create
* @socket_connect
* @socket_write
* @socket_read
* @socket_close
+--------------------------------
*/
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 第一個參數”AF_INET”用來指定域名;
// 第二個參數”SOCK_STREM”告訴函數將創建一個什么類型的Socket(在這個例子中是TCP類型),UDP是SOCK_DGRAM
if ($socket < 0) {
echo "socket_create() failed: reason: " . socket_strerror($socket) . "\n";
} else {
echo "OK.\n";
}
echo "試圖連接 '$ip' 端口 '$port'...\n";
$result = socket_connect($socket, $ip, $port);
if ($result < 0) {
echo "socket_connect() failed.\nReason: ($result) " . socket_strerror($result) . "\n";
} else {
echo "連接OK\n";
}
$in = "Ho\r\n";
$in.= "first blood\r\n";
$out = '';
if (!socket_write($socket, $in, strlen($in))) {
echo "socket_write() failed: reason: " . socket_strerror($socket) . "\n";
} else {
echo "發送到服務器信息成功!\n";
echo "發送的內容為:<font color='red'>$in</font> <br>";
}
while ($out = socket_read($socket, 8192)) {
echo "接收服務器回傳信息成功!\n";
echo "接受的內容為:", $out;
}
echo "關閉SOCKET...\n";
socket_close($socket);
echo "關閉OK\n";
?>
這時我們來看看各自的鏈接(先不管圖中的錯誤,這是我php配置有問題~)


然后服務器端接着處於監聽狀態,每次client請求都會接到反饋,注意該列使用的socket通訊方式其實是很落后的同步阻塞 IO 模型,其上還有同步非阻塞 IO 模型(select/poll 的同步模型)以及使用 epoll/kqueue 的異步模型:屬於異步阻塞/非阻塞 IO 模型;(大多數都是epoll/kqueue模型)
