Socket通信分为服务端和客户端两部分:服务端用来提供资源,在Socket网络编程中,服务端是被动的,会主动打开端口等待客户端来进行连接。客户端是主动的,当有需求,需要连接到服务端时,会主动的发起对服务端的连接。
1.2.1 TCP通信-Server端
Server端编程主要有以下五个步骤:
1. 创建server端socket,并设置socket属性。
使用socket.socket()来创建服务端socket,该函数含有family,type和protocal三个参数。Family通常选择AF_INET即网络间服务器通信,此外还有用于本地进程间通信的AF_UNIX;基于IPV6的网络间通信AF_INET6。Type对应的是协议的类型,通常取值为SOCK_STREAM,即基于TCP的定向连接通信,或者SOCK_DGRAM,基于UDP的不定向连接通信。Type参数已经暗含了选择的协议类型,此外,可以选择第三个参数来代替暗含的参数类型。
表11.1 Socket()函数参数表
参数类型 | 值 | 描述 |
Family | AF_UNIX | 计算机本地进程间通信 |
AF_INET | 计算机网络间通信 | |
AF_INET6 | 基于IPV6的网络间通信 | |
Type | SOCK_STREAM | 基于TCP的稳定的流式socket通信 |
SOCK_DGRAM | 基于UDP的快速的数据报文式socket通信 | |
SOCK_RAW | 最原始的socket编程,能处理ICMP,IGMP协议 | |
SOCK_RDM | 相对可靠的UDP通信,不保证数据报文的传输顺序 | |
SOCK_SEQPACKET | 可靠的连续数据包服务形式socket通信 |
Socket.socket()默认的参数是AF_INET,SOCK_STREAM:基于TCP的网络间通信。
2. 绑定地址
绑定地址用到的函数为bind(),参数要以Tuple的形式传入,如server.bind(('127.0.0.1',9090)),端口号的选择一般应大于1024且为非占用端口。
3. 监听
监听使用的函数为listen(backlog),传入参数值为一个整型数据backlog,backlog表示服务器拒绝连接之前,操作系统可以挂起的连接的数目。
当服务器进行监听时,操作系统会把对应端口状态置为LISTENING,防止被其它进程占用。我们在Windows操作系统上,命令行界面可以使用netstat -ano查看所有被占用的端口的状态。在图11.2中,没有9090端口,而如图11.3所示,此时9090端口的状态为LISTENING,即处于监听状态,且协议为TCP。
监听时端口状态图
4. 建立连接
服务端会被动的使用accept()函数来接受客户端的连接,并返回连接的客户端的IP地址和端口号,以方便进行通信。当客户端和服务器端建立连接时,服务器会将端口状态调节为ESTAVLISHED,即连接建立状态,并且客户端为127.0.0.1:53118。
建立TCP连接后端口状态图
5. 通信
当通信双方建立连接完成后,可以通过send()(或sendall())和recv()来进行发收数据。接受到的数据为byte形式,需要转码后才能正常显示。
6 关闭服务
当通信完成后,使用close()函数关闭服务,包括socket和网络连接。
例1 Server端示例代码
import socket #导入包
# 建立一个服务端
'''
创建socket,默认AF_INET,SOCK_STREAM基于TCP的通信AF_INET,SOCK_DGRAM为UDP通信
'''
server = socket.socket()
server.bind(('127.0.0.1',9090)) #服务端需要绑定端口,一般选择大于1024号的未被占用的端口来进行绑定
'''
当为TCP通信时,需要设置listen函数的参数,借助于listen函数建立稳定连接。Listen函数的参数一般设置至少为1
当为UDP通信时,不需要
'''
server.listen(5)
while True:
print("连接中...")
conn, addr = server.accept()
print("连接成功!",addr)
while True:#循环接受、发送数据
data = conn.recv(1024) #接收数据,data是byte类型
if data.decode()=='exit':
conn.close()
break
else:
print('recive:',data.decode()) #打印接收到的数据
conn.sendall('收到请求'.encode('utf-8'))#发送数据
server.close()
1.2.2 TCP通信-Client端
客户端与服务端类似,大致分为以下四个步骤:
1. 创建server端socket,并设置socket属性
Socket属性的设置和服务端要保持一致,才能够进行通信
2. 建立连接
客户端使用connect()函数来与服务端建立连接,connect()函数传递的参数为tuple的形式,如client.connect(('127.0.0.1', 9090))两部分值分别为服务端的IP地址和对应端口
3. 通信
与服务端相同,使用send或者sendall来发送数据,recv来接受数据。
4. 关闭服务
例2 client端示例代码
import socket# 客户端 发送数据,以及接收数据
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #声明socket类型,同时生成链接对象#
client.bind(('localhost',9091)) #一般来讲,客户端不需要绑定端口,客户端发送信息时会由系统内核自行选择端口
client.connect(('127.0.0.1', 9090)) #建立一个链接,连接到本地的9090端口
while True:
msg = input("发出消息为:")
client.send(msg.encode('utf-8')) #发送一条信息 注意:python3.x 只接收btye流信息
data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
print('recv:',data.decode()) #输出接收到的信息
if msg == 'exit':
break
client.close()
1.2.3 基于UDP单连接通信
基于UDP的单连接通信与基于TCP的单连接通信类似,并且更为简单。
UDP服务器端编程的一般步骤如下:
1. 创建socket
使用函数socket()创建server端socket,注意和UDP不同,需要手动设置属性为socket.AF_INET,socket.SOCK_DGRAM 。
2. 绑定地址信息
与TCP通信类似,需要使用bind()函数以tuple形式绑定IP地址、端口等信息到socket上。
3. 通信
和TCP不同,UDP不需要建立连接,因此在发送数据时,需要将通信地址与数据一起放到函数中:接收数据,用函数recvfrom(); 发送数据,用函数sendto(msg,addr),其中msg为byte形式,addr为tuple形式,如
addr = ('127.0.0.1',9090)
msg = input("发出消息为:")
client.sendto(msg.encode('utf-8'), addr) #发送一条信息 python3.x 只接收btye流信息
UDP端口状态
4. 关闭网络连接
使用close()函数,关闭socket。
UDP编程的客户端一般步骤是:
1. 创建socket
使用函数socket()创建server端socket,注意和UDP不同,需要手动设置属性为socket.AF_INET,socket.SOCK_DGRAM 。即:
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2. 准备连接
设置服务端IP地址,端口等信息
3. 通信
与服务端类似,接收数据,用函数recvfrom(); 发送数据,用函数sendto(msg,addr),其中msg为byte形式,addr为tuple形式
4. 关闭网络连接;
使用close()函数,关闭socket。