您的位置:首页 > 编程语言 > PHP开发

PHP Socket 编程-socket讲解与实例

2016-03-04 09:50 537 查看
socket


 Socket协议的形象描述

  
  
1.一个是发动机(Socket),提供了网络通信的能力 

  
  
  
   
一个是轿车(Http),提供了具体的方式 


 2.socket的英文原义是“孔”或“插座”。在这里作为4BDS
UNIX的进程通信机制,取后一种意义。socket非常类似于电话插座。以一个国家级电话网为例。电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。


 在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的过程,话音传输的过程以及整个电话系统的技术细节对他都是透明的,这也与
socket机制非常相似。socket利用网间网通信设施实现进程通信,但它对通信设施的细节毫不关心,只要通信设施能提供足够的通信能力,它就满足了。


 至此,我们对socket进行了直观的描述。抽象出来,socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。在网间网内部,每一个socket用一个半相关描述 : 


 (协议,本地地址,本地端口)


   
一个完整的socket有一个本地唯一的socket号,由操作系统分配。


 最重要的是,socket
是面向客户 / 服务器模型而设计的,针对客户和服务器程序提供不同的socket
系统调用。客户随机申请一个socket
(相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个socket号;服务器拥有全局公认的 socket
,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。


 socket利用客户 / 服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器socket
半相关为全局所公认非常重要。读者不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的socket
固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。


 ----- 


 Socket 接口是访问
Internet 使用得最广泛的方法。
如果你有一台刚配好TCP / IP协议的主机,其IP地址是202 . 120.127 . 201 ,
此时在另一台主机或同一台主机上执行ftp 202.120 .127.201 ,显然无法建立连接。因 " 202.120.127.201 " 这台主机没有运行FTP服务软件。同样,
在另一台或同一台主机上运行浏览软件 如Netscape,输入 " http://202.120.127.201 " ,也无法建立连接。现在,如果在这台主机上运行一个FTP服务软件(该软件将打开一个Socket,并将其绑定到21端口),再在这台主机上运行一个Web
服务软件(该软件将打开另一个Socket,并将其绑定到80端口)。这样,在另一台主机或同一台主机上执行ftp 202.120 .127.201 ,FTP客户软件将通过21端口来呼叫主机上由FTP
服务软件提供的Socket,与其建立连接并对话。而在netscape中输入 " http://202.120.127.201 " 时,将通过80端口来呼叫主机上由Web服务软件提供的Socket,与其建
立连接并对话。 


 在
Internet上有很多这样的主机,这些主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,象一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电,
有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。


    ----- 


     1 . 什么是socket
所谓socket通常也称作 " 套接字 " ,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过 " 套接字 " 向网络发出请求或者应答网络请求。
以J2SDK - 1 . 3为例,Socket和ServerSocket类库位于java . net
包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是
ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。


   
重要的Socket API:java . net . Socket继承于java . lang . Object ,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK - 1 . 3文档。


   
Accept方法用于产生 " 阻塞 " ,直到接受到一个连接,并且返回一个客户端的Socket对象实例。 " 阻塞 " 是一个术语,它使程序运行暂时 " 停留 " 在这个地方,直到一个会话产生,然后程序继续;通常 " 阻塞 " 是由循环产生的。 


   
getInputStream方法获得网络连接输入,同时返回一个IutputStream对象实例。 


   
getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。 


    2 . 如何开发一个Server - Client模型的程序
开发原理: 


   
服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。 


   
客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。


   
Socket接口是TCP / IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP / IP网络上的应用程序。要学Internet上的TCP / IP网络编程,必须理解Socket接口。
Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I / O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。


    
常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Socket建立为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:int
socket(int domain , int
type , int
protocol);domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP / IP协议族);type参数指定socket的类型:SOCK_STREAM
或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值 " 0 " 。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上 " 建立一个Socket " 意味着为一个Socket数据结构分配存储空间。
Socket执行体为你管理描述符表。两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。
socket在测量软件中的使用也很广泛 


