目录
一:socket层在通信协议中的位置
图一:网络协议栈
二:理解socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
问:从Python编程者的角度来看socket到低是个什么东西?
答:一个模块。
答案解析:其实站在编程者的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。 也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。 所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
三:套接字(socket)的发展史
概念:一会套接字、一会socket是什么鬼? 套接字是socket的中文名。
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,如果只关心网络编程,所以大部分时候只使用AF_INET)
四:TCP协议和UDP协议
TCP(Transmission Control Protocol)传输控制协议:
特点:可靠的、面向长连接的、传输效率低、全双工通信(发送缓存&接收缓存)、面向字节流。
UDP(User Datagram Protocol)用户数据报协议:
特点:不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。
基于TCP与UDP的socket协议通信怎样实现?
图二:基于TCP与UDP的socket协议通信实现流程图
五.套接字(socket)基本使用
太理论了!还是先上代码、理解了代码回头再看一下图、学习的过程是一个形式上的简单重复、理解上的盘旋上升过程。
基于TCP协议的socket
tcp是基于链接的,一般情况下、是客户端发起链接服务器的请求。比如;我们购物时一般是链接京东或淘宝的服务器,如果京东或淘宝的服务器每天没事链接你的app,那估计要疯掉! 所以我们要先启动服务端,然后再启动客户端去链接服务端
server(服务器)端代码:
import socket # 导入socket模块
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket() # 创建socket对象、Python中一切恶接对象
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 解决地址占用问题
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字(ip,port)
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字
client(客户端)端代码:
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('127.0.0.1',8898)) # 尝试连接服务器、此处链接的ip是目标服务器的ip地址、port是目标服务器的端口、该例子用的是环回口实现(ICMP)
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
sk.close() # 关闭客户套接字
基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
server(服务器)端代码:
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字、注意1
udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024) # 注意2
print(msg)
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)、把对方的地址一块发过去 注意3
udp_sk.close() # 关闭服务器套接字
client(客户端)端代码:
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)
socket参数的详解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
创建socket对象时的参数说明:
family | 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 (AF_UNIX 域实际上是使用本地 socket 文件来通信) |
type | 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。 SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。 SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。 |
proto | 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。 |
fileno | 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。 与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。 这可能有助于使用socket.close()关闭一个独立的插座。 |
六:基于TCP与UDP的趣味实现
1、授时服务器:
问题:以windows系统为例、为什么A的电脑的时间与B的电脑的时间总是保持一致、而C的机械手表与D的机械手表可能存在时间偏差呢?
原因:就时间系统而言、所有的windows用户用的都是一个系统,windows服务器提供的服务器时间。而两只机械表分属两个不同的时间系统、出现时间不一致也是合理的。要不八路军分头夜袭小日本是=时都要先对一下表,以便统一行动时间。
server(服务器)端代码:
from socket import *
from time import strftime
ip_port = ('127.0.0.1', 9000)
bufsize = 1024
tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_server.bind(ip_port)
while True:
msg, addr = tcp_server.recvfrom(bufsize)
print('===>', msg)
if not msg:
time_fmt = '%Y-%m-%d %X'
else:
time_fmt = msg.decode('utf-8')
back_msg = strftime(time_fmt)
tcp_server.sendto(back_msg.encode('utf-8'), addr)
tcp_server.close()
client(客户端)端代码:
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
tcp_client=socket(AF_INET,SOCK_DGRAM)
while True:
msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
tcp_client.sendto(msg.encode('utf-8'),ip_port)
data=tcp_client.recv(bufsize)
2、山寨QQ聊天功能:
server(服务器)端代码:
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)
while True:
qq_msg,addr=udp_server_sock.recvfrom(1024)
print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
back_msg=input('回复消息: ').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
client(客户端)端代码:
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
'唐玄奘':('127.0.0.1',8081),
'孙悟空':('127.0.0.1',8081),
'沙悟净':('127.0.0.1',8081),
'猪八戒':('127.0.0.1',8081),
}
while True:
qq_name=input('请选择聊天对象: ').strip()
while True:
msg=input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
if msg == 'q':break
if not msg or not qq_name or qq_name not in qq_name_dic:continue
udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
udp_client_socket.close()