官方文档socket
菜鸟教程socket
廖雪峰网络编程
每一条TCP连接有两个端点,UDP也是端点到端点的传输
这个端点不是主机、不是主机IP、不是应用进程、也不是运输层的协议端口
这个端点就是套接字(socket)
套接字socket=IP地址:端口号
每一条TCP连接唯一地被通信两端的两个端点(即套接字socket)确定
TCP连接::={socket1,socket2}={ (IP1,port1) , (IP2,port2) }
同一个IP地址可以有多个TCP连接
同一个端口号也可以出现在多个不同的TCP连接中(Server同一个端口为多个Client服务)
SYN:Synchronize 同步
ACK:ACKnowledge Character 确认字符
第一次:Client发送一个SYN数据包
给Server
我(客户端)想连接到你(服务器)
第二次:Server收到后回应一个SYN+ACK数据包
给Client
你(客户端)确定想连接到我(服务器)嘛?
第三次:Client回应一个ACK数据包
给Server
我(客户端)确定想连接到你(服务器)
为什么是三次握手而非两次
已失效的连接请求报文段” 的产生在这样一种情况下:client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。”
FIN:Finish 结束
第一次:Client发送FIN数据包
给Server
我没有数据要发了,已经准备好关闭连接了,你如果没发完可以继续发
第二次:Server发送ACK数据包
给Client,收到后Client进入FIN_WAIT状态
等待服务器发FIN数据包
好的,我知道你想关闭了,但请你再等我一下,我先把数据发完
第三次:数据发完后,Server发送FIN数据包
给Client
数据发完了,我这边也准备好关闭了
第四次:Client发送ACK数据包
给Server,进入TIME_WAIT状态
好的,那就关闭吧,不过我怕你没收到确认信号,所以再等等
如果由于网络原因最后的ACK数据包丢失,Server会重发第三次的FIN数据包
。
如果未丢失则Server进入CLOSED状态
,不会再发数据包给Client,Client等待 2MSL未收到FIN数据包
后则进入CLOSED状态
Server
import socket
import sys
MySocket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#socket对象
host = '127.0.0.1'
port = 9999
MySocket.bind((host, port))
# 绑定端口号
MySocket.listen(5)
# 设置最大连接数,超过后排队
while True:
ClientSocket,addr = MySocket.accept()
# 建立客户端连接
print(ClientSocket.recv(1024).decode('utf-8'))
print("连接地址: %s" % str(addr))
msg='Hello World!'+ "\r\n"
ClientSocket.send(msg.encode('utf-8'))
ClientSocket.close()
Client
import socket
import sys
ServerSocket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host='127.0.0.1'
port = 9999
ServerSocket.connect((host, port))
# 尝试连接服务端套接字
ServerSocket.send('I am client'.encode('utf-8'))
msg = ServerSocket.recv(1024)
# 接收小于 1024 字节的数据
ServerSocket.close()
print (msg.decode('utf-8'))
socket.socket(family, type, [proto], [fileno])
创建并返回一个socket对象
family:地址族。通常为AF_INET或AF_INET6,分别代表IPV4和IPV6地址族
type:套接字类型。通常为SOCK_STREAM或SOCK_DGRAM,分别代表面向流的TCP和面向数据报的UDP
MySocket.bind((host,port))
(绑定套接字)指定服务端监听哪块网卡的哪个端口
host:网卡IP。注意如果是127.0.0.1的话,客户端程序也必须在本机运行才能访问
MySocket.listen(NUM)
服务端启动监听
NUM:连接池中挂起队列的最大长度,即提前准备好NUM个挂起的连接,要用时直接使用,节省了有客户端连接时创建socket的时间和资源
MySocket.accept()
服务端等待并返回一个客户端的连接
第一个返回值是代表客户端的socket对象,第二个是tuple形式的客户端套接字地址(host,port)
ClientSocket.recv(buffersize)
接收从客户端套接字传来的最多buffersize大小的bytes类型数据,使用decode(‘utf-8’)可解码
ClientSocket.send(MESSAGE)
服务端向客户端的socket对象ClientSocket发送bytes类型的MESSAGE
ClientSocket.close()
服务端关闭客户端的套接字
建议显式close或打开时使用with
ServerSocket.connect((host,port))
客户端连接到指定的服务端套接字
ServerSocket.recv(buffersize)
从服务端套接字接收数据并返回
客户端:Win10 (192.168.0.115)
服务端:CentOS 7 (192.168.0.111)
服务端一定先配置好防火墙把所监听的端口打开!!!
MySocket.bind('192.168.0.111',9999)
绑定套接字 192.168.0.111:9999
MySocket.listen()
开始本服务端的套接字192.168.0.111:9999
ServerSocket.connect(('192.168.0.111',9999))
尝试连接服务端的套接字
ClientSocket,addr=ServerSocket.accept()
获得客户端的socket对象和套接字并分别用ClientSocket和addr接收
接收到的addr是 (‘192.168.0.115’, 55062)
ClientSocket.send(MESSAGE)
向客户端发送bytes型MESSAGE
ServerSocket.recv()
接收MESSAGE
8. 通信结束,服务端使用ClientSocket.close()关闭客户端的套接字连接,继续监听绑定的本服务端套接字MySocket,客户端使用ServerSocket.close()关闭到服务端的套接字
注意:服务端需绑定自己监听的端口,而客户端连接时不需指定自己所使用的端口
使用UDP时不需要建立连接,只需要知道对方的IP:PORT就可以发送数据报,但是能不能到达就不一定了
Server代码
import socket
MySocket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#创建UDP类型的socket对象
MySocket.bind(('127.0.0.1', 9999))
#绑定套接字
#不需要调用listen()方法,而是直接接收客户端数据
while True:
data,ClientAddr= MySocket.recvfrom(1024)
#返回值是数据和客户端套接字地址,没有客户端socket对象
print('Received from %s:%s.' %ClientAddr)
print('client message is',data.decode('utf-8'))
MySocket.sendto(b'Hello, %s!' % data,ClientAddr)
#从本机socket向客户端socket发送数据
Client代码
import socket
ServerSocket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['Michael', 'Tracy', 'Sarah']:
ServerSocket.sendto(data.encode('utf-8'), ('127.0.0.1', 9999))
#只能发送bytes型的数据
print(ServerSocket.recv(1024).decode('utf-8'))
ServerSocket.close()