鄙人学习笔记
文章目录
套接字介绍定义套接字分类(针对TCP和UDP的分类)TCP套接字编程服务端流程代码实现举个例子客户端流程代码实现举个例子TCP套接字数据传输特点做个练习网络收发缓冲区举个例子TCP粘包套接字介绍
定义
套接字是实现网络编程进行数据传输的一种技术手段
套接字分类(针对TCP和UDP的分类)
①流式套接字(SOCK_STREAM): 以字节流方式(就像是管道中的水流一样)传输数据,实现TCP网络传输方案。
②数据报套接字(SOCK_DGRAM):以数据报形式(就像是用瓶子打包好的水一样)传输数据,实现UDP网络传输方案。
TCP套接字编程
服务端流程
先来看一个流程图:
代码实现
创建套接字sockfd=socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
功能:创建套接字对象参数: socket_family 网络地址类型:AF_INET表示ipv4socket_type 套接字类型:SOCK_STREAM 流式;SOCK_DGRAM 数据报proto(在应用层网络编程中用不到) 选择子协议:通常为0 返回值: 套接字对象
备注:底层/系统层套接字也针对某种协议,这些协议,有的时候会产生一些分支,也就是子协议。那么为啥在应用层网络编程中用不到proto这个参数呢?因为TCP协议和UDP协议没有子协议,所以参数设为0,即不选择任何子协议。
绑定地址
sockfd.bind(addr)
功能: 绑定本机网络地址(如果不写本机网络地址,则会报错)参数: 二元元组 (ip,port) 比如('0.0.0.0',8888)
备注:addr为一个元组,这个元组有两个元素一个是网络地址(IP),一个是端口号(port)。
设置监听
sockfd.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小参数 : 监听队列大小
备注1:并不是所有的套接字都具备监听的功能,即被客户端连接的功能。通过调用listen,我们的套接字对象才能被客户端连接。
备注2:一个服务端套接字对象可以同时连接多个客户端,与客户端进行连接需要进行3次握手,且连接的过程需要一个一个来进行。比如说,同时有5个客户端发起了连接请求,这时我们就会形成一个”先来后到”的队列,对这5个客户端的连接请求,一个一个进行处理。比如说,我们设置监听队列大小为5,表示队列最多容纳5个客户端等待处理。但有10个客户端同时发起了连接请求,这时,服务端会先处理第1个发起请求的那个客户端,前2~6个客户端则在等待队列中,最晚发起请求的4个客户端,则会被拒绝。
等待处理客户端连接请求
connfd,addr = sockfd.accept()
功能: 阻塞等待处理客户端请求(等待客户端连接,啥时候有客户端连接,才结束阻塞)返回值:connfd : 客户端连接套接字.客户端连接后,新产生的专门与客户端进行通信的套接字对象。则每当有一个客户端连接时,都会有一个新的专用于连接的套接字对象产生。就像对每个客户端提供一个专属管家一样,提升用户端体验。我们如果找到一个连接套接字,就可以知道它对应的客户端是谁addr :连接的客户端地址
阻塞函数:当程序运行到这个函数时,则程序暂停执行。程序在等待某种条件,当条件满足后才继续执行,如:input()、sleep()。阻塞函数在IO操作中很常见。
消息收发
data = connfd.recv(buffersize)
功能 : 接受客户端消息参数 :每次最多接收消息的大小(最多一次接受多少字节的消息)返回值: 接收到的内容
n = connfd.send(data)
功能 : 发送消息参数 :要发送的内容-bytes格式(字节串格式)返回值: 发送的字节数
备注1:所有和网络相关的消息传送都得是bytes格式(字节串格式).发送和接收都得是字节串。
备注2:我们发送/接收的是字节串,但平时写/读的是字符串,所以我们需要用encode()/dencode()进行转换。
关闭套接字
sockfd.close()
功能:关闭套接字
举个例子
服务端代码:
import socket#创建流式套接字sockfd = socket.socket(socket.AF_INET, \socket.SOCK_STREAM)#绑定地址sockfd.bind(('127.0.0.1', 8888))#设置监听sockfd.listen(5)#等待处理客户端链接print("Waiting for connect....")connfd, addr = sockfd.accept()#收发消息data = connfd.recv(1024)print("接收到的消息:", data.decode())n = connfd.send(b'Receive your message')print("发送了 %d 个字节数据" % n)#关闭套接字connfd.close()sockfd.close()
我们运行一下,得到以下结果:
结果说明,程序阻塞在accept,等待客户端连接。所以这时,我们就要找一个客户端与之连接,下一节,我们就学一下客户端流程。
客户端流程
先看看流程图:
代码实现
创建套接字(和客户端代码相同)
注意:只有相同类型的套接字才能进行通信
请求连接
sockfd.connect(server_addr)
功能:连接服务器参数:元组 服务器地址
收发消息(和客户端代码相同)
注意: 为了防止两端都阻塞,故recv和send要配合使用。比如,服务端是先send后recv,那么客户端则需要先recv后send。否则,若两端同时recv,则两端都会阻塞。
关闭套接字(和客户端代码相同)
举个例子
客户端代码:
服务端代码:
我们想要运行,但是若先运行客户端,则会报错。所以我们要先启动服务端
①运行服务端
查看服务端的控制台Console 2/A输出的结果:
②运行客户端
我们先看一下服务端的控制台Console 2/A输出的结果:
我们再看一下客户端的控制台Console 3/A输出的结果:
消息收发成功了,很好~
TCP套接字数据传输特点
①TCP连接中,当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
②TCP连接中,如果一端已经不存在,仍然试图通过send发送信息,则会产生BrokenPipeError
③一个监听套接字可以同时连接多个客户端,也能够重复被连接。
做个练习
要求1:一个客户端退出了,服务器不会退出,而是连接下一个客户端
要求2:客户端可以不停的循环发送消息
服务端代码:
#-*- coding: utf-8 -*-import socket#创建流式套接字sockfd = socket.socket(socket.AF_INET, \socket.SOCK_STREAM)#绑定地址sockfd.bind(('127.0.0.1', 8888))#设置监听sockfd.listen(5)#等待处理客户端链接while True:print("Waiting for connect....")try:connfd, addr = sockfd.accept()print("Connect from:", addr)except KeyboardInterrupt:print("退出服务")break# 收发消息while True:data = connfd.recv(1024)# 得到空则退出循环if not data:breakprint("接收到的消息:", data.decode())n = connfd.send(b'Receive your message')print("发送了 %d 个字节数据" % n)connfd.close()#关闭套接字sockfd.close()
客户端代码:
#-*- coding: utf-8 -*-from socket import *#创建tcp套接字sockfd = socket()#发起连接server_addr = ('127.0.0.1',8888)sockfd.connect(server_addr)#收发消息while True:data = input("消息:")if not data:breaksockfd.send(data.encode())data = sockfd.recv(1024)print("From server:",data.decode())#关闭sockfd.close()
先运行服务端,再运行客户端并发送消息。
客户端结果:
服务端结果:
网络收发缓冲区
①网络缓冲区有效的协调了消息的收发速度、缓解收发压力。
②send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。
网络缓冲区完成消息传递示意图:
举个例子
还记得我们上面那个练习么?其中一段代码是:
它表示,每次最多可接收消息大小为1024个字节.
我们来更改一下最大可接受的消息字节数为5:
用客户端发送消息:
服务端结果:
客户端结果:
则说明,客户端1次发送,服务端分3次接收了,同时返回给服务端3句’ Receive your message’也就是说,服务端的内层循环(如下图所示)循环了3次
备注1:所以我们一开始说【函数recv()是阻塞函数,当发送方不发消息,就会阻塞】。但其实更准确的来说,并不是发送方不发消息就会阻塞,接收方其实是先从缓冲区去拿消息,当缓冲区为空时,会阻塞,当缓冲区一直有消息时,则会一直获取消息,一直不阻塞。
注意!运行上面的代码时,客户端有时候也会出现以下这种情况:
按照上面的说法,当客户端发送1条消息,客户端会返回3条’ Receive your message’。但是也会出现,当第1条’ Receive your message’进入客户端缓冲区,客户端就从客户端缓冲区recv()了这条消息,导致剩下2条’ Receive your message’停留在缓冲区没被接受。等到下一次,客户端再从缓冲区中recv()时,才把剩下’ Receive your message’接收到。
备注1:我们第一次运行代码时,服务端快速的连续3次send()了1条’ Receive your message’,客户端1次recv()了3条’ Receive your message’。接收端一次接收了多条发送端消息,我们称这种情况叫:粘包。
TCP粘包
原因
TCP以字节流方式传输,没有消息边界。多次发送的消息被一次接收,此时就会形成粘包。
影响(分情况,比如:传一部电影/发送用户名消息)
①如果发送的内容中,每个信息都有独立的含义(比如:发送几个用户姓名),需要接收端独立解析,此时,这时粘包会有影响。
②如果发送的是一个字节流文件,是一个连在一起的整体(比如:发送一部电影),接收端无需单独解析,而是将所有接收到内容最终合成一个整体,这时粘包没啥影响。
处理方法
①人为的添加消息边界,比如:每次发送一个姓名时,在姓名后加一个特殊符号。
②控制发送速度(因为粘包的产生是因为收发速度不协调),比如用sleep()函数调节。