代码实现
服务端:socket_server.php
<?php
//创建服务端的socket套接流,net协议为IPv4,protocol协议为TCP
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//绑定接收的套接流主机和端口,与客户端相对应
if(socket_bind($socket,'127.0.0.1',8888) == false){
echo 'server bind fail:'.socket_strerror(socket_last_error());
}
//监听套接流
if(socket_listen($socket,4)==false){
echo 'server listen fail:'.socket_strerror(socket_last_error());
}
//让服务器无限监听获取客户端传过来的信息
do{
//socket_accept的作用就是接受socket_bind()所绑定的主机发过来的套接流
$accept_resource = socket_accept($socket);
if($accept_resource !== false){
//读取客户端传过来的资源,并转化为字符串
//socket_read的作用就是读出socket_accept()的资源并把它转化为字符串
$string = socket_read($accept_resource,1024);
echo 'server receive is :'.$string.PHP_EOL;
if($string != false){
$return_client = $string. ' too' . PHP_EOL;
//向socket_accept的套接流写入信息,也就是回馈信息给socket_bind()所绑定的主机客户端
//socket_write的作用是向socket_create的套接流写入信息,或者向socket_accept的套接流写入信息
socket_write($accept_resource,$return_client,strlen($return_client));
}else{
echo 'socket_read is fail';
}
//socket_close的作用是关闭socket_create()或者socket_accept()所建立的套接流
socket_close($accept_resource);
}
}while(true);
socket_close($socket);
客户端:socket_client.php
<?php
//创建一个socket套接流
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//接收套接流的最大超时时间1秒,后面是微秒单位超时时间,设置为零,表示不管它
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 1, "usec" => 0));
//发送套接流的最大超时时间为6秒
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 6, "usec" => 0));
//连接服务端的套接流,这一步就是使客户端与服务器端的套接流建立联系
if(socket_connect($socket,'127.0.0.1',8888) == false){
echo 'connect fail massege:'.socket_strerror(socket_last_error());
}else{
$message = 'l love you socket';
//转为GBK编码,处理乱码问题,这要看你的编码情况而定,每个人的编码都不同
$message = mb_convert_encoding($message,'GBK','UTF-8');
//向服务端写入字符串信息
if(socket_write($socket,$message,strlen($message)) == false){
echo 'fail to write'.socket_strerror(socket_last_error());
}else{
echo 'client write success'.PHP_EOL;
echo 'send message is:' . $message . PHP_EOL;
//读取服务端返回来的套接流信息
while($return_message = socket_read($socket,1024)){
echo 'server return message is:' . $return_message . PHP_EOL;
}
}
}
socket_close($socket);//工作完毕,关闭套接流
服务端:
[root@www html]# php socket_server.php
server receive is :l love you socket
客户端:
localhost:php why$ php socket_client.php
client write success
send message is:l love you socket
server return message is:l love you socket too
localhost:php why$
问题
上面的代码都运行在本地,任何问题都没有,但是如果服务端和客户端是在不同的主机上,会出现如下问题:
PHP Warning: socket_connect(): unable to connect [61]: Connection refused in /Users/why/Desktop/php/socket_client.php on line 11
connect fail massege:Connection refused
很明显,是server端拒绝连接,猜测是服务端防火墙问题,应该是没有开放8888端口
[root@www ~]# firewall-cmd --zone=public --list-ports
80/tcp
通过firewall-cmd命令可以看到只开放了80端口,所以第一步就是开放8888端口
firewall-cmd --zone=public --add-port=8888/tcp --permanent
success
此时如果重新开启防火墙,那么所有的主机都可以访问本机的8888端口了,而假如我们当前端口提供的服务仅允许某台机器使用,所以需要设置仅允许指定的机器连接
[root@www ~]# firewall-cmd --zone=public --remove-port=8888/tcp --permanent
success
[root@www ~]# firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="10.211.55.2" port protocol="tcp" port="8888" accept"
success
[root@www ~]# firewall-cmd --reload
success
[root@www ~]#
此时再运行客户端机器上的脚本发现运行成功:
localhost:php why$ php socket_client.php
client write success
send message is:l love you socket
server return message is:l love you socket too
localhost:php why$
同时通过tcpdump抓包查看数据:
[root@www ~]# tcpdump tcp port 8888
21:40:52.910291 IP localhost.61008 > www.weihaoyu.com.ddi-tcp-1: Flags [SEW], seq 1721959505, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1131017848 ecr 0,sackOK,eol], length 0
21:40:52.910364 IP www.weihaoyu.com.ddi-tcp-1 > localhost.61008: Flags [S.E], seq 893885278, ack 1721959506, win 28960, options [mss 1460,sackOK,TS val 348747313 ecr 1131017848,nop,wscale 7], length 0
21:40:52.910489 IP localhost.61008 > www.weihaoyu.com.ddi-tcp-1: Flags [.], ack 1, win 2058, options [nop,nop,TS val 1131017848 ecr 348747313], length 0
21:40:52.910542 IP localhost.61008 > www.weihaoyu.com.ddi-tcp-1: Flags [P.], seq 1:18, ack 1, win 2058, options [nop,nop,TS val 1131017848 ecr 348747313], length 17
21:40:52.910567 IP www.weihaoyu.com.ddi-tcp-1 > localhost.61008: Flags [.], ack 18, win 227, options [nop,nop,TS val 348747313ecr 1131017848], length 0
21:40:52.910654 IP www.weihaoyu.com.ddi-tcp-1 > localhost.61008: Flags [P.], seq 1:23, ack 18, win 227, options [nop,nop,TS val 348747314 ecr 1131017848], length 22
21:40:52.910678 IP www.weihaoyu.com.ddi-tcp-1 > localhost.61008: Flags [F.], seq 23, ack 18, win 227, options [nop,nop,TS val 348747314 ecr 1131017848], length 0
21:40:52.910957 IP localhost.61008 > www.weihaoyu.com.ddi-tcp-1: Flags [.], ack 23, win 2058, options [nop,nop,TS val 1131017848 ecr 348747314], length 0
21:40:52.910985 IP localhost.61008 > www.weihaoyu.com.ddi-tcp-1: Flags [.], ack 24, win 2058, options [nop,nop,TS val 1131017848 ecr 348747314], length 0
21:40:52.911013 IP localhost.61008 > www.weihaoyu.com.ddi-tcp-1: Flags [F.], seq 18, ack 24, win 2058, options [nop,nop,TS val1131017848 ecr 348747314], length 0
21:40:52.911034 IP www.weihaoyu.com.ddi-tcp-1 > localhost.61008: Flags [.], ack 19, win 227, options [nop,nop,TS val 348747314ecr 1131017848], length 0
可以看到完整的三次握手和四次挥手的过程,其中[SEW]为连接请求报文、[S.E]为确认连接报文、[.]为确认报文、[P.]为数据报文、[F.]为释放报文,从上到下解释如下:
- 客户端seq为1721959505,标志SEW
- 服务端收到请求后同意连接,向客户端发送确认,ack=seq+1=1721959506
- 客户端收到服务端的确认后,向服务端给出去人,确认报文段ack为1
- 客户端向服务端发送数据,长度为17,刚好等于 'I love you socket'的长度
- 服务端回复确认收到数据ack=seq=18
- 服务端向客户端传输数据,长度为22(这里我认为应该刚好等于'I love you socket too'的长度,但是长度却多了1,这里不太明白,评论区请教大佬)
- 服务器发送断开连接请求,停止发送数据,初始化seq为23
- 客户端回复确认收到数据(dump打印顺序问题),发送确认ack=seq=23
- 客户端收到断开连接请求,向服务端发送确认,ack=seq+1=24(这个步骤客户端仍然处于timewait状态,等待2个时间周期,服务端在这两个时间周期内如果没收到客户端的确认,会再次发送断开连接的请求报文,直到完整收到确认后才会断开连接,这样做的目的是为了确保服务端关闭连接,避免持续浪费服务端资源)
- 客户端发送断开连接请求,停止发送数据,初始化seq=18
- 服务端收到断开连接请求,向客户端发送确认ack=seq+1=19