TCP:传输控制协议(使用情况多于udp)
稳定:保证数据一定能收到
相对UDP会慢一点
web服务器一般都使用TCP(银行转账,稳定比快要重要)
TCP通信模型:
在通信之前,必须先等待建立链接
TCP的三次握手:
第一次握手:建立连接时,客户端发送SYN(请求同步)包到服务器,并进入SYN_SENT(请求连接)状态,等待服务器确认
第二次握手:服务器收到syn包,必须确认客户的SYN(x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV(SYN派遣)状态
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端与服务器才正式开始传送数据
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去
TCP服务器:
在tcp传输过程中,如果有一方收到了对方的数据,一定会发送一个ACK确认包给发送方
TCP的四次挥手:
断开一个TCP连接则需要“四次挥手”
第一次挥手:主动关闭方调用close,会发送一个长度为0的数据包以及FIN(结束标志)用来关闭主动方到被动关闭方的数据传送,
告诉被动关闭方:我已经不会再给你发数据了,但是,此时主动关闭方还可以接受数据
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,
我的数据也发送完了,不会再给你发数据了
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
长连接:三次握手四次挥手之间分多次传递完所有数据(优酷看视频、在线游戏),长时间占用某个套接字
短连接:三次握手四次挥手之间传递少部分数据,多次握手挥手才传递完所有数据(浏览器),短时间占用
tcp服务器流程如下:1. socket创建⼀个套接字
2. bind绑定ip和port
3. listen设置最大连接数,收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求
4. accept等待客户端的链接、接收连接请求
5. recv/send接收发送数据
TCP服务端:
from socket import *
#创建套接字对象,参数使用TCP协议:
s = socket(AF_INET,SOCK_STREAM)
#绑定一个IP地址和端口号:
s.bind(("127.0.0.1",8881))
#设置监听状态和最大连接数:
s.listen(5)
如果有新的客户端来链接服务器, 那么就产⽣⼀个新的套接字
# newS⽤来为这个客户端服务(10086小妹)
# addr就可以省下来等待其他新客户端的链接
newS,addr = s.accept()
#发送数据:
newS.send("nihao".encode("utf-8"))
#接收1024个字节:
data = newS.recv(1024)
print(data)
TCP客户端:
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.connect(("127.0.0.1",8881))
data = s.recv(1024)
print(data)
s.send(b"123")
s.close()
单进程服务器(每次只能服务一个客户端):
from socket import *
serSocket = socket(AF_INET, SOCK_STREAM)
localAddr = ('',7788)
serSocket.bind(localAddr)
serSocket.listen(5)
while True:
print("主进程等待新客户端")
newSocket,destAddr = serSocket.accept()
print("主进程接下来负责处理",str(destAddr))
try:
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0: #如果收到的客户端数据长度为0,代表客户端已经调用close()下线
print("接收到", str(destAddr),recvData)
else:
print("%s-客户端已关闭" %str(destAddr))
break
finally:
newSocket.close()
serSocket.close()
并发服务器:
serSocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
重新设置套接字选项,重复使用绑定的信息
# 当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,
你的程序就要用到SO_REUSEADDR选项。
创建多进程服务器:
from socket import *
from multiprocessing import Process
import time
def func(new):
while True:
new.send(b"nihao")
data = new.recv(1024)
time.sleep(1)
print(data)
if data.decode() == "886":
new.close()
break
if __name__ == "__main__":
s = socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(("192.168.19.138",7789))
s.listen(5)
try:
while True:
time.sleep(1)
print('-----主进程, , 等待新客户端的到来------’)
new,addr = s.accept()
print(‘-----主进程, , 接下来创建⼀个新的进程负责数据处理’)
p = Process(target=func,args=(new,))
p.start()
except ConnectionResetError:
print("非法中断")
#当为所有的客户端服务完之后再进⾏关闭,表示不再接收新的客户端的链接
s.close()
创建多进程客户端:
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.19.138",7789))
while True:
data = s.recv(1024)
print(data)
s.send(b"123")
s.close()
多线程服务器(耗费的资源比多进程小一些):
from socket import *
from threading import Thread
from time import sleep
# 处理客户端的请求并执⾏事情
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr), recvData))
else:
print('[%s]客户端已经关闭'%str(destAddr))
break
newSocket.close()
def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print(‘-----主进程, , 等待新客户端的到来------’)
newSocket,destAddr = serSocket.accept()
print(‘主进程接下来创建⼀个新的线程负责处理 ‘, str(destAddr)))
client = Thread(target=dealWithClient, args=(newSocket,destAddr))
client.start()
#因为线程中共享这个套接字, 如果关闭了会导致这个套接字不可⽤,
#但是此时在线程中这个套接字可能还在收数据, 因此不能关闭
#newSocket.close()
finally:
serSocket.close()
if __name__ == '__main__’:
main()
socketserver:
可以使用socketserver来创建socket用来简化并发服务器
socketserver可以实现和多个客户端通信(实现并发处理多个客户端请求的Socket服务端)
它是在socket的基础上进行了一层封装,也就是说底层还是调用的socket
服务器接受客户端连接请求——》实例化一个请求处理程序——》根据服务器类和请求处理程序类,调用处理方法。
例如:
基本请求程序类(BaseRequestHandler)调用方法 handle 。此方法通过属性 self.request 来访问客户端套接字
多线程的并发服务器旗舰版:
import socketserver
import time
#创建一个请求处理类,继承 BaseRequestHandler 并且重写父类中的 handle()
class Mysock(socketserver.BaseRequestHandler):
#在handle()中处理和客户端所有的交互,建立链接时会自动执行handle方法
def handle(self):
while True:
re = self.request
re.send(b"abc")
data = re.recv(1024)
time.sleep(1)
print(data)
#对socketserver.ThreadingTCPServer 类实例化对象,将ip地址,端口号以及自己定义的类名传入,并返回一个对象
sock = socketserver.ThreadingTCPServer(("192.168.19.138",8181),Mysock)
#对象执行serve_forever方法,开启服务端(handle_request()只处理一个请求)
sock.serve_forever()
#处理多个请求,永远执行
多线程的并发客户端旗舰版:
from socket import *
import time
s = socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.19.138",8181))
while True:
data = s.recv(1024)
time.sleep(1)
print(data)
s.send(b"123")
s.close()
socketserver服务端自定义类实现通信循环服务端:
import socketserver
# 自定义类来实现通信循环
class MyServer(socketserver.BaseRequestHandler):
# 必须写入handle方法,建立链接时会自动执行handle方法
def handle(self):
while True:
data = self.request.recv(1024)
# handle 方法通过属性 self.request 来访问客户端套接字
print('->client:', data)
self.request.send(data.upper())
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyServer)
server.serve_forever()
socketserver客户端自定义类实现通信循环客户端:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
client.send('hello'.encode('utf-8'))
data = client.recv(1024)
print(data)
远程执行命令subprocess:
Python可以使用subprocess下的Popen类中的封装的方法来执行命令
构造方法 popen() 创建popen类的实例化对象
obj = Subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
data 命令内容
shell = True 命令解释器,相当于调用cmd 执行指定的命令
stdout 正确结果丢到管道中
stderr 错了丢到另一个管道中
PIPE 将结果转移到当前进程
stdout.read() 可以获取命令执行的结果
指定结果后会将执行结果封装到指定的对象中
然后通过对象.stdout.read()获取执行命令的结果,如果不定义stdout会将结果进行标准输出
print(obj.stdout.read().decode('gbk')) # 正确命令
print(obj.stderr.read().decode('gbk')) #错误命令
远程执行命令服务端subprocess:
import subprocess
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.bind(("192.168.19.138",8878))
s.listen(5)
while True:
ns,addr = s.accept()
while True:
try:
data = ns.recv(1024)
sub = subprocess.Popen(data.decode,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE )
a = sub.stdout.read().decode("gbk")
b = sub.stderr.read().decode("gbk")
ns.send((a + b).encode("utf-8"))
except ConnectionResetError:
print("服务结束")
break
ns.close()
s.close()
远程执行命令客户端subprocess:
客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while 1:
cmd = input('>>>')
if cmd == "zaijian":
break
phone.send(cmd.encode('utf-8'))
from_server_data = phone.recv(1024)
print(from_server_data.decode('gbk'))
phone.close()
#提出沾包问题
解决沾包问题:
TCP协议是面向流的协议,容易出现粘包问题
不管是recv还是send都不是直接接收对方的数据(不是一个send一定对应一个recv),而是操作自己的缓
存区(产生沾包的根本原因)
例如基于tcp的套接字客户端往服务端上传数据,发送时数据内容是按照一段一段的字节流发送的,
在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,
不能一次提取任意字节的数据,这一点和TCP是很不同的
只有TCP有粘包现象,UDP永远不会粘包
粘包不一定会发生
如果发生了:1.可能是在客户端已经粘了
2.客户端没有粘,可能是在服务端粘了
客户端粘包:
发送端需要等缓冲区满才发送出去,造成粘包
(发送数据时间间隔很短,数据量很小,TCP优化算法会当做一个包发出去,产生粘包)
服务端粘包
接收方没能及时接收缓冲区的包(或没有接收完),造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,
服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
粘包服务端:
from socket import *
import time
s = socket(AF_INET,SOCK_STREAM)
s.bind(("192.168.19.138",8881))
s.listen(5)
newS,addr = s.accept()
time.sleep(1)
data = newS.recv(1024)
data1 = newS.recv(1024)
print("1",data.decode())
print("2",data1.decode())
粘包客户端:
from socket import *
import time
s = socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.19.138",8881))
s.send("hello".encode("utf-8"))
s.send("world".encode("utf-8"))
s.close()
不合适的解决方案:
send时加上时间间隔,虽然可以解决,但是会影响效率。不可取。
问题的根源在于:接收端不知道发送端将要传送的字节流的长度
所以解决粘包的方法就是发送端在发送数据前,发一个头文件包,告诉发送的字节流总大小,然后接收端来一个死循环接收完所有数据
使用struct模块可以用于将Python的值根据格式符,转换为固定长度的字符串(byte类型)
struct模块中最重要的三个函数是pack(), unpack(), calcsize()
pack(fmt, v1, v2, ...) 按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)
unpack(fmt, string) 按照给定的格式(fmt)解析字节流string,返回解析出来的tuple
calcsize(fmt) 计算给定的格式(fmt)占用多少字节的内存
解决粘包服务端代码:
from socket import *
import struct
s = socket(AF_INET,SOCK_STREAM)
s.bind(("",7788))
s.listen(5)
ns,addr = s.accept()
d1 = ns.recv(4)
l = struct.unpack("i",d1)
print(l)
d2 = b""
while True:
if l < 1024:
d2 += ns.recv(l)
break
else:
d2 += ns.recv(1024)
l -= 1024
print(d2.decode("gbk"))
1.基于tcp协议完成登录认证
客户端输入用户名密码
发送到服务端
服务端认证
发送结果到客户端
服务端代码:
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.bind(("127.0.0.1",8881))
s.listen(5)
news,addr = s.accept()
data = news.recv(1024)
data1 = news.recv(1024)
if data.decode("utf-8") == "zd" and data1.decode("utf-8") == "1":
news.send("登陆成功!".encode("utf-8"))
客户端代码:
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.connect(("127.0.0.1",8881))
username = input("用户名:")
pwd = input("密码:")
s.send(username.encode("utf-8"))
s.send(pwd.encode("utf-8"))
data = s.recv(1024)
print(data.decode("utf-8"))
2,看代码写结果【如果有错误,则标注错误即可,并且假设程序报错可以继续执行】
class Foo ( object ):
a1 = 1
__a2 = 2
def __init__(self, num):
self.num = num
self.__salary = 1000
def show_data(self):
print ( self.num + self.a1 )
obj = Foo ( 666 )
print ( obj.num )
结果:666
print ( obj.a1 )
结果:1
print ( obj.__salary )
结果:AttributeError: 'Foo' object has no attribute '__salary'--AttributeError:“Foo”对象没有属性“u salary”
print ( obj.__a2 )
结果:AttributeError: 'Foo' object has no attribute '__a2'--AttributeError:“Foo”对象没有属性“uu a2”
print ( Foo.a1 )
结果:1
print ( Foo.__a2 )
结果:AttributeError: type object 'Foo' has no attribute '__a2'--AttributeError:类型对象“Foo”没有属性“uu a2”
3,看代码写结果【如果有错误,则标注错误即可,并且假设程序报错可以继续执行】
class Foo ( object ):
a1 = 1
def __init__(self, num):
self.num = num
def show_data(self):
print ( self.num + self.a1 )
obj1 = Foo ( 666 )
obj2 = Foo ( 999 )
print ( obj1.num )
结果:666
print ( obj1.a1 )
结果:1
obj1.num = 18
obj1.a1 = 99
print ( obj1.num )
结果:18
print ( obj1.a1 )
结果:99
print ( obj2.a1 )
结果:1
print ( obj2.num )
结果:999
print ( Foo.a1 )
结果:1
print ( obj1.a1 )
结果:1
4,看代码写结果,注意返回值。
class Foo ( object ):
def f1(self):
return 999
def f2(self):
v = self.f1 ()
print ( 'f2' )
return v
def f3(self):
print ( 'f3' )
return self.f2 ()
def run(self):
result = self.f3 ()
print ( result )
obj = Foo ()
v1 = obj.run ()
print ( v1 )
结果: f3
f2
999
None
5,看代码写结果【如果有错误,则标注错误即可,并且假设程序报错可以继续执行】
class Foo ( object ):
def f1(self):
print ( 'f1' )
@classmethod
def f2(cls):
print ( 'f2' )
obj = Foo ()
obj.f1 ()
结果:f1
obj.f2 ()
结果:f2
Foo.f1 ()
结果:TypeError: f1() missing 1 required positional argument: 'self'--TypeError:f1()缺少1个必需的位置参数:“self”
Foo.f2 ()
结果:f2
6,看代码写结果
class Department ( object ):
def __init__(self, title):
self.title = title
class Person ( object ):
def __init__(self, name, age, depart):
self.name = name
self.age = age
self.depart = depart
def message(self):
msg = "我是%s,年龄%s,属于%s" % (self.name, self.age, self.depart.title)
print ( msg )
d1 = Department ( '人事部' )
d2 = Department ( '销售部' )
p1 = Person ( '武沛齐', 18, d1 )
p2 = Person ( 'alex', 18, d1 )
p1.message ()
结果:我是武沛齐,年龄18,属于人事部
p2.message ()
结果:我是alex,年龄18,属于人事部
8,什么是C / S架构?
答:客户端/服务器
9,为何基于tcp协议的通信比基于udp协议的通信更可靠?
1.基于连接与无连接
2.对系统资源的要求(TCP较多,UDP少)
3.UDP程序结构较简单
4.流模式与数据报模式
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证
10,流式协议指的是什么协议,数据报协议指的是什么协议?
cp,udp
11,什么是socket?简述基于tcp协议的套接字通信流程
1. socket创建⼀个套接字
2. bind绑定ip和port
3. listen设置最大连接数,收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求
4. accept等待客户端的链接、接收连接请求
5. recv/send接收发送数据
12,什么是粘包? socket中造成粘包的原因是什么? 哪些情况会发生粘包现象?
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
14,简述TCP / IP四层协议,七层协议
应用层,传输层,网络层,数据链路层
应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
18,网络编程中设计并发服务器, 使用多进程与多线程, 请问有什么区别?
并发服务器使用多进程: 重新设置套接字选项,重复使用绑定的信息
当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,
你的程序就要用到SO_REUSEADDR选项。
并发服务器使用多线程:耗费的资源比多进程小一些