"""
一客户端 / 服务端架构:
1 硬件 C/S 架构(打印机)
2 软件 C/S 架构
互联网中处处都是 CS 架构, 服务端与客户端
CS 架构与 socket 的关系
- 我们学习 socket 就是为了完成 C/S 架构的开发
二 OSI 七层模型:
一个完整的计算机系统是由硬件操作系统应用软件三者组成, 一台计算机只能自己玩自己, 要想跟别人玩就需要上网了
互联网的核心就是由一堆协议组成, 协议就是标准, 比如全世界人通信的标准是英语;
如果把计算机比做人, 那么互联网协议就是计算机界的英语了, 如果所有的计算机都学会了这门英语, 那么就可以互相通信了
为何学习 socket 一定要先学习互联网协议:
- 1 首先我们的目标是基于 socket 编程, 来开发一款自己的 cs 架构软件
- 2 其次 cs 架构软件 (软件属于应用层) 是基于网络进行通信的
- 3 网络的核心即一堆协议, 协议即标准, 你想开发一款基于网络通信的软件, 就必须遵循这些标准
三 socket 层
socket 层位于运输层和应用层之间, 即 socket 抽象层
四 socket 是什么?
socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层, 它是一组接口在设计模式中, socket 其实就是一个门面模式, 它吧复杂的
TCP/IP 协议族隐藏在了 socket 接口的后面, 对用户来说, 一组简单的接口就是全部, 让 socket 去组织数据, 以符合指定的协议
所以我们无需深入理解 tcp/udp 协议, socket 已经帮我们封装好了, 我们只需要遵循 socket 的规定去编程, 写出的程序自然符合标准
- 也有人将 socket 说成是 IP+port,IP 是用来标识互联网中的一台主机的位置, 而 port 是用来标识这台主机上的一个应用程序, IP 地址
配置到网卡上的, 而 port 是应用程序开启的, IP 与 port 的绑定就标识了互联网中独一无二的应用程序
程序的 pid 是同一台机器上不同进程或者线程的标识
五套接字的分类:
- 基于文件类型的套接字家族
套接字家族的名字: AF_UNIX
unix 一切皆文件, 基于文件的套接字调用的就是底层文件系统来取数据, 两个套接字进程运行在同一机器, 可以通过访问同一个
间接完成通信
- 基于网络类型的套接字家族
套接字家族的名字: AF_INET
... 所有的地址家族中, AF_INET 是使用最广泛的一个, python 支持很多种地址家族, 但是由于我们只关心网络编程, 所以我们
大部分时间都只是使用 AF_INET
六套接字的工作流程:
一个生活中的场景: 你要给你前对象打电话, 先拨号, 前对象听到电话铃声后拿起电话, 这时候你俩就建立起来了不正当的链接, 然后
就可以进行不正当的交易了交易完成后, 挂断电话结束此次交谈;
先从服务端说起, 服务端先初始化 socket, 然后与端口绑定(bind), 对端口进行监听(listen), 调用 accept 阻塞, 等待客户端链接
在这个时候如果有客户端初始化一个 socket, 然后链接服务器(connect), 如果链接成功, 这时客户端与服务端的链接就建立了
客户端发送数据请求, 服务端接收请求并处理, 然后把回应数据发送给客户端, 客户端读取数据, 关闭链接, 交互结束
- socket 函数的用法
- import socket
- socket.socket(socket_family,socket_type,protocal=0)
socket_family: 可以是 AF_UNIX 或者是 AF_INET;socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM,protocol 一般默认为 0
获取 tcp/ip 套接字
tcpsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
获取 udp/ip 套接字
udpsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
由于 socket 模块中有太多的属性, 我们直接在这里使用 from xxx import * 的语句导入, 这样就可以减少代码量
eg:tcpsock = socket(AF_INET,SOCK_STREAM)
- 服务端套接字函数
s.bind()绑定 (主机, 端口号) 到套接字
s.listen()开始 tcp 监听
s.accept()被动接受 tcp 客户的连接,(阻塞式)等待连接的到来
- 客户端套接字函数
c.connect()被动初始化 tcp 服务器连接
c.connect_ex() connect()函数的扩展版本, 出错时返回错误码, 而不是抛出异常
- 公共用途的套接字函数
s.recv()接受 tcp 数据
s.send()发送 tcp 数据(send 在待发送数据量大于已缓存区剩余的空间时, 数据丢失, 不会发完)
s.sendall()发送完整的 tcp 数据(本质就是循环 send,... 数据不会丢失)
s.recvfrom()接受 udp 数据
s.sendto()发送 udp 数据
s.getpeername()链接到当前套接字的远程地址
s.getsockname()当前套接字的地址
s.getsockopt()返回指定套接字的参数
s.setsockopt()设置指定套接字的参数
s.close()关闭套接字链接
- 面向锁的套接字方法
s.setblocking()设置套接字的阻塞与非阻塞模式
s.settimeout()设置阻塞套接字操作的超时时间
s.gettimeout()得到阻塞套接字操作的超时时间
- 面向文件的套接字函数
s.fileno()套接字的文件描述符
s.makefile()创建一个与该套接字相关的文件
七基于 tcp 的套接字
tcp 是基于链接的, 必须先启动服务端, 然后再启动客户端去链接服务端
- tcp 服务端:
- server = socket() #创建服务器套接字
- server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
- server.bind() #把地址绑定到套接字
- server.listen() #监听链接
- while true: #服务器无限循环
- client,addr = server.accept() #接受客户端链接
- while True: #通信循环
- client.recv()/client.send() #接收与发送数据
- client.close() #关闭客户端套接字
- server.close() #关闭服务器套接字
- tcp 客户端:
- client = socket() #创建客户端套接字
- client.connect() #尝试链接服务器
- while True:
- client.send()/client.recv() #发送 / 接收数据
- client.close() #关闭客户端套接字
八基于 udp 的套接字
- udp 服务端
- server = socket() #创建服务器套接字
- server.bind() #绑定套接字地址
- while True: #服务器无线循环
- client,addr = server.recvfrom()/server.sendto() #接收与发送数据
- server.close() #关闭服务器套接字
- udp 客户端
- client = socket() #创建客户端套接字
- while True: #通信循环
- client.sendto()/client.recvfrom() #发送与接收数据
- client.close() #关闭客户端套接字
九粘包现象(了解)
如果当我们用 socket 模拟 cmd 执行远程命令时, 有些数据太长, 超过了 1024 个字节, 一次接受不完, 就会造成粘包
- 什么是粘包?
只有 tcp 才有粘包现象, udp 永远不会粘包, 因为 udp 是基于流收发数据的
所谓粘包的问题主要还是因为接收方不知道消息之间的界限, 不知道一次性提取多少数据造成的
- 粘包的解决
- struct 模块
- 自定义报头
- ...
- 什么时候才会发生粘包?
发送端需要等缓冲区满才发送出去, 造成粘包(发送数据时间间隔很短, 数据很小会喝到一起, 产生粘包)
接收方不及时接收缓冲区的包, 造成多个包接收产生粘包
- 了解
tcp 是可靠传输, udp 是不可靠传输
十 socketserver 模块
- 终极必杀技
"""
来源: http://www.bubuko.com/infodetail-2498937.html