zoukankan      html  css  js  c++  java
  • TCP:

     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选项。
    并发服务器使用多线程:耗费的资源比多进程小一些

  • 相关阅读:
    flashplayer关闭休眠模式
    大道至简,职场上做人做事做管理[转一下]
    flash程序员对python中while True的理解
    github搭建个人主页
    flash素材在as程序中使用的几种方法
    python中解析xml文档转化成字符串的方法
    查看修改mysql编码方式
    FusionCharts
    extJSjson字符串和json对象
    我的收藏
  • 原文地址:https://www.cnblogs.com/zhang-da/p/11908436.html
Copyright © 2011-2022 走看看