PHP Socket 编程过程详解

澳门新葡亰赌995577 21

使用代码

目的:开发一个客户端用于发送string消息到服务端,服务端将相同的信息反转后返回给客户端。

PHP服务器

第1步:设置变量,如“主机”和“端口”

$host = "127.0.0.1";
$port = 5353;
// No Timeout 
set_time_limit(0);

端口号可以是1024 -65535之间的任何正整数。

第2步:创建socket

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

第3步:绑定socket到端口和主机

创建的socket资源绑定到IP地址和端口号。

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

第4步:启动socket监听

在绑定到IP和端口后,服务端开始等待客户端的连接。在没有连接之前它就一直等下去。

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

第5步:接受连接

这个函数会接受所建的socket传入的连接请求。在接受来自客户端socket的连接后,该函数返回另一个socket资源,实际上就是负责与相应的客户端socket通信。这里的“$spawn”就是负责与客户端socket通信的socket资源。

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

到现在为止,我们已经准备好了服务端socket ,但实际上这个脚本并没有做任何事情。所以为了继续完成上述目标,我们将读取客户端socket消息,然后将接收到的消息反转后发回给客户端socket。

第6步:从客户端socket读取消息

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

第7步:反转消息

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

第8步:发送消息给客户端socket

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

关闭socket

socket_close($spawn);
socket_close($socket);

这就完成了服务端。现在,我们学习如何创建PHP客户端。

PHP客户端

前两个步骤与服务端相同。

第1步:设置变量,如“主机”和“端口”

$host = "127.0.0.1";
$port = 5353;
// No Timeout 
set_time_limit(0);

注:这里的端口和主机应该和服务端中的定义是相同的。

第2步:创建socket

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

第3步:连接到服务端

$result = socket_connect($socket, $host, $port) or die("Could not connect toservern");

此时和服务端不同,客户端socket不绑定端口和主机。相反,它连接到服务端socket,等待接受来自客户端socket的连接。这一步建立了客户端socket到服务端socket的连接。

第4步:写入服务端socket

socket_write($socket, $message, strlen($message)) or die("Could not send data to servern");

在此步骤中,客户端socket的数据被发送到服务端socket。

第5步:阅读来自服务端的响应

$result = socket_read ($socket, 1024) or die("Could not read server responsen");
echo "Reply From Server  :".$result;

第6步:关闭socket

socket_close($socket);

完整的代码

服务端(server.php)

// set some variables
$host = "127.0.0.1";
$port = 25003;
// don't timeout!
set_time_limit(0);
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socketn");
// bind socket to port
$result = socket_bind($socket, $host, $port) or die("Could not bind to socketn");
// start listening for connections
$result = socket_listen($socket, 3) or die("Could not set up socket listenern");

// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket) or die("Could not accept incoming connectionn");
// read client input
$input = socket_read($spawn, 1024) or die("Could not read inputn");
// clean up input string
$input = trim($input);
echo "Client Message : ".$input;
// reverse client input and send back
$output = strrev($input) . "n";
socket_write($spawn, $output, strlen ($output)) or die("Could not write outputn");
// close sockets
socket_close($spawn);
socket_close($socket);

澳门新葡亰赌995577,客户端(client.php)

$host    = "127.0.0.1";
$port    = 25003;
$message = "Hello Server";
echo "Message To server :".$message;
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socketn");
// connect to server
$result = socket_connect($socket, $host, $port) or die("Could not connect to servern");  
// send string to server
socket_write($socket, $message, strlen($message)) or die("Could not send data to servern");
// get server response
$result = socket_read ($socket, 1024) or die("Could not read server responsen");
echo "Reply From Server  :".$result;
// close socket
socket_close($socket);

建立上述文件(server.php和client.php)后,执行如下操作:

  1. 复制www目录中的这些文件(假设WAMP),安置于C:wamp。
  2. 打开Web浏览器,在地址栏中键入localhost 。
  3. 先浏览server.php然后client.php。

如果我们在网络调试助手中一直不发送,pycharm里的程序将一直处于阻塞状态等待接收信息。
多次运行脚本,然后在“网络调试助手”中,看到的现象如下:

本文由码农网 –
小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

使用wireshark抓包工具,使用TFTP协议。
TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
特点:
1、简单
2、占用资源小
3、适合传递小文件
4、适合在局域网进行传递
5、端口号为69
6、基于UDP实现
TFTP下载过程
TFTP服务器默认监听69号端口
当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送
服务器若批准此请求,则使用一个新的、临时的 端口进行数据传输。