让我们以一个简单的例子开始 --- 一个接收输入字符串 , 处理并返回这个字符串到客户端的TCP服务 . 下面是相应的代码 : 




 PHP
代码 : 




 --------------------------------------------------------------------------------

<?

// 设置一些基本的变量

$host = "192.168.1.99";

$port = 1234;

// 设置超时时间

set_time_limit(0);

// 创建一个Socket

$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not
create

socket\n");

//绑定Socket到端口

$result = socket_bind($socket, $host, $port) or die("Could not bind
to

socket\n");

// 开始监听链接

$result = socket_listen($socket, 3) or die("Could not set up
socket

listener\n");

// accept incoming connections

// 另一个Socket来处理通信

$spawn = socket_accept($socket) or die("Could not accept
incoming

connection\n");

// 获得客户端的输入

$input = socket_read($spawn, 1024) or die("Could not read
input\n");

// 清空输入字符串

$input = trim($input);

//处理客户端输入并返回结果

$output = strrev($input) . "\n";

socket_write($spawn, $output, strlen ($output)) or die("Could not
write

output\n");

// 关闭sockets

socket_close($spawn);

socket_close($socket);

?>

复制代码




 -------------------------------------------------------------------------------- 




 下面是其每一步骤的详细说明 : 




 1 . 第一步是建立两个变量来保存Socket运行的服务器的IP地址和端口 . 你可以设置为你自己的服务器和端口(这个端口可以是1到65535之间的数字) , 前提是这个端口未被使用 . 






 PHP
代码 : 




 -------------------------------------------------------------------------------- 



<?

// 设置两个变量

$host = "192.168.1.99";

$port = 1234;

?>

复制代码

在这一章里你将了解到迷人而又让人容易糊涂的套接字(Sockets)。Sockets在PHP中是没有充分利用的功能。今天你将看到产生一个能使用客户端连接的服务器,并在客户端使用socket进行连接,服务器端将详细的处理信息发送给客户端。

当你看到完整的socket过程,那么你将会在以后的程序开发中使用它。这个服务器是一个能让你连接的HTTP服务器,客户端是一个Web浏览器,这是一个单一的
客户端/服务器 的关系。

◆ Socket 基础

PHP使用Berkley的socket库来创建它的连接。你可以知道socket只不过是一个数据结构。你使用这个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。

Resourece socket_create(int protocol, int socketType, int
commonProtocol);

现在你产生一个socket,然后呢?php提供了几个操纵socket的函数。你能够绑定socket到一个IP,监听一个socket的通信,接受一个socket;现在我们来看一个例子,了解函数是如何产生、接受和监听一个socket。

<?php

$commonProtocol = getprotobyname(“tcp”);

$socket = socket_create(AF_INET, SOCK_STREAM,
$commonProtocol);

socket_bind($socket, ‘localhost’, 1337);

socket_listen($socket);

// More socket functionality to come

?>

 

上面这个例子产生一个你自己的服务器端。例子第一行,

$commonProtocol = getprotobyname(“tcp”);

使用公共协议名字来获取一个协议类型。在这里使用的是TCP公共协议,如果你想使用UDP或者ICMP协议,那么你应该把getprotobyname()函数的参数改为“udp”或“icmp”。还有一个可选的办法是不使用getprotobyname()函数而是指定SOL_TCP或SOL_UDP在socket_create()函数中。

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

例子的第二行是产生一个socket并且返回一个socket资源的实例。在你有了一个socket资源的实例以后,你就必须把socket绑定到一个IP地址和某一个端口上。

socket_bind($socket, ‘localhost’, 1337);

在这里你绑定socket到本地计算机(127.0.0.1)和绑定socket到你的1337端口。然后你就需要监听所有进来的socket连接。

socket_listen($socket);

在第四行以后,你就需要了解所有的socket函数和他们的使用。

表四: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()    写数据到分散/聚合数组

(注: 函数介绍删减了部分原文内容,函数详细使用建议参考英文原文,或者参考PHP手册)

 

以上所有的函数都是PHP中关于socket的,使用这些函数,你必须把你的socket打开,如果你没有打开,请编辑你的php.ini文件,去掉下面这行前面的注释:

extension=php_sockets.dll

如果你无法去掉注释,那么请使用下面的代码来加载扩展库:

<?php 

if(!extension_loaded(‘sockets’)) 



if(strtoupper(substr(PHP_OS, 3)) == “WIN”) 



dl(‘php_sockets.dll’);

}

else

{

dl(‘sockets.so’); 





?>

 

如果你不知道你的socket是否打开,那么你可以使用phpinfo()函数来确定socket是否打开。你通过查看phpinfo信息了解socket是否打开。如下图:

 

查看phpinfo()关于socket的信息

◆ 产生一个服务器

现在我们把第一个例子进行完善。你需要监听一个指定的socket并且处理用户的连接。

<?php

$commonProtocol = getprotobyname("tcp");

$socket = socket_create(AF_INET, SOCK_STREAM,
$commonProtocol);

socket_bind($socket, 'localhost', 1337);

socket_listen($socket);

// Accept any incoming connections to the server

$connection = socket_accept($socket);

if($connection)

{

 socket_write($connection, "You have connected to
the socket...\n\r"); 



?>

你应该使用你的命令提示符来运行这个例子。理由是因为这里将产生一个服务器,而不是一个Web页面。如果你尝试使用Web浏览器来运行这个脚本,那么很有可能它会超过30秒的限时。你可以使用下面的代码来设置一个无限的运行时间,但是还是建议使用命令提示符来运行。

set_time_limit(0);

在你的命令提示符中对这个脚本进行简单测试:

Php.exe example01_server.php

如果你没有在系统的环境变量中设置php解释器的路径,那么你将需要给php.exe指定详细的路径。当你运行这个服务器端的时候,你能够通过远程登陆(telnet)的方式连接到端口1337来测试这个服务器。如下图:

 

上面的服务器端有三个问题:1. 它不能接受多个连接。2. 它只完成唯一的一个命令。3.
你不能通过Web浏览器连接这个服务器。

这个第一个问题比较容易解决,你可以使用一个应用程序去每次都连接到服务器。但是后面的问题是你需要使用一个Web页面去连接这个服务器,这个比较困难。你可以让你的服务器接受连接,然后些数据到客户端(如果它一定要写的话),关闭连接并且等待下一个连接。

在上一个代码的基础上再改进,产生下面的代码来做你的新服务器端:

<?php 

// Set up our socket 

$commonProtocol = getprotobyname("tcp"); 

$socket = socket_create(AF_INET, SOCK_STREAM,
$commonProtocol); 

socket_bind($socket, 'localhost', 1337); 

socket_listen($socket); 

// Initialize the buffer 

$buffer = "NO DATA"; 

while(true) 



 // Accept any connections coming in on this
socket

 $connection = socket_accept($socket);

 printf("Socket connected\r\n");

 // Check to see if there is anything in the
buffer

 if($buffer != "")

 {

  printf("Something is in the
buffer...sending data...\r\n"); 

  socket_write($connection,
$buffer . "\r\n"); 

  printf("Wrote to
socket\r\n");

 }

 else 

 { 

  printf("No Data in the
buffer\r\n");

 }

 // Get the input

 while($data = socket_read($connection, 1024,
PHP_NORMAL_READ))

 {

  $buffer =
$data; 

  socket_write($connection,
"Information Received\r\n"); 

  printf("Buffer: " . $buffer .
"\r\n"); 

 } 

 socket_close($connection); 

 printf("Closed the
socket\r\n\r\n"); 



?>

这个服务器端要做什么呢?它初始化一个socket并且打开一个缓存收发数据。它等待连接,一旦产生一个连接,它将打印“Socket
connected”在服务器端的屏幕上。这个服务器检查缓冲区,如果缓冲区里有数据,它将把数据发送到连接过来的计算机。然后它发送这个数据的接受信息,一旦它接受了信息,就把信息保存到数据里,并且让连接的计算机知道这些信息,最后关闭连接。当连接关闭后,服务器又开始处理下一次连接。(翻译的烂,附上原文)

This is what the server does. It initializes the socket and the
buffer that you use to receive 

and send data. Then it waits for a connection. Once a connection is
created it prints “Socket connected” to the screen the server is
running on. The server then checks to see
if 

there is anything in the buffer; if there is, it sends the data to
the connected computer. 

After it sends the data it waits to receive information. Once it
receives information it stores 

it in the data, lets the connected computer know that it has
received the information, and 

then closes the connection. After the connection is closed, the
server starts the whole 

process again.

 

◆ 产生一个客户端

处理第二个问题是很容易的。你需要产生一个php页连接一个socket,发送一些数据进它的缓存并处理它。然后你又个处理后的数据在还顿,你能够发送你的数据到服务器。在另外一台客户端连接,它将处理那些数据。

To solve the second problem is very easy. You need to create a PHP
page that connects to 

a socket, receive any data that is in the buffer, and process it.
After you have processed the 

data in the buffer you can send your data to the server. When
another client connects, it 

will process the data you sent and the client will send more data
back to the server.

下面的例子示范了使用socket:

<?php 

// Create the socket and connect 

$socket = socket_create(AF_INET, SOCK_STREAM,
SOL_TCP); 

$connection = socket_connect($socket,’localhost’,
1337); 

while($buffer = socket_read($socket, 1024,
PHP_NORMAL_READ)) 



 if($buffer == “NO DATA”) 

 { 

 echo(“<p>NO
DATA</p>”); 

 break;

 }

 else

 {

  // Do something with the data
in the buffer 

  echo(“<p>Buffer
Data: “ . $buffer .
“</p>”); 

 } 



echo(“<p>Writing to
Socket</p>”);

// Write some test data to our socket

if(!socket_write($socket, “SOME DATA\r\n”))

{

 echo(“<p>Write
failed</p>”); 



// Read any response from the socket

while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ))

{

 echo(“<p>Data sent
was: SOME DATA<br> Response was:” .
$buffer .
“</p>”); 



echo(“<p>Done Reading from
Socket</p>”); 

?>

这个例子的代码演示了客户端连接到服务器。客户端读取数据。如果这是第一时间到达这个循环的首次连接,这个服务器将发送“NO
DATA”返回给客户端。如果情况发生了,这个客户端在连接之上。客户端发送它的数据到服务器,数据发送给服务器,客户端等待响应。一旦接受到响应,那么它将把响应写到屏幕上。

结合Socket的坦克大战

(因为是描述游戏和socket结合,跟本文联系不大,所以不翻译,建议参考英文原文)

 

[ 题外话 ]

翻译文章的初衷是因为我个人对socket非常感兴趣,而且目前国内见php的文章比较少,除了php手册里面的部分内容,所以在我看了《PHP
Game Programming》这本书里有关于socket的内容后毅然决定要翻译,我知道翻译出来的质量不行,还请见谅。

另外,我在《Core PHP Programming》Third
Edition中也发现里面的Socket内容讲的不错,如果有空,我想也许我会把它也给翻译一下。这是我第一次翻译文章,花了我近五个小时,文章可以说是错误百出,如果翻译的不合理请见谅,如果有兴趣提高这个内容可以给我发邮件。这个凌晨时分,竟然无法入眠,不知道是不是在其他角落,也有人同我一样。

希望本文能够给向学习PHP Socket编程的朋友一点帮助,感谢你阅读这个错误百出的文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: