准備做Java的課程設計,一個通訊錄。采用C/S架構。客戶端用java FX和Java,服務器端用php,采用socket通信。
下面來講一講php的socket通信:
講之前,得先講一下TCP/IP,UDP。隨著互聯網應用的日益廣泛,相信很多人或多或少都聽說過這些協議,那麼到底它們是什麼呢?
1、什麼是TCP/IP、UDP?
TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標准的協議集,它是為廣域網(WANs)設計的。TCP/IP協議族包括運輸層、網絡層、鏈路層。
UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。
下面我們上圖直觀地展示一下它們的關系:
這裡並沒有socket什麼事,那socket到底在哪裡呢?又有什麼用?別急,接下來我們就來介紹一下socket與它們的關系。同樣上圖。
現在明白它們的關系了吧。那socket到底是什麼?又有什麼用?
2、socket是什麼?有什麼用?
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
既然socket是一組封裝好的接口,那我們如何去運用它來通信呢?具體是一個什麼流程呢?我們可以詳細一下我們打電話的過程。首先我們的電話肯定是處於監聽狀態的,沒錯吧。如果不是,那肯定也不會接到別人打的電話。當被人打電話過來時,首先是先撥號,號碼是目標,也是唯一的。撥號後電話會開始連接,接通了之後就可以開始交談,聊完了之後要掛斷電話,這樣別人才能再打通這個號碼。 socket也是一樣的,服務器端要先建立socket監聽自己電腦某個端口,當有客戶端創建socket發起連接請求時接受,這樣就可以開始傳遞信息了,傳輸完之後再把socket關閉。當然,服務器端也可以主動向客戶端發起連接請求然後開始通信。這是區別於ajax,是可以雙向通信的。下面看圖:
先從服務器端說起。服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把回應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束
3、socket的相關函數 :
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() 寫數據到分散/聚合數組
4、socket通信演示:
//服務器端:
<?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) { echo "socket_create() 失敗的原因是:".socket_strerror($sock)."\n"; } if(($ret = socket_bind($sock,$ip,$port)) < 0) { 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) { 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; }; } socket_close($msgsock); } while (true); socket_close($sock); ?>
//客戶端
<?php error_reporting(E_ALL); set_time_limit(0); $ip = "127.0.0.1"; $port = 1935; /* +------------------------------- * @socket連接整個過程 +------------------------------- * @socket_create * @socket_connect * @socket_write * @socket_read * @socket_close +-------------------------------- */ $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 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"; ?>
注意:1)服務器端要用CLI方式運行,也就是命令行模式運行。不要用CGI方式(浏覽器訪問);找到php.exe所在目錄,然後運行以下代碼,php 服務器文件;
2)運行之後可以另開dos窗口下運行netstat -ano查看端口占用情況。
5、把socket封裝成類:
//ServerSocket.class.php
//客戶端socket操作類
<?php error_reporting(E_ALL); set_time_limit(0); class ServerSocket { private $server_host; //服務器IP private $server_port; //服務器端口 private $client_host; //客戶端IP private $client_port; //客戶端端口 private $create_socket=null; private $accept_socket=null; private $get_data=""; private $send_data=""; //夠造函數 public function __construct($host,$port){ if(!extension_loaded("socket")){ exit("請先打開socket擴展!"); } if(empty($host)) exit("請輸入目標主機IP!"); if(empty($port)) exit("請輸入有效端口號!"); $this->server_host=$host; $this->server_port=$port; $this->CreateSocket(); } //創建一個socket並將其用來綁定監聽端口 private function createSocket(){ if($this->create_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)==false){ echo "socket_create() failed. reason:".socket_strerror(socket_last_error())."\n"; } if(socket_bind($this->create_socket,$this->server_host,$this->server_port)==false){ echo "socket_bind()failed. reason:".socket_strerror(socket_last_error($this->create_socket))."\n"; } if(socket_listen($this->create_socket,5)==false){ echo "socket_listen()failed. reason:".socket_strerror(socket_last_error($this->create_socket))."\n"; } } //向目標主機發起連接 public function connectClient(){ if(socket_getpeername($this->create_socket,$this->client_host,$this->client_port)==null){ echo "socket_getpeername()failed. reason:".socket_strerror(socket_last_error($this->create_socket))."\n"; } if(socket_connect($this->create_socket,$this->client_host,$this->client_port)==false){ echo "socket_connect()failed. reason:".socket_strerror(socket_last_error($this->create_socket))."\n"; } } //接受連接獲取到一個socket資源,想客戶端讀取以及傳輸信息 public function wr(){ do{ //循環防止阻塞延遲 if($this->accept_socket=socket_accept($this->create_socket)==null){ echo "socket_accept()failed. reason:".socket_strerror(socket_last_error($this->create_socket))."\n"; break; } $this->get_data = socket_read($this->accept_socket, 8192); $this->send_data=$this->operateData($this->get_data); if (socket_write($this->accept_socket,$this->send_data, strlen($this->send_data))==false) { echo "socket_write() failed reason:" . socket_strerror(socket_last_error($this->accept_socket)) ."\n"; } socket_close($this->accept_socket); }while(true); } //數據處理 private function operateData(){ return ; } //關閉監聽socket private function closeSocket(){ socket_close($this->createSocket); } //析構函數 public function __destruct(){ $this->closeSocket(); } }
//ClientSocket.class.php //客戶端socke操作類 class Socket { private $host;//連接socket的主機 private $port;//socket的端口號 private $error=array(); private $socket=null;//socket的連接標識 private $queryStr="";//發送的數據 public function __construct($host,$port) { if(!extension_loaded("sockets")){ exit("請打開socket擴展 "); } if(empty($host)) exit("請輸入目標地址"); if(empty($port)) exit("請輸入有效的端口號"); $this->host=$host; $this->port=$port; $this->CreateSocket();//創建連接 } //創建socket private function CreateSocket(){ !$this->socket&&$this->socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);//創建socket $r=@socket_connect($this->socket,$this->host,$this->port); if($r){ return $r; }else{ $this->error[]=socket_last_error($this->socket); return false; } } //向socket服務器寫入數據並讀取 public function wr($contents){ $this->queryStr=""; $this->queryStr=$contents; !$this->socket&&$this->CreateSocket(); $contents=$this->fliterSendData($contents); $result=socket_write($this->socket,$contents,strlen($contents)); if(!intval($result)){ $this->error[]=socket_last_error($this->socket); return false; } $response=socket_read($this->socket,12048); if(false===$response){ $this->error[]=socket_last_error($this->socket); return false; } return $response; } //對發送的數據進行過濾 private function fliterSendData($contents){ //對寫入的數據進行處理 return $contents; } //所有錯誤信息 public function getError(){ return $this->error; } //最後一次錯誤信息 public function getLastError(){ return $this->error(count($this->error)); } //獲取最後一次發送的消息 public function getLastMsg(){ return $this->queryStr; } public function getHost(){ return $this->host; } public function getPort(){ return $this->port; } //關閉socket連接 private function close(){ $this->socket&&socket_close($this->socket);//關閉連接 $this->socket=null;//連接資源初始化 } public function __destruct(){ $this->close(); } }