PHP套接字編程
發(fā)表時(shí)間:2024-02-24 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]作者:久隆信息/張曉剛 套接字編程,一般使用c或c++。特別的在web應(yīng)用程序開(kāi)發(fā)中,常用perl實(shí)現(xiàn)套接字。除此以外,用php進(jìn)行套接字編程也是一個(gè)選擇。Php可以勝任嗎?當(dāng)然可以。Php是一門(mén)高質(zhì)量的web應(yīng)用程序開(kāi)發(fā)語(yǔ)言,他的許多特性可以處理眾多的任務(wù),網(wǎng)絡(luò)編程也不例外。1. 理解...
作者:久隆信息/張曉剛
套接字編程,一般使用c或c++。特別的在web應(yīng)用程序開(kāi)發(fā)中,常用perl實(shí)現(xiàn)套接字。除此以外,用php進(jìn)行套接字編程也是一個(gè)選擇。Php可以勝任嗎?當(dāng)然可以。Php是一門(mén)高質(zhì)量的web應(yīng)用程序開(kāi)發(fā)語(yǔ)言,他的許多特性可以處理眾多的任務(wù),網(wǎng)絡(luò)編程也不例外。
1. 理解套接字
Mail、ftp、telnet、name和finger這些服務(wù)都是在一個(gè)專(zhuān)用的公開(kāi)的端口上提供的,通過(guò)連接到這些端口,客戶(hù)程序就能夠訪問(wèn)這些服務(wù)。這與現(xiàn)實(shí)生活是相似的——當(dāng)需要干洗衣服的時(shí)候,找干洗店;當(dāng)需要取錢(qián)的時(shí)候,去銀行,等等。除了專(zhuān)用于特定服務(wù)器的端口外,計(jì)算機(jī)還有其它的端口讓程序員創(chuàng)建他們自己的服務(wù)器。
端口一般是編號(hào)的,通過(guò)指定服務(wù)器的端口號(hào),客戶(hù)程序可以連接到該端口上。每種服務(wù)器或端口要有特定的協(xié)議,為了讓客戶(hù)的請(qǐng)求能夠被理解和響應(yīng),客戶(hù)必須以這種服務(wù)器特有的方式形成客戶(hù)請(qǐng)求。
Socket是網(wǎng)絡(luò)上運(yùn)行的兩個(gè)程序間雙向通信連接的一端。Socket這個(gè)詞的一般意義是自然的或人工的插口,如家用電器的電源插口等。
客戶(hù)程序可以向Socket寫(xiě)請(qǐng)求,服務(wù)器將處理此請(qǐng)求,然后通過(guò)Socket把結(jié)果返回給客戶(hù)。
Socket是一種底層連接。客戶(hù)機(jī)和服務(wù)器通過(guò)寫(xiě)入到Socket的字節(jié)流進(jìn)行通信。它們必須有共同的協(xié)議,也就是說(shuō),通過(guò)Socket相互傳送信息時(shí)所用的語(yǔ)言必須是協(xié)定好的。
2. Socket建立連接的過(guò)程
建立過(guò)程如下:(connection-oriented)
server 方過(guò)程 client 方過(guò)程
socket() socket()
bind() bind()
listen()
accept()<------------------connect()
recv()/send() <----------> send()/recv()
3. Php 基本套接字調(diào)用:
3.1. 基本套接字調(diào)用
創(chuàng)建套接字--socket();
綁定本機(jī)端口--bind();
建立連接--connect(),accept();
偵聽(tīng)端口--listen();
數(shù)據(jù)傳輸--send(),recv();
輸入/輸出多路復(fù)用--select();
關(guān)閉套接字--closesocket()
3.2. php提供的套接字調(diào)用:
接受連接-—accept connect()
綁定端口—bind ()
關(guān)閉套接字—close()
初始化連接—connect()
偵聽(tīng)端口—listen()
讀取套接字—read()
創(chuàng)建套接字—socket()
寫(xiě)套接字—write()
4. 基本應(yīng)用
4.1. 一個(gè)簡(jiǎn)單的TCP服務(wù)器
1 #!/usr/local/bin/php -q
2
3 <?php
4 /*
5 * We don't want any time-limit for how the long can hang
6 * around, waiting for connections:
7 */
8 set_time_limit(0);
9
10 /* Create a new socket: */
11 if( ($sock = socket( AF_INET, SOCK_STREAM, 0 )) < 0 )
12 {
13 print strerror( $sock ) . "n";
14 exit(1);
15 }
16
17 /* Bind the socket to an address and a port: */
18 if( ($ret = bind( $sock, "10.31.172.77", 10000 )) < 0 )
19 {
20 print strerror( $ret ) . "n";
21 exit(1);
22 }
23
24 /*
25 * Listen for incoming connections on $sock.
26 * The '5' means that we allow 5 queued connections.
27 */
28 if( ($ret = listen( $sock, 5 )) < 0 )
29 {
30 print strerror( $ret ) . "n";
31 }
32
33 /* Accept incoming connections: */
34 if( ($msgsock = accept_connect( $sock )) < 0)
35 {
36 print strerror( $msgsock ) . "n";
37 exit(1);
38 }
39
40 /* Send the welcome-message: */
41 $message = "Welcome to my TCP-server!n";
42 if( ($ret = write( $msgsock, $message, strlen($message)) ) < 0 )
43 {
44 print strerror( $msgsock ) . "n";
45 exit(1);
46 }
47
48 /* Read/Receive some data from the client: */
49 $buf = '';
50 if( ($ret = read( $msgsock, $buf, 128 )) < 0 )
51 {
52 print strerror( $ret ) . "n";
53 exit(1);
54 }
55
56 /* Echo the received data back to the client: */
57 if( ($ret = write( $msgsock, "You said: $bufn", strlen("You said: $bufn")) ) < 0 )
58 {
59 print strerror( $ret ) . "n";
60 exit(1);
61 }
62
63 /* Close the communication-socket: */
64 close( $msgsock );
65
66 /* Close the global socket: */
67 close( $sock );
68 ?>
第8行:使用set_time_limit設(shè)定程序執(zhí)行時(shí)間為無(wú)限以等待連接;
11-15: 創(chuàng)建一個(gè)套接字;
18-22: 把創(chuàng)建的套接字與IP及端口綁定;
28-31: 偵聽(tīng)端口;
34-38: 接受連接;
41-46: 顯示歡迎信息;
49-54: 讀取客戶(hù)端信息;
57-61: 向客戶(hù)端回顯信息;
63-67: 關(guān)閉套接字
4.2. TCP服務(wù)器的運(yùn)行
上邊這個(gè)tcp服務(wù)器的運(yùn)行要求php編譯成cgi解釋方式,并且編譯時(shí)加入--enable-sockets。
如果你已經(jīng)編譯成cgi解釋方式運(yùn)行,但是使用命令php -m列出的項(xiàng)目沒(méi)有sockets,則說(shuō)明你需要重新編譯php。當(dāng)這些要求達(dá)到后你就可以運(yùn)行這個(gè)服務(wù)器了
啟動(dòng)服務(wù)器:
./filename.php
然后就可以使用telnet登錄了。
telnet 10.31.172.77 10000
你的終端上將顯示:
Trying 10.31.172.77...
Connected to 10.31.172.77.
Escape character is '^]'.
Welcome to my TCP server!
然后輸入一些東西,并回車(chē):
Hello
You said: Hello
Connection closed by foreign host
你也可以修改一下這個(gè)程序,讓它像phpmanual上的那個(gè)例子,只有當(dāng)客戶(hù)端輸入“quit“的時(shí)候才關(guān)閉連接。
5. 其他應(yīng)用
5.1. 聊天室應(yīng)用
5.1.1. 常見(jiàn)的聊天室實(shí)現(xiàn)
一般的聊天室的實(shí)現(xiàn)常使用的方法是使用框架頁(yè)面,然后對(duì)其中一個(gè)用于顯示談話內(nèi)容的框架使用html的方式刷新,例如:
<meta http-equiv=“refresh” content=”3;http://www.jite.net”>
使用這種方式會(huì)導(dǎo)致瀏覽器端不斷的向服務(wù)器端發(fā)出請(qǐng)求,當(dāng)有大量的請(qǐng)求時(shí)就會(huì)使得服務(wù)器運(yùn)行效率降低。這樣的聊天室顯然是有設(shè)計(jì)弊端的。
但是如果使用socket的方式實(shí)現(xiàn)聊天室,情況就不同了。
5.1.2. 使用socket實(shí)現(xiàn)聊天室
我們要討論的聊天室非常簡(jiǎn)單,只是一個(gè)原理上的實(shí)現(xiàn)。
它是一個(gè) client/server 結(jié)構(gòu)的程序, 首先啟動(dòng) server, 然后用戶(hù)使用 client 進(jìn)行連接. client/server 結(jié)構(gòu)的優(yōu)點(diǎn)是速度快, 缺點(diǎn)是當(dāng) server 進(jìn)行更新時(shí), client 也必需更新.
初始化 server, 使server 進(jìn)入監(jiān)聽(tīng)狀態(tài): (以下只是實(shí)現(xiàn)原理,并不涉及具體程序)
$socket = socket( AF_INET,SOCK_STREAM, 0);
// 首先建立一個(gè) socket, 族為 AF_INET, 類(lèi)型為 SOCK_STREAM.
// AF_INET = ARPA Internet protocols 即使用 TCP/IP 協(xié)議族
// SOCK_STREAM 類(lèi)型提供了順序的, 可靠的, 基于字節(jié)流的全雙工連接.
// 由于該協(xié)議族中只有一個(gè)協(xié)議, 因此第三個(gè)參數(shù)為 0
bind ($sock, $address, $port)
// 再將這個(gè) socket 與某個(gè)地址進(jìn)行綁定.
listen( sockfd, MAX_CLIENT)
// 地址綁定之后, server 進(jìn)入監(jiān)聽(tīng)狀態(tài).
// MAX_CLIENT 是可以同時(shí)建立連接的 client 總數(shù).
server 進(jìn)入 listen 狀態(tài)后, 等待 client 建立連接。
Client端要建立連接首先也需要初始化連接:
$socket= socket( AF_INET,SOCK_STREAM,0))
// 同樣的, client 也先建立一個(gè) socket, 其參數(shù)與 server 相同.
connect ($socket, $address, $service_port)
// client 使用 connect 建立一個(gè)連接.
當(dāng) client 建立新連接的請(qǐng)求被送到Server端時(shí), server 使用 accept 來(lái)接受該連接:
accept_connect($sock)
// accept 返回一個(gè)新的文件描述符.
在 server 進(jìn)入 listen 狀態(tài)之后, 由于可能有多個(gè)用戶(hù)請(qǐng)求連接,所以程序需要同時(shí)對(duì)這些用戶(hù)進(jìn)行操作,并在它們之間實(shí)現(xiàn)信息交換。這在實(shí)現(xiàn)上稱(chēng)為I/O多路復(fù)用技術(shù)。
I/O多路復(fù)用技術(shù)的方法就不是本文所要敘述的內(nèi)容了,如有興趣請(qǐng)參考相關(guān)書(shū)籍。
5.2. 一個(gè)基于web的新聞組瀏覽器
在php中可以使用fsockopen打開(kāi)一個(gè)tcp socket連接
int fsockopen (string hostname, int port [, int errno [, string errstr [, double timeout]]])
有關(guān)此函數(shù)的使用請(qǐng)參考php手冊(cè)。
訪問(wèn)新聞組服務(wù),需要使用一個(gè)協(xié)議叫NNTP,即Network News Transfer Protocol。
這個(gè)協(xié)議有一個(gè)專(zhuān)用的RFC描述,它位于 http://www.w3.org/Protocols/rfc977/rfc977.html。
該文檔詳細(xì)的說(shuō)明了如何同一個(gè)nntp服務(wù)器對(duì)話及如何使用命令完成任務(wù)。
5.2.1. 連接一個(gè)服務(wù)器
<?php
$cfgServer = "news.php.net";
$cfgPort = 119;
$cfgTimeOut = 10;
// open a socket
if(!$cfgTimeOut)
// without timeout
$usenet_handle = fsockopen($cfgServer, $cfgPort);
else
// with timeout
$usenet_handle = fsockopen($cfgServer, $cfgPort, &$errno, &$errstr, $cfgTimeOut);
if(!$usenet_handle) {
echo "Connexion failedn";
exit();
}
else {
echo "Connectedn";
$tmp = fgets($usenet_handle, 1024);
}
?>
5.2.2. 同服務(wù)器進(jìn)行對(duì)話
在前面,我們已經(jīng)同服務(wù)器連接上了,假如我們要從某一新聞組中選取10條最近的新聞,該怎么辦呢?
RFC977指出,選擇一個(gè)新聞組使用group命令:
GROUP ggg
<?php
//$cfgUser = "xxxxxx";
//$cfgPasswd = "yyyyyy";
$cfgNewsGroup = "alt.php";
// identification required on private server
if($cfgUser) {
fputs($usenet_handle, "AUTHINFO USER ".$cfgUser."n");
$tmp = fgets($usenet_handle, 1024);
fputs($usenet_handle, "AUTHINFO PASS ".$cfgPasswd."n");
$tmp = fgets($usenet_handle, 1024);
// check error
if($tmp != "281 Okrn") {
echo "502 Authentication errorn";
exit();
}
}
// select newsgroup
fputs($usenet_handle, "GROUP ".$cfgNewsGroup."n");
$tmp = fgets($usenet_handle, 1024);
if($tmp == "480 Authentication required for commandrn") {
echo "$tmpn";
exit();
}
$info = split(" ", $tmp);
$first = $info[2];
$last = $info[3];
print "First : $firstn";
print "Last : $lastn";
?>
5.2.3. 讀取新聞
讀取新聞的命令是article,具體用法請(qǐng)參考RFC977,這里就不提供例程了。
6. 后記
我以為上次寫(xiě)了一篇,這次就可以免了。離交稿日期沒(méi)幾天了,于榮賦來(lái)約稿。程稿倉(cāng)促,難免有錯(cuò),請(qǐng)見(jiàn)諒,并且指出。
7. 參考文獻(xiàn):
廖斌,《php的守護(hù)程序編程》;
w3c,《RFC977》;
Daniel Solin,Introduction to Socket Programming with PHP;