写在前面
由于本人并没有系统学习过网络通信的知识,所以本文的目的只是在于简单梳理一下自己对网络通信的基本认知以及socket编程的基本流程,重点是第三部分python的socket库用法。
1. 网络通信中最基本的概念
1.1 网络通信的目的
网络通信的目的就在于将数据(可以是结构化的数据,也可以是非结构化的数据比如语音,视频等)传递给另一方,这种传递最简单的情况下可以是同一个主机下同一个软件的不同进程之间的传递(比如QQ聊天的双开),可以是同一个主机不同程序间的通信;再复杂一点,可以是不同主机之间的进程之间的通信等。
1.2 IP地址和端口(Port)
在进行通信的时候,必须要明确跟“谁”在进行数据交互,也就是必须要确定IP地址和端口。IP地址是主机的一个标识,端口则是主机中的某一个进程。
IP地址
查看IP地址
windows:在cmd中输入ipconfig
即可显示网卡信息;在linux中终端中输入ifconfig
IP地址的分类
- IPV4
IPV4使用四组数,每一组数的范围是0~255,例如192.168.1.1。再细分的话,IPV4又可以有A ~ E类(差别在于网络与主机号位数不同)现如今,IPV4地址数量已经枯竭,所以才有了IPV6,差别在于数字的位数的增多。 - IPV6
例如:fe80::250::56ff::fe32::8c60/64
端口
端口就是某一个主机的某个进程的代号,网络通信其实是不同进程之间的通信,进程就是运行的程序+运行时所需要的资源。端口又可以分为知名端口(Well-known Ports)和动态端口(Dynamic Ports):这里的知名端口代表一些常见的固定的进程端口号,范围是0 ~ 1023,比如80端口是HTTP服务。用户可以自定义动态端口,其范围是1024 ~ 65536,也就是随机分配端口号。
2. 对Socket的基本理解
有了前面所说的IP地址和端口号,下一步要对它们进行一步抽象。Socket处于抽象层,所谓“抽象”就是把IP地址和端口号看成一个整体,即一个点,更形象的类比是把他们想象成公母线(male/female connectors),或者是插座,插头插在某个插孔中即可获取服务。socket(套接字)本质上是一种便于网络编程的API,还是一种文件类型,可以进行读写操作(分别是接收和发送数据)
不同协议下的socket有所不同,TCP/IP下的socket是stream类型(流形套接字),UDP协议下的socket是datagram类型,使用的时候需要区分一下。
UDP (User Datagram Protocol) v.s. TCP/IP (Transmission Control Protocol)
UDP协议中数据的传输不够稳定,而TCP/IP中有三次握手四次分手的机制,也就保证了数据传输的稳定性。
3. Python中使用Socket的基本流程
3.0 编程之前
在进行与网络通信有关的编程任务之前,一定要下载一个网络调试助手!!!这里我用的时NetAssist,它能让你清楚地看到数据是什么。
3.1 UDP协议中socket的使用
UDP协议中的socket属于datagram类型。可以把UDP传输想象成“写信”模型。
使用过程
用UDP发送数据大部分时候不需要绑定IP和端口,而用UDP接收必须要绑定端口和IP地址。
UDP发送端
- 导入socket模块并创建socket对象
- 确定接收端的IP地址和端口,和要发送的数据
- 发送"编码"过的数据给接收端
重要的是.sendto(send_data, dest_addr)
函数,这里dest_addr是元组类型。 - 关闭socket
简单代码:
import socket
def main():
# Create a socket
udp_send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Specify the IP address and port of receiver.
ip_addr = '127.0.0.1'
port = 54320
dest_addr = (ip_addr, port)
# When you want to transmit more data, please add a loop.
while True:
# Acquire the data from keyboard
send_data = input("Your data is: ")
udp_send_socket.sendto(send_data.encode("utf-8"), dest_addr) # dest_addr is a tuple.
# if input is 'exit', then jump out of the loop.
if send_data.lower() == 'exit':
break
# Close the socket.
udp_send_socket.close()
if __name__ == "__main__":
main()
UDP接收端
- 导入socket模块并创建socket对象
- 绑定IP地址和端口
- 接收数据
- “解码”接收的数据
接收的数据格式是一个元组,第一个元素是接收到的数据,第二个是发送方的地址。 - 关闭socket
import socket
def main():
# Create a socket
udp_recv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Specify the ip address and port number.
ip_addr = '127.0.0.1'
port = 54321
recv_addr = (ip_addr, port)
# Bind the IP address and port of receiver.
udp_recv_socket.bind(recv_addr)
# When you want to transmit more data, please add a loop.
while True:
# Receive data from other ports:
recv_data = udp_recv_socket.recvfrom(1024)
print("The data from %s is %s" % (recv_data[1], recv_data[0])) # dest_addr is a tuple.
# if input is 'exit', then jump out of the loop.
if recv_data[0].lower() == b'exit':
break
# Close the socket.
udp_recv_socket.close()
if __name__ == "__main__":
main()
3.2 TCP/IP协议中socket的使用
TCP/IP协议的使用要比UDP麻烦,TCP类似于“打电话”模型。TCP协议的数据传输是更可靠的,它采用了发送应答机制;超时重传;错误校验还有流量控制和阻塞管理。
TCP的客户端这一部分并不复杂,TCP的服务器一端多了Listen和accept过程。
使用过程
TCP Client
创建过程
- 导入socket模块并创建流形socket(socket.SOCK_STREAM)
- 确定服务器的IP和端口并连接服务器
- 收发数据
- 关闭socket
import socket
def main():
# Create a socket for tcp client
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Assign the IP address and port of tcp server
server_ip = '127.0.0.1'
server_port = 7788
server_addr = (server_ip, server_port)
tcp_client_socket.connect(server_addr) # The 4 statements above can be merged into a single one statement.
# Send or receive data
while True:
# Catch the input data from keyboard.
send_data = input("The data you wanna send to the server is: ")
# Send data to the tcp server.
tcp_client_socket.send(send_data.encode("utf-8"))
if send_data.lower() == 'exit':
break
tcp_client_socket.close()
if __name__ == "__main__":
main()
TCP Server
创建过程
- 导入socket模块并创建流形socket(socket.SOCK_STREAM)
- 绑定服务器的IP和端口号
- 用socket.listen()将套接字设置为被动链接(处于监听状态)
(注意: 这里的监听socket是唯一的) - 用socket.accpet()方法等待客户连接:accpet()将返回一个新的socket(用于跟客户端进行通信)和客户端的IP地址和端口号
(3 ,4步相当于先把服务器的socket置成被动模式,当客户端接入的时候将解阻塞,用新的套接字与之通信,程序中一定是用新的socket发送和接收数据。) - 接收客户端编码后的消息
- 回送编码后的数据给客户端
- 关闭socket
import socket
def main():
# 1. Create a socket for tcp server
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. Bind the IP address and port for tcp server
server_ip = '127.0.0.1'
server_port = 7788
server_addr = (server_ip, server_port)
tcp_server_socket.bind(server_addr) # The 4 statements above can be merged into a single one statement.
# 3. Change server to "listen" mode
tcp_server_socket.listen(128)
print("------------------------------")
while True:
print("Wait for a new client")
# 4. Accept for clients' connections
new_socket_for_client, client_addr = tcp_server_socket.accept()
print("The new client is %s" % str(client_addr))
# Communicate with client.
while True:
recv_data = new_socket_for_client.recv(1024)
print("The transmitted data is : %s" % recv_data.decode("utf-8"))
# Catch the input data from keyboard.
if recv_data: # if the client break the connection, recv_data has no data and the condition is set to false.
send_data = input("Please answer your clent: ")
# Answer the tcp client back.
new_socket_for_client.send(send_data.encode("utf-8"))
else:
break
tcp_server_socket.close()
if __name__ == "__main__":
main()