一. Socket介绍
socket工作原理如下:
比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是 把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来 保存你Copy过来的数据的话...这时候就体现出阻塞和非阻塞的不同之处了:对于阻塞模式的socket send函数将不返回直到系统缓冲区有足够的空间把你要发送的数据Copy过去以后才返回,而对于非阻塞的socket来说send会立即返回 WSAEWOULDDBLOCK告诉调用者说:"发送操作被阻塞了!!!你想办法处理吧..."
对于recv函数,同样道理,该函数的内部工 作机制其实是在等待TCP/IP协议栈的接收缓冲区通知它说:嗨,你的数据来了.对于阻塞模式的socket来说如果TCP/IP协议栈的接收缓冲区没有 通知一个结果给它它就一直不返回:耗费着系统资源....对于非阻塞模式的socket该函数会马上返回,然后告诉 你:WSAEWOULDDBLOCK---"现在没有数据,回头在来看看"
php Socket分类如下:
A. 集成于内核的socket
这个系列函数仅仅只能做主动连接无法实现端口监听相关的功能。
此系列函数包括
fsockopen,pfsockopen
他们均会返回一个资源编号对于这个资源可以使用几乎所有对文件操作函数对其进行操作,如gets(),fwrite(),fclose()等。但注意的是所有函数遵循这些函数面对网络信息流时的规律,
例如:fread()从文件指针handle读取最多length个字节。该函数在读取完length个字节数,或到达EOF的时候,或(对于网络流)当一个包可用时就会停止读取文件,视为先碰到哪种情况。
可以看出对于网络流就必须注意渠道的是一个完整的包就停止。
B. php扩展模块带有的socket功能
此模块具有强大的socket功能,包括listen端口,阻塞以及非阻塞模式的切换,multiclient交互式处理等
三、Socket阻塞、非阻塞模式
Windows套接字在阻塞和非阻塞两种模式下执行I/O操作。在阻塞模式下,在I/O操作完成前,执行的操作函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。
在阻塞模式的套接字上,调用任何一个Windows Sockets API都会耗费不确定的等待时间。图所示,在调用recv()函数时,发生在内核中等待数据和复制数据的过程。
当 调用recv()函数时,系统首先查是否有准备好的数据。如果数据没有准备好,那么系统就处于等待状态。当数据准备好后,将数据从系统缓冲区复制到用户空 间,然后该函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数就会处于等待状态。
Windows套接字程序使用“生产者-消费者”模式来解决上述问题。在程序中,“生产者”读入数据,“消费者”根据需求对读入数据进行处理。通常“生产 者”和“消费者”存在于两个线程中,当“生产者”完成读入数据时,使用线程同步机制,例如设置一个事件通知“消费者”,“消费者”接收到这个事件后对读入 的数据进行处理。
当使用socket()函数和WSASocket()函数创建套接字时,默认的套接字都是阻塞的。这意味着当调用Windows Sockets API不能立即完成时,线程处于等待状态,直到操作完成。
并不是所有Windows Sockets API以阻塞套接字为参数调用都会发生阻塞。例如,以阻塞模式的套接字为参数调用bind()、listen()函数时,函数会立即返回。将可能阻塞套接字的Windows Sockets API调用分为以下四种:
1.输入操作
recv()、recvfrom()、WSARecv()和WSARecvfrom()函数。以阻塞套接字为参数调用该函数接收数据。如果此时套接字缓冲区内没有数据可读,则调用线程在数据到来前一直睡眠。
2.输出操作
send()、sendto()、WSASend()和WSASendto()函数。以阻塞套接字为参数调用该函数发送数据。如果套接字缓冲区没有可用空间,线程会一直睡眠,直到有空间。
3.接受连接
accept()和WSAAcept()函数。以阻塞套接字为参数调用该函数,等待接受对方的连接请求。如果此时没有连接请求,线程就会进入睡眠状态。
4.外出连接
connect()和WSAConnect()函数。对于TCP连接,客户端以阻塞套接字为参数,调用该函数向服务器发起连接。该函数在收到服务器的应答前,不会返回。这意味着TCP连接总会等待至少到服务器的一次往返时间。
使用阻塞模式的套接字,开发网络程序比较简单,容易实现。当希望能够立即发送和接收数据,且处理的套接字数量比较少的情况下,使用阻塞模式来开发网络程序比较合适。
阻 塞模式套接字的不足表现为,在大量建立好的套接字线程之间进行通信时比较困难。当使用“生产者-消费者”模型开发网络程序时,为每个套接字都分别分配一个 读线程、一个处理数据线程和一个用于同步的事件,那么这样无疑加大系统的开销。其最大的缺点是当希望同时处理大量套接字时,将无从下手,其扩展性很差。
非阻塞模式
把套接字设置为非阻塞模式,即通知系统内核:在调用Windows Sockets API时,不要让线程睡眠,而应该让函数立即返回。在返回时,该函数返回一个错误代码。图所示,一个非阻塞模式套接字多次调用recv()函数的过程。前 三次调用recv()函数时,内核数据还没有准备好。因此,该函数立即返回WSAEWOULDBLOCK错误代码。第四次调用recv()函数时,数据已 经准备好,被复制到应用程序的缓冲区中,recv()函数返回成功指示,应用程序开始处理数据。
当使用socket()函数和WSASocket()函数创建套接字时,默认都是阻塞的。在创建套接字之后,通过调用ioctlsocket()函数,将该套接字设置为非阻塞模式。Linux下的函数是:fcntl().
套接字设置为非阻塞模式后,在调用Windows Sockets API函数时,调用函数会立即返回。大多数情况下,这些函数调用都会调用“失败”,并返回WSAEWOULDBLOCK错误代码。说明请求的操作在调用期 间内没有时间完成。通常,应用程序需要重复调用该函数,直到获得成功返回代码。
需要说明的是并非所有的Windows Sockets API在非阻塞模式下调用,都会返回WSAEWOULDBLOCK错误。例如,以非阻塞模式的套接字为参数调用bind()函数时,就不会返回该错误代 码。当然,在调用WSAStartup()函数时更不会返回该错误代码,因为该函数是应用程序第一调用的函数,当然不会返回这样的错误代码。
要将套接字设置为非阻塞模式,除了使用ioctlsocket()函数之外,还可以使用WSAAsyncselect()和WSAEventselect()函数。当调用该函数时,套接字会自动地设置为非阻塞方式。
由于使用非阻塞套接字在调用函数时,会经常返回WSAEWOULDBLOCK错误。所以在任何时候,都应仔细检查返回代码并作好对“失败”的准备。应用 程序连续不断地调用这个函数,直到它返回成功指示为止。上面的程序清单中,在While循环体内不断地调用recv()函数,以读入1024个字节的数 据。这种做法很浪费系统资源。
要完成这样的操作,有人使用MSG_PEEK标志调用recv()函数查看缓冲区中是否有数据可读。同样,这种方法也不好。因为该做法对系统造成的开销是 很大的,并且应用程序至少要调用recv()函数两次,才能实际地读入数据。较好的做法是,使用套接字的“I/O模型”来判断非阻塞套接字是否可读可写。
非阻塞模式套接字与阻塞模式套接字相比,不容易使用。使用非阻塞模式套接字,需要编写更多的代码,以便在每个Windows Sockets API函数调用中,对收到的WSAEWOULDBLOCK错误进行处理。因此,非阻塞套接字便显得有些难于使用。
但是,非阻塞套接字在控制建立的多个连接,在数据的收发量不均,时间不定时,明显具有优势。这种套接字在使用上存在一定难度,但只要排除了这些困难,它在 功能上还是非常强大的。通常情况下,可考虑使用套接字的“I/O模型”,它有助于应用程序通过异步方式,同时对一个或多个套接字的通信加以管理。
二. 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的数据结构
说明:创建并返回一个套接字资源,也称为一个端点的通信。一个典型的网络连接是由2套接字,一个执行客户机的角色,和另一个执行服务器的角色 共3个参数
域(第一个参数)
域参数指定了协议的家庭使用的套接字。 可用地址/协议家庭域的描述
AF_INET IPv4互联网协议为基础。TCP和UDP协议,这个协议是常见的家庭。 AF_INET6 IPv6 Internet协议进行。 TCP和UDP协议,这个协议是常见的家庭。
AF_UNIX 本地通信协议的AF_INET家庭。效率高、低开销使它成为一个伟大的形式的IPC(进程间通信)。
Type是基于这个套接字类型。(第二个参数)
SOCK_DGRAM 支持数据报(无连接,不可靠的消息的一个固定的最大长度)。UDP协议是基于这个套接字类型。
SOCK_SEQPACKET 提供了一个排序、可靠、双向数据传输路径数据报它固定最大长度;一个消费者需要读取整个包与每个读取调用。
SOCK_RAW 提供了原始网络协议访问。这种特殊类型的套接字可以用来手动构建任何类型的协议。一个常见的使用对于这个套接字类型是执行ICMP请求(比如ping)。
SOCK_RDM 提供了一个可靠的数据报层,并不能保证订货。这是最有可能不是您的操作系统上实现。
Protocol(协议)
说明:协议参数设置特定的协议在指定域用于交流时返回的套接字。适当的值可以通过名称检索利用getprotobyname()。如果所需的协议是TCP或UDP相应的常量SOL_TCP和SOL_UDP也可以使用。 共同协议名称描述(Common protocols)
icmp 网际控制报文协议主要用于通过网关和主机来报告错误在数据报通信。“ping”命令(目前在大多数现代操作系统)是一个示例应用程序的ICMP。
udp 的用户数据报协议是一种无连接、不可靠、协议和固定长度的记录。由于这些方面,UDP需要最少的协议开销。
tcp 传输控制协议是一个可靠的、基于连接,流取向,全双工协议。TCP保证所有数据包将收到的顺序与它们的发送。如果任何包似乎失去了在交流中,TCP会自动重新发送数据包到目的地主机承认包。对可靠性和性能的原因,TCP实现本身决定适当的八位字节边界的底层数据报通信层。因此,TCP应用程序必须允许部分记录传输的可能性。
socket_get_option 获取socket选项
socket_getpeername 获取远程类似主机的ip地址
socket_getsockname 获取本地socket的ip地址
socket_last_error 获取当前socket的最后错误代码
socket_listen 监听由指定的socket的所有连接
两个参数:socket资源 最大连接数
socket_read 读取指定长度的数据
共3个参数:socket资源,长度,类型(可选)
可选的类型参数是一个名叫常数:
■php二进制读(默认值)—使用系统recv()函数。阅读二进制数据的安全。
■php正常阅读——阅读停在\ n或\ r。
socket_recv 读取从分散/聚合数组过来的数据
socket_recvfrom 接收数据从指定的socket,如果没有指定则默认当前socket
socket_select 多路选择
socket_send 发送数据到已连接的socket
socket_sendto 发送数据到指定的socket
socket_set_block 在socket里设置为块模块
socket_set_nonblock 在socket里设置为非块模块
socket_set_option 设置socket选项
socket_shutdown 这个函数允许你关闭读、写、或者指定的socket
socket_strerror 返回指定错误好的详细错误
s
ocket_write 写数据到socket缓存
socket_writev 写数据到分散/聚合数组
socket_iovec_add 添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc 这个函数创建一个能够发送数据接收读写的iovec数据结构
socket_iovec_delete 删除一个已经分配的iovec
socket_iovec_fetch 返回指定的iovec资源的数据
socket_iovec_free 释放一个iovec的资源
socket_iovec_set 设置iovec的数据新值
三、Socket编程步骤
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);
TCP/UDP编程步骤:
一、基于TCP的socket编程,采用的是流式套接字
服务器端编程的步骤:
1:加载套接字库,创建套接字(socket());
2:绑定套接字到一个IP地址和一个端口上(bind());
3:将套接字设置为监听模式等待连接请求(listen());
4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
5:用返回的套接字和客户端进行通信(read()/write());
6:返回,等待另一连接请求;
7:关闭套接字,关闭加载的套接字库(closesocket())。
客户端编程的步骤:
1:加载套接字库,创建套接字(socket());
2:向服务器发出连接请求(connect());
3:和服务器端进行通信(read()/write());
4:关闭套接字,关闭加载的套接字库(closesocket())。
二.基于UDP的socket编程(无所谓客户端服务器端,服务器端充当客户端客户端也可以充当服务器端)
服务器端编程的步骤:
1:加载套接字库,创建套接字(socket());
2:绑定套接字到一个IP地址和一个端口上(bind());
3:等待和接收数据(sendto()/recvfrom());
4:关闭套接字,关闭加载的套接字库(closesocket())。
客户端编程的步骤:
1:创建一个套接字(socket);
2:向服务器发送数据(sendto);
3:关闭套接字;
参考文献: