Python网络编程基础TCP与UDP协议

在Python中数据是如何在网络之间传递的呢?今天的博客也主要是记录一些关于网络基础与Python中基于TCP与UDP协议的编程基础,至于TCP协议中的黏包我单独写一篇文章喽!

python-socket

0x00 OSI模型及网络套接字Socket

互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层,学Java的时候老师都说是7层,当然7层不太好记,还是推荐记忆5层的模型即可。

osi-moudles不同的层中负责着不同的工作,因而有着不同的协议。在传输层中主要是TCP协议或者UDP协议,网络层中是IP协议,而数据连链路层是ARP协议(主要是通过IP获得MAC地址)。

套接字是一种具有“端口通信”概念的计算机网络数据结构。而关于套接字家族,Python只支持AF_Unix、AF_NETLINK和AF_INET家族,Python中主要使用的是AF_INET。

无论是用什么类型的套接字,套接字的类型只有两种,一种是面向连接的,即在进行数据传输之前,必须先建立一个连接,之后在进行传输,类似于打电话,这种通信方式也被成为“流套接字”,其特点是顺序的、可靠的、不会重复的数据传输;而另一种则无需先建立连接,即可通讯,因而数据的同学顺序、可靠性及不重复型就无法保证了,但会保留数据边界。

python中网络编程需要使用socket模块。

import socket
#创建网络套接字
sk = socket.socket()

0x01 基于TCP协议的编程

网络编程需要有一个Service和Client,因而分开写两段代码,在同一台机子上运行。主要实现一个Client获取一个时间戳发送个Service,Server返回一个格式化时间给Client。

Server端

import socket
import time

IP = '127.0.0.1'
Port = 8090
ADDR = (IP,Port)
BUFF = 1024

sk = socket.socket()
sk.bind(ADDR)   #绑定服务器的IP与端口,需要一个Tuple对象
sk.listen()
conn,addr = sk.accept()

while True:
    ret = conn.recv(BUFF).decode('utf-8')
    ftime = time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(float(ret)))
    bytes_ftime = ftime.encode('utf-8')
    conn.send(bytes_ftime)

conn.close()
sk.close()

1~2行代码,导入相关模块。4~7行代码,设置基本的常量数据。9行创建套接字对象。10行绑定Server的IP地址与Server使用的端口。11行侦听等待Client端的链接。12行,待与Client端相连后,收到一个链接对象及Client的IP与端口的元祖(addr)。14~18行,接收到bytes类型的数据,将其转行为格式化时间,在转为bytes类型发送给Client。20~21行,关闭链接与关闭套接字,这里虽然不会运行的,主要是提醒一下,如果需要退出链接与关闭套接字的话,那么就需要这两行代码了。

Client端

import socket
import time

IP = '127.0.0.1'
Port = 8090
ADDR = (IP,Port)
BUFF = 1024

sk = socket.socket()
sk.connect(ADDR)

while True:
    t = str(time.time()).encode('utf-8')
    sk.send(t)
    ret = sk.recv(BUFF).decode('utf-8')
    print(ret)
    time.sleep(3)

sk.close()
'''
2018-08-17-22-58-50
2018-08-17-22-58-53
2018-08-17-22-58-56
......
'''

前9行代码是一样的。10行,与Server端相连,由于客户端不会与多个Server相连接,因而不用使用链接来操作数据,直接使用套接字对数据进行收发即可。12~17行,将时间戳转换为bytes类型发送给Server,并接受Server段的格式化时间,并打印出来。

从上面的代码,需要注意的是,在发送数据的时候,只能发送bytes类型的数据,所以在收发数据时候需要进行转码与解码的工作。另外在创建TCP协议下传输的套接字是Python默认的,并且使用的是AF_INET的家族地址。

TCP协议具有长链接的特点,Client向Server发起连接,Server接受Client连接,双方建立连接。Client与Server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

上面的套接字就是添加了家族地址与套接字类型,TCP为“流套接字”,这个也是《Python核心编程》(第二版)中推荐的写法。

0x02 基于UDP协议的编程

同样是写上面的例子,类似于手机时间的网络同步吧!

Server端

import socket
import time

IP = '127.0.0.1'
Port = 8090
ADDR = (IP,Port)
BUFF = 1024

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sk.bind(ADDR)   #绑定服务器的IP与端口,需要一个Tuple对象
while True:
    conntent,addr = sk.recvfrom(BUFF)
    t = time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(float(conntent.decode('utf-8'))))
    sk.sendto(t.encode('utf-8'),addr)

sk.close()

大体代码含义与TCP协议的代码意义相同。9行时候创建UDP的套接字的类型为SOCK_DGRAM(datagram的意思)。12行,UDP协议无需建立链接,因而不需要侦听以及接受链接。直接接收消息即可,接收到一个bytes类型的消息及对方的地址元祖(包含IP与端口)。14行,由于UDP协议没有建立链接,因而无法知道要传送给哪一个客户端,则在发送消息的时候需带有对方的地址元祖。

Client端

import socket
import time

IP = '127.0.0.1'
Port = 8090
ADDR = (IP,Port)
BUFF = 1024

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
    t = str(time.time()).encode('utf-8')
    sk.sendto(t,ADDR)
    content,addr = sk.recvfrom(BUFF)
    print(content.decode('utf-8'))
    time.sleep(3)

sk.close()
'''
2018-08-18-07-55-40
2018-08-18-07-55-43
2018-08-18-07-55-46
......
'''

UDP客户端的使用就更为简单了,直接发送消息即可,不过与服务器端一样需要带有目标的地址元祖。

发表评论