Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有,让Socket去组织数据,以符合指定的协议。html
简而言之:node
其实站在你的角度上看,socket就是一个模块。咱们经过调用模块中已经实现的方法创建两个进程之间的链接和通讯。
也有人将socket说成ip+port,由于ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
因此咱们只要确立了ip和port就能找到一个应用程序,而且使用socket模块来与之通讯。
套接字得发展史: (套接字分为两类,文件类型和网络类型)python
(文件)套接字家族的名字:AF_UNIX(起源于UNIX)web
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,能够经过访问同一个文件系统间接完成通讯编程
(网络)套接字家族的名字:AF_INETwindows
(还有AF_INET6被用于ipv6,还有一些其余的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是不多被使用,或者是根本没有实现,全部地址家族中,AF_INET是使用最普遍的一个,python支持不少种地址家族,可是因为咱们只关心网络编程,因此大部分时候我么只使用AF_INET)设计模式
TCP(Transmission Control Protocol)可靠的、面向链接的协议(eg:打电话)、传输效率低全双工通讯(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。浏览器
UDP(User Datagram Protocol)不可靠的、无链接的服务,传输效率高(发送前时延小),一对1、一对多、多对1、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)缓存
具体的三次握手,四次挥手过程:服务器
三次握手:
SYN_SEND
状态,等待服务器(B)的确认;主机B由SYN=1知道,A要求创建联机;SYN_RECV
状态;ESTABLISHED
状态,完成TCP三次握手问题一:为何链接创建须要三次握手,而不是两次握手?
四次挥手:
Sequence Number
和Acknowledgment Number
,向主机2发送一个FIN
报文段;此时,主机1进入FIN_WAIT_1
状态;这表示主机1没有数据要发送给主机2了;FIN
报文段,向主机1回一个ACK
报文段,Acknowledgment Number
为Sequence Number
加1;主机1进入FIN_WAIT_2
状态;主机2告诉主机1,我也没有数据要发送了,能够进行关闭链接了;FIN
报文段,请求关闭链接,同时主机2进入CLOSE_WAIT
状态;FIN
报文段,向主机2发送ACK
报文段,而后主机1进入TIME_WAIT
状态;主机2收到主机1的ACK
报文段之后,就关闭链接;此时,主机1等待2MSL后依然没有收到回复,则证实Server端已正常关闭,那好,主机1也能够关闭链接了问题一:.为何TIME_WAIT状态须要通过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
问题二:lient发送完最后一个ack以后,进入time_wait状态,可是他怎么知道server有没有收到这个ack呢?莫非sever也要等待一段时间,若是收到了这个ack就close,若是没有收到就再发一个fin给client?这么说server最后也有一个time_wait哦?求解答
答:由于网络缘由,主动关闭的一方发送的这个ACK包极可能延迟,从而触发被动链接一方重传FIN包。极端状况下,这一去一回,就是两倍的MSL时长。若是主动关闭的一方跳过TIME_WAIT直接进入CLOSED,或者在TIME_WAIT停留的时长不足两倍的MSL,那么当被动
关闭的一方早先发出的延迟包到达后,就可能出现相似下面的问题:1.旧的TCP链接已经不存在了,系统此时只能返回RST包2.新的TCP链接被创建起来了,延迟包可能干扰新的链接,这就是为何time_wait须要等待2MSL时长的缘由。
问题三:为何要四次挥手?
答:确保数据可以完整传输。
当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方全部的数据都完整的发送给了主动方,因此被动方不会立刻关闭SOCKET,它可能还须要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方赞成关闭链接,因此这里的ACK报文和FIN报文多数状况下都是分开发送的
xxxxxxxxxx
总结:
TCP协议:
一、 面向链接\可靠\慢\对传递的数据的长短没有要求
二、 两台机器之间要想传递信息必须先创建链接
三、 以后在有了链接的基础上,进行信息的传递
四、可靠 : 数据不会丢失 不会重复被接收
五、慢 : 每一次发送的数据还要等待结果
六、 三次握手和四次挥手
UDP协议:
一、无链接\不可靠\快\不能传输过长的数据0
二、机器之间传递信息不须要创建链接 直接发就行
三、不可靠 : 数据有可能丢失
五层协议:
一、应用层 python send(b'hello,world')
# socket(虚拟,对下面的进行了整合,直接使用)
二、传输层 端口 tcp/udp协议 四层路由器 四层交换机
三、网络层 ip地址相关 ip协议 路由器 三层交换机
四、数据链路层 mac地址相关 arp协议 网卡 二层交换机
五、物理层 网线
基于TCP协议的socket使用:
#基础服务端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听连接
conn,addr = sk.accept() #接受客户端连接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息,须要时要解码decode
conn.send(b'hi') #向客户端发送信息,编码必要时
conn.close() #关闭客户端套接字,不是closed
sk.close() #关闭服务器套接字(可选)
#基础客服端
import socket
sk = socket.socket() # 建立客户套接字
sk.connect(('127.0.0.1',8898)) # 尝试链接服务器,0,0,0,1是全网段,全部来防本机的均可以
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
sk.close() # 关闭客户套接字
#注意,在服务端启动时,若是遇到,Address alreadly in use,则须要在bind前加sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#TCP的聊天版本服务端:可多链接,可是必须一对一回复
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
conn,addr = sk.accept() # 等待用户来链接我
while True:
msg = input('>>>')
conn.send(msg.encode('utf-8'))
if msg.upper() == 'Q':
break
content = conn.recv(1024).decode('utf-8') # 等待 客户端给我φ消息
if content.upper() == 'Q': break
print(content)
conn.close()
sk.close()
#TCP客户端:
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
ret = sk.recv(1024).decode('utf-8')
if ret.upper() == 'Q':break
print(ret)
msg = input('>>>')
sk.send(msg.encode('utf-8'))
if msg.upper() == 'Q':
break
sk.close()
基于UDP协议的socket的使用:
xxxxxxxxxx
#基础服务端
import socket
udp_sk= socket.socket(type=socket.SOCK_DGRAM) #建立一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024) # 若是有from则须要addr接收地址
print(msg)
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
udp_sk.close()
#基础客服端
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)
#进阶服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9001))
while True:
msg,client_addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
content = input('>>>')
sk.sendto(content.encode('utf-8'),client_addr)
sk.close()
#进阶客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1',9001)
while True:
content = input('>>>')
if content.upper() == 'Q':break
sk.sendto(content.encode('utf-8'),server_addr)
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q':break
print(msg)
sk.close()
#高级QQ服务端
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)
#高级QQ服务端
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),
'egg':('127.0.0.1',8081),
'yuan':('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()
xxxxxxxxxx
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的链接,(阻塞式)等待链接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器链接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据
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() 建立一个与该套接字相关的文件