介绍

Socket用于进程间通信。进程间通信通常基于客户端—服务端模型。此时,客户端—服务端是可以彼此交互的应用程序。客户端和服务端之间的交互需要连接。Socket编程负责的就是为应用程序之间建立可进行交互的连接。

在本文中,我们将学习如何用PHP创建一个简单的客户端—服务端。我们还将学习如何客户端应用程序如何发送消息到服务端,以及如何从服务端接受消息。

澳门新葡亰赌995577 1

有了之前的upd知识,接下来,模拟从TFTP上下载文件。

许可证

这篇文章,以及任何相关的源代码和文件,是经过The Code Project Open
License (CPOL)许可的。

每重新运行一次网络程序,上图中画红线的数字,不一样的原因在于,这个数字标识这个网络程序,当重新运行时,如果没有确定到底用哪个,系统默认会随机分配。记住一点:这个网络程序在运行的过程中,这个就唯一标识这个程序,所以如果其他电脑上的网络程序如果想要向此程序发送数据,那么就需要向这个数字(即端口)标识的程序发送即可。
刚才接收消息是先发送,根据收到的消息得到端口号再发送回去完成了接收,那我们也可以直接绑定端口号,解决端口号随机分配的问题。

澳门新葡亰赌995577 2

澳门新葡亰赌995577 3

使用网络的目的就是为了联通多方然后进行通信用的,即把数据从一方传递给另外一方。前面的学习编写的程序都是单机的,即不能和其他电脑上的程序进行通信,为了让在不同的电脑上运行的软件,之间能够互相传递数据,就需要借助网络的功能。
计算机都遵守的网络通信协议叫做TCP/IP协议。
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个IP包除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个IP包来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。在linux系统中,端口可以有65536(2的16次方)个之多!既然有这么多,操作系统为了统一管理,所以进行了编号,这就是端口号。端口是通过端口号来标记的,端口号只有整数,范围是从0到65535。端口号不是随意使用的,而是按照一定的规定进行分配。动态端口的范围是从1024到65535。之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。动态分配是指当一个系统进程或应用程序进程需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。当这个进程关闭时,同时也就释放了所占用的端口号。用“netstat
-an”查看端口状态。
了解了TCP/IP协议的基本概念,IP地址和端口的概念,我们就可以开始进行网络编程了。
socket(简称:套接字)
是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于
Socket 来完成通信的。
在 Python 中 使用socket 模块的函数 socket 就可以完成:
函数 socket.socket 创建一个 socket,返回该 socket
的描述符,该函数带有两个参数:
1、Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者
AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET。
2、Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP
协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)。
UDP是面向无连接的协议。使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
我们来看看在UDP协议如何发送数据。

澳门新葡亰赌995577 4

澳门新葡亰赌995577 5

澳门新葡亰赌995577 6

澳门新葡亰赌995577 7

tcp的客户端要比服务器端简单很多,代码如下:

澳门新葡亰赌995577 8

澳门新葡亰赌995577 9

澳门新葡亰赌995577 10

澳门新葡亰赌995577 11

import socket

# 创建socket对象
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 确定对象目的地ip和端口号port,组成一个元组
destAdress = ('192.168.11.76', 7777)
# 发送内容到目标机器上,内容要编码
msg = input('>>')
#编码信息
msg = msg.encode('gbk')
#发送消息
udpSocket.sendto(msg, destAdress)
# 关闭socket对象
udpSocket.close()

澳门新葡亰赌995577 12

澳门新葡亰赌995577 13

在windows中运行“网络调试助手”:

澳门新葡亰赌995577 14

单工、半双工、全双工
单工数据传输只支持数据在一个方向上传输;半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力.
使用多线程完成一个全双工的聊天程序

结果如下:

澳门新葡亰赌995577 15

结果如下:

澳门新葡亰赌995577 16

import socket
'''
serverSocket是用来接收新的客户端的
以后与这个连接的客户端的收发消息就不能用serverSocket了,
而是用返回来的新的newSocket
'''

serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.bind(('', 7777))
serverSocket.listen(10)
newSocket, clientAddr = serverSocket.accept()

# 发
sendData = input('>>')
newSocket.send(sendData.encode('gbk'))
# 收
recvData = newSocket.recv(1024)
print(recvData.decode('gbk'))

newSocket.close()
serverSocket.close()

澳门新葡亰赌995577 17

澳门新葡亰赌995577 18

结果如下:

澳门新葡亰赌995577 19

澳门新葡亰赌995577 20

结果如下:

