它是一个复杂的协议族,但是经过层层封装之后转换为网络数据帧经过网卡发送出去的,当然在发送之前会先发起一次ARP请求查询一下对方的mac物理地址,对方响应后返回以便封装数据传送,一般来说网卡的mac地址有的是写入EEPROM寄存器里存储起来的。 但是它底层网卡驱动要动的事情,那么我们码农只关注一下传输层的TCP/UDP即可,TCP传输层拥有自己的接收与发送缓冲区,而UDP并没有,每次发送数据时,接收端必须立即接受,否则丢包。TCP的发送端与接收端读写次数并不一定相等,这就是字节流的概念,而UDP则是数据报提供不可靠传输。
socket_write ( resource $socket , string $buffer [, int $length = 0 ] ) : int
socket_send ( resource $socket , string $buf , int $len , int $flags ) : int
socket_sendmsg ( resource $socket , array $message [, int $flags = 0 ] ) : int
socket_sendto( resource $socket , string $buf , int $len , int $flags , string $addr [, int $port = 0 ] ) : int
socket_read ( resource $socket , int $length [, int $type = PHP_BINARY_READ ] ) : string
socket_recv ( resource $socket , string &$buf , int $len , int $flags ) : int
socket_recvfrom( resource $socket , string &$buf , int $len , int $flags, string &$name [, int &$port ] ) : int
socket_recvmsg ( resource $socket , array &$message [, int $flags = 0 ] ) : int
$ip = "0.0.0.0";
$port = $argv[1];
$sockefd = socket_create(AF_INET,SOCK_STREAM,0);
socket_set_option($sockefd,SOL_SOCKET,SO_REUSEPORT,1);
socket_bind($sockefd,$ip,$port);
socket_listen($sockefd,5);
while (1){
$connfd = socket_accept($sockefd);
$message = "php is the best language in the world!";
$remoteIp;
$remoteAddr;
//获取socket 文件描述符绑定的端口和地址
//网卡接收数据时执行的中断函数会根据端口找到对应的文件描符并写入其缓冲区
//一般带有缓冲的称为IO流【用户空间】
socket_getpeername($connfd,$remoteIp,$remoteAddr);
echo "有客户端连接,ip为:".$remoteIp.",端口为:".$remoteAddr.PHP_EOL;
if ($connfd){
socket_send($connfd,$message,strlen($message),0);
while (1){
$recvMessage = "";
$recvBytes = socket_recv($connfd,$recvMessage,8192,0);
if ($recvBytes){
$sendBytes = socket_sendto($connfd,$recvMessage,$recvBytes,0,$remoteIp,$remoteAddr);
fprintf(STDOUT,"发送了%d 字节\n",$sendBytes);
}
}
}
}
socket_close($sockefd);
socket_close($connfd);
UDP SEVER 端
$ip = "0.0.0.0";
$port = $argv[1];
$sockefd = socket_create(AF_INET,SOCK_DGRAM,SOL_UDP);
socket_set_option($sockefd,SOL_SOCKET,SO_REUSEPORT,1);
socket_bind($sockefd,$ip,$port);
function resetClient()
{
//此结构PHP.net手册并没有细说,而是本人通过调试低层内核代码获取以及参考unix API
//大家可以自行参考
return [
'name' =>[],//socket 套接字地址 【有通用地址和专用地址】 协议不同 此参数内容结构不同,后面扯
'control' =>[],//
'iov' => [],//数据 可参考 struct iovec
'flags'=> 0,//接收消息标志位 给0正常就行
'controllen'=>8192//辅助数据的地址
];
}
while(1){
$client=resetClient();
//在此阻塞SLEEPING
socket_recvmsg($sockefd,$client,0);
print_r($client);
if (isset($client['name']['addr'])){
socket_sendto($sockefd,"world",5,0,$client['name']['addr'],$client['name']['port']);
}
sleep(2);
}
socket_close($sockefd);
UPD CLIENT端
$ip = "127.0.0.1";
$port = $argv[1];
$socketfd = socket_create(AF_INET,SOCK_DGRAM,SOL_UDP);
$msgStruct = [
'name' =>[
'addr'=>$ip,
'port'=>$port
],
'control' =>[],
'iov' => ["hello,world"],
'flags'=> 0
];
while (1){
//阻塞读取终端的输入
$data = fread(STDIN,8192);
if ($data){
$recvMsg="";$remoteIp;$remoteAddr;
socket_sendto($socketfd,$data,strlen($data),0,$ip,$port);
socket_recvfrom($socketfd,$recvMsg,8192,0,$remoteIp,$remoteAddr);
fprintf(STDOUT,"ip:%s,port:%d,msg:%s\n",$remoteIp,$remoteAddr,$recvMsg);
}
}
socket_close($socketfd);
启动后netstat 观察
大家应该看到比较明显的地方是State它并没有显示什么LISTEN
PS:是每次客户端连接端口是系统随机分配的 UDP通信并不像tcp那样连接的操作,而是大家向socket【绑架了ip和端口的文件描述符】进行读写操作完成了所谓的通信。 它的通信只要指定ip,port和数据即可通信,是面向报文并非连接。