Python----socket编程
基于TCP的套接字编程:
socket层的位置:socket在传输层和应用层之间
socket是什么:
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
套接字分类:1、基于文件类型的套接字家族;套接字家族的名字:AF_UNIX
2、基于网络类型的套接字家族;套接字家族的名字:AF_INET
套接字工作流程:
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
socket模块的应用:
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字 、s.listen() 开始TCP监听、s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来。
s.close() 关闭服务器。con.close()关闭链接
import socket
# 明确配置变量
ip_port = ('127.0.0.1',8080)
back_log = 5
buffer_size = 1024
# 创建一个TCP套接字
ser = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 套接字类型AF_INET, socket.SOCK_STREAM tcp协议,基于流式的协议
ser.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 对socket的配置重用ip和端口号
# 绑定端口号
ser.bind(ip_port) # 写哪个ip就要运行在哪台机器上
# 设置半连接池
ser.listen(back_log) # 最多可以连接多少个客户端
while 1:
# 阻塞等待,创建连接
con,address = ser.accept() # 在这个位置进行等待,监听端口号
while 1:
try:
# 接受套接字的大小,怎么发就怎么收
msg = con.recv(buffer_size)
if msg.decode('utf-8') == '1':
# 断开连接
con.close()
print('服务器收到消息',msg.decode('utf-8'))
except Exception as e:
break
# 关闭服务器
ser.close()
客户端:p.connect(ip地址,端口号) 连接服务器
import socket
p = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
p.connect(('127.0.0.1',8080))
while 1:
msg = input('please input')
# 防止输入空消息
if not msg:
continue
p.send(msg.encode('utf-8')) # 收发消息一定要二进制,记得编码
if msg == '1':
break
p.close()
※用户态:专门存放应用程序。内核态:专门存放操作系统的内核。
※在socket 里面 from socket import * 这样可以减少代码量。
※当发送回车换行,内容为空,没有必要发所以就会卡顿。当自己这一端 的内核态没东西会卡住recv()。加一个判断是否为空解决。
※在Windows下面杀掉一个链接,会报错。有的系统下面不会报错,这样的话判断是否收空。
※按照socket数据--->内核态----->网卡的顺序发送
send()\recv()都是发送socket数据
send()和recv()都是往自己的内存里面收发。
※端口号+ip地址+mac地址 = 哪个应用程序+哪台电脑+哪个房间(一一对应) 标示互联网上唯一的一个程序
udp套节字:
由于udp是无连接的所以比TCP更简洁。
服务端:recefrom() 接受的结果是发送的信息,和发送方的IP和端口号。sendto(信息,目标主机IP和端口号)
from socket import *
udp_ser = socket(AF_INET, SOCK_DGRAM) # 数据报式的套接字
udp_ser.bind(('127.0.0.1', 8080))
while 1:
data = udp_ser.recvfrom(1024)
print(data)
udp_ser.sendto('data'.encode('utf-8'),data[1])
# udp_ser.close()
>>> (b'2333', ('127.0.0.1', 58113))
客户端:
from socket import *
s = socket(AF_INET, SOCK_DGRAM)
while 1:
msg = input('input-1')
s.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data,addr = s.recvfrom(1024)
print(data.decode('utf-8'))
# s.close()
※recv在自己这端缓冲区为空时,阻塞。
recvfrom在自己这端的缓冲区为空时,就收一个空。
用udp实现两个不同客户端的交流:
服务端:
from socket import *
udp_ser = socket(AF_INET, SOCK_DGRAM) # 数据报式的套接字
udp_ser.bind(('127.0.0.1', 8080))
data_1, addr_1 = udp_ser.recvfrom(1204)
data_2, addr_2 = udp_ser.recvfrom(1204)
while 1:
# udp_ser.sendto('你好'.encode('utf-8'))
data1 = udp_ser.recvfrom(1204)
udp_ser.sendto(data1[0],addr_2)
data2 = udp_ser.recvfrom(1204)
# udp_ser.sendto(data,)
udp_ser.sendto(data2[0], addr_1)
客户端:
from socket import *
s = socket(AF_INET, SOCK_DGRAM)
s.sendto('hello'.encode('utf-8'), ('127.0.0.1', 8080))
while 1:
msg = input('input-1')
s.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data,addr = s.recvfrom(1024)
print(data.decode('utf-8'))
客户端2:
from socket import *
s = socket(AF_INET,SOCK_DGRAM)
s.sendto('hello'.encode('utf-8'),('127.0.0.1', 8080))
while 1:
data,addr= s.recvfrom(1024)
print(data.decode('utf-8'))
msg = input('input-2')
s.sendto(msg.encode('utf-8'),('127.0.0.1', 8080))
HTTP三次握手、四次挥手:
三次握手(因为刚开始没有数据传输所以可以合并),四次挥手(因为客户端到服务端数据传完可以断开,但是服务端的数据不一定发完,所以不能一次性断开)
握手:1.客户端发送连接请求(syn)
2.服务器回应确认发送(ack),第一条客户端到服务器端的连接建好,并且向客户端发送连接请求(syn)
3.客户端回应确认发送(ack),第二条服务器端到客户端的连接建好。
挥手(谁先发完,谁就断开连接):1.客户端发送请求断开连接(seq) 2.服务端回应确认(ack)此时客户端到服务端的链接断开
3.服务端发送请求断开连接(seq) 4.客户端回应确认(ack) 此时服务端到客户端的链接断开
至此双向连接都已断开。