可以看到,两个客户端都链接到了服务端,接下来,让两个客户端给服务端发信息:

可以看出,客户端链接服务器,必须提供服务器的信息(ip和端口号)。对于自身而言,并不用像服务端一样绑定自己的端口号。
我们已经完成了客户端和服务端的建立,接下来完成客户端与服务器的收发信息。

既然可以发送消息,那如何接受消息呢?

澳门新葡亰赌995577 21

结果如下:

结果如下:

因为udp的数据包不安全,即发送方发送是否成功不能确定,所以TFTP协议中规定,为了让服务器知道客户端已经接收到了刚刚发送的那个数据包,所以当客户端接收到一个数据包的时候需要向服务器进行发送确认信息,即发送收到了,这样的包成为ACK(应答包)
为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了。
TFTP数据包的格式如下:

import socket
import multiprocessing
import time


def socketProcess(newSerSocket, clientAddr):
    while True:
        recData = newSerSocket.recv(1024)
        recData = recData.decode('gbk')
        if recData == '':
            print('客户端%s:%s退出了...' % (clientAddr[0],clientAddr[1]))
            newSerSocket.close()
            break
        else:
            print('来自于%s:%s的消息(%s):%s' % (clientAddr[0], clientAddr[1], time.strftime('%Y-%m-%d %H:%M:%S'), recData))
            sendData = 'echo:%s' % recData
            newSerSocket.send(sendData.encode('gbk'))


def main():
    serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serverSocket.bind(('', 7777))
    serverSocket.listen(10)

    while True:
        newSerSocket, clientAddr = serverSocket.accept()
        creatprocess = multiprocessing.Process(target=socketProcess, args=(newSerSocket, clientAddr))
        creatprocess.start()
        '''
        因为是各拿一份代码,所以在此关闭newSerSocket,不会影响其他进程。但是如果是线程,
        在此将newSerSocket关闭,将会影响其他线程,其他客户端的访问将报错。
        '''
        newSerSocket.close()
        print('客户端%s:%s链接了,服务器开始服务了...' % (clientAddr[0],clientAddr[1]))


if __name__ == '__main__':
    main()
import struct
import socket

data = struct.pack('!H8sb5sb', 1, b'girl.jpg', 0, b'octet', 0)
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udpSocket.sendto(data, ('192.168.11.72', 69))
udpSocket.close()
from socket import *
from threading import *
import os

'''全局变量'''
# socket
udpSocket = None
# ip
destIp = None
# port
destPort = None


# 收
def recv():
    while True:
        recvInfo = udpSocket.recvfrom(1024)
        print("r<<%s:%s%s>>" % (str(recvInfo[1]), recvInfo[0].decode('gbk'), os.linesep), end='')


# 发
def send():
    while True:
        info = input('>>')
        udpSocket.sendto(info.encode('gbk'), (destIp, destPort))


# 主方法
def main():
    global udpSocket
    global destIp
    global destPort

    udpSocket = socket(AF_INET, SOCK_DGRAM)
    bindAddr = ('', 7788)  # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
    udpSocket.bind(bindAddr)

    destIp = input("对方的ip:")
    destPort = int(input("对方的port:"))

    tSend = Thread(target=send)
    tRecv = Thread(target=recv)

    tSend.start()
    tRecv.start()


if __name__ == '__main__':
    main()
import socket

# 1. 创建套接字
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. 绑定本地的相关信息
bindAddr = ('', 7788)  # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
udpSocket.bind(bindAddr)

num = 1
while True:
    # 3. 等待接收对方发送的数据
    recvData = udpSocket.recvfrom(1024)  # 1024表示本次接收的最大字节数

    # 4. 将接收到的数据再发送给对方
    udpSocket.sendto(recvData[0], recvData[1])

    # 5. 统计信息
    print('已经将接收到的第%d个数据返回给对方,内容为:%s' % (num, recvData[0].decode('gbk')))
    num += 1

# 5. 关闭套接字
udpSocket.close()

结果如下:

当服务器找到需要现在的文件后,会立刻打开文件,把文件中的数据通过TFTP协议发送给客户端
如果文件的总大小较大(比如3M),那么服务器分多次发送,每次会从文件中读取512个字节的数据发送过来
因为发送的次数有可能会很多,所以为了让客户端对接收到的数据进行排序,所以在服务器发送那512个字节数据的时候,会多发2个字节的数据,用来存放序号,并且放在512个字节数据的前面,序号是从1开始的,当发送的数据包排到65535的时候,会从0再开始。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图