1.tcp和udp协议的区别

TCP协议
  面向连接\可靠\慢\对传递的数据的长短没有要求
  两台机器之间要想传递信息必须先建立连接
  之后在有了连接的基础上,进行信息的传递
  可靠 : 数据不会丢失 不会重复被接收
  慢 : 每一次发送的数据还要等待结果
  三次握手和四次挥手

UDP协议
  无连接\不可靠\快\不能传输过长的数据0
  机器之间传递信息不需要建立连接 直接发就行
  不可靠 : 数据有可能丢失


  import socket
  sk = socket.socket()
  sk.bind(('0.0.0.0',9090))
  sk.listen(5)
  conn,addr = sk.accept()

  accept 相当于和客户端的connect 一起完成了TCP的三次握手
  至于之前的sk, 它只起到一个大门的作用了, 意思是说,欢迎敲门, 进门之后我将为你生成一个独一无二的socket描述符!  

2.socket模块

什么是socket?
  建立网络通信连接至少要一对端口号(socket)。  socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;  HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

两种套接字:基于文件和面向网络的  基于文件的:AF_UNIX  基于网络的:AF_INIT

特殊意义的解释socket:  socekt又称为‘套接字’,用于描述IP和地址端口,是一个通信链路的句柄,应用程序通常通过套接字向网络发出请求或者应答网络请求。  socket起源于Unix,所以也遵从“一切皆文件”的基本哲学,对于文件,进行打开/读取/关闭的操作模式。  socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。  socket和file文件的区别:    file模块是针对指定文件进行打开、读写、关闭操作。    socket模块是针对服务器客户端socket进行打开、读写、关闭操作。
python中socket模块:  地址簇:    socket.AF_INET IPv4(默认)    socket.AF_INET6 IPv6    socket.AF_UNIX 只能够用于单一的Unix系统进程间通信  类型:    socket.SOCK_STREAM  流式socket , for TCP (默认)    socket.SOCK_DGRAM   数据报式socket , for UDP
tcp/ip和http的关系? tcp/ip协议是传输层协议,主要解决数据如何在网络中传输,而HTTP协议是应用层协议,主要解决如何包装数据。
  我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容。
 如果想要使传输的数据有意义,则必须使用到应用层协议。 应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。
 WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。”

socket对象

Socket对象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

参数一:地址簇
  参数    描述
  socket.AF_INET    IPv4(默认)
  socket.AF_INET6    IPv6
  ocket.AF_UNIX    只能够用于单一的Unix系统进程间通信
参数二:类型
  参数    描述
  socket.SOCK_STREAM    流式socket , for TCP (默认)
  socket.SOCK_DGRAM    数据报式socket , for UDP
  socket.SOCK_RAW    原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
  socket.SOCK_RDM    是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
  socket.SOCK_SEQPACKET    可靠的连续数据包服务

Socket类方法
  方法    描述
  s.bind(address)    将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
  sk.listen(backlog)    开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
  sk.setblocking(bool)    是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
  sk.accept()    接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
  sk.connect(address)    连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
  sk.connect_ex(address)    同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
  sk.close()    关闭套接字连接
  sk.recv(bufsize[,flag])    接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
  sk.recvfrom(bufsize[.flag])    与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
  sk.send(string[,flag])    将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
  sk.sendall(string[,flag])    将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。
  sk.sendto(string[,flag],address)    将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
  sk.settimeout(timeout)    设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。
  sk.getpeername()    返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
  sk.getsockname()    返回套接字自己的地址。通常是一个元组(ipaddr,port)
  sk.fileno()    套接字的文件描述符

socket对象

基于tcp协议(流式socket):是两个进程之间的通信,通过端口号来区分不同进程

import socket
sk = socket.socket()  #)返回的用于监听和接受客户端的连接请求的套接字
# sk.bind(('192.168.16.96',9090))
sk.bind(('0.0.0.0',9090)) #能接收到所有的ip的访问
sk.listen(5)  #监听链接,并且设置监听个数,
conn,addr = sk.accept()
# hold住,等待用户链接,accept()接受一个客户端的连接请求,并返回一个新的套接字,与客户端通信是通过这个新的套接字上发送和接收数据来完成的。
#每个连接进来的客户端,都会通过accept函数返回一个不同的客户端的socket对象和属于客户端的套接字
    #bytes:是字节   b'kobe'
    #str:字符串类型  'kobe'
    #str----编码(encode)---->bytes
    #bytes-----解码(decode)--->str
    #英文字符串可以直接加b转成bytes
    #中文的必须的加上''.encode('utf-8')
#发送回复信息,在网络传输中的最小单位为字节,所以,要将数据转为字节格式
conn.send('我接受到了'.encode('utf-8'))
ret = conn.recv(4096)
print(ret.decode('utf-8'))
conn.close()  #conn.close和cilent的sk.close()是四次挥手的过程
sk.close()  #关闭socket,不接受任何client请求

import socket
sk =socket.socket()
sk.connect(('192.168.16.96',9090))  #只和server的accept对应
ret = sk.recv(1024)
print(ret.decode('utf-8'))
sk.send('你好啊'.encode('utf-8'))
sk.close()

socket服务端

import socket
sk =socket.socket()
sk.connect(('192.168.16.96',9090))  #只和server的accept对应
ret = sk.recv(1024)
print(ret.decode('utf-8'))
sk.send('你好啊'.encode('utf-8'))
sk.close()

socket客户端

基于udp协议(报文式socket)

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)  #SOCK_DGRAM指的是udp协议
sk.bind(('192.168.16.96',8081))
while True:
    msg,client_addr = sk.recvfrom(1024)
    #在udp协议中,recvfrom接收返回的时候能接收到客户端的信息msg和客户端的链接信息client_addr,
    #在tcp协议中,在等待连接的conn,addr = sk.accept()会接受到conn客户端的socket和客户端的链接信息
    print(str(client_addr)+":"+msg.decode('utf-8'))
    content = input('>>>')
    sk.sendto(content.encode('utf-8'),client_addr)
    #通过sendto和客户端的连接信息发送消息
sk.close()

socket服务器端

import socket
client_addr= ('192.168.16.96',8081)
sk = socket.socket(type=socket.SOCK_DGRAM)
while True:
    connect = input(">>>>")
    if connect.upper() !='Q':
        sk.sendto(connect.encode('utf-8'),client_addr)
        msg = sk.recv(1024).decode('utf-8')
        if msg.upper() == 'Q': break
        print(str(client_addr)+":"+msg)
    else:
        break

socket客户端

注意:
    1.tcp协议服务端是通过accept来建立链接,获取客户端的链接信息
         conn,addr = sk.accept()
      通过recv来获取客户端消息
         msg = sk.recv(1024).decode('utf-8')    通过send发送信息     conn.send('hello'.encode('utf-8'))

    客户端需要连接服务端地址     sk.connect(服务器地址和端口)    客户端发送信息     sk.send('你好'.encode('utf-8'))    客户端接收信息     msg = sk.recv(1024).deocde('1024')

    2.udp协议服务器是通过recvfrom来获取客户端发送的信息和客户端链接的信息      msg,addr1 = sk.recvfrom(1024)    通过sendto发送信息给客户端,要指定客户端信息      sk.sendto('hello'.encode('utf-8'),addr1)

    udp客户端不需要链接服务器端,是通过sendto发送信息      sk.sendto('你好'.encode('utf-8'),服务器地址和端口)    通过recv来获取信息      msg = sk.recv(1024).decode('utf-8') 

 3.tcp黏包

什么叫做黏包?

一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。

为什么只有TCP通讯存在粘包?

主要原因是TCP是以流的方式来处理数据,并且能发送大量的数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。

TCP协议拆包机制

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
MTU是Maximum Transmission Unit的缩写。意思是(网卡)网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。
如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令时又会接到之前执行的另外一部分结果,这种就是黏包。

面向流的通信特点和Nagle(优化)算法

TCPtransport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。 
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。 

可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

黏包的两种情况

1,发送方的缓存机制:发送端需要等缓冲区满才发送出去,造成黏包(发送数据时间间隔很短,数据很小,会合到一起,产生黏包)
    连续send两次且数据很小
2,接收方的缓存机制:接收不及时接收缓冲区的包,造成多个包接收(客户端发送一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿走上次剩余的数据,产生黏包。)
    连续recv两次且第一个recv接收的数据小

黏包处理

# -*- coding: utf-8 -*-
# @Time    : 2019/4/10 16:58
# @Author  : p0st
# @Site    :
# @File    : 传递大文件server.py
# @Software: PyCharm
import time
import json
import struct
import socket
start_time = time.time()
sk = socket.socket()
ip_addr = (('127.0.0.1',9999))
sk.bind(ip_addr)
sk.listen(5)
conn,addr = sk.accept()

num = conn.recv(4)
l_num = struct.unpack('i',num)[0]  #bytes类型的json的长度为45,因为struct.unpack是元组类型的
l_dic = conn.recv(l_num).decode('utf-8')  #将bytes类型的json转成字符串类型的json
dic = json.loads(l_dic)
filesize = dic['file_size']
with open(dic['file_name'],'wb') as info:
        while filesize>=1024:
            content = conn.recv(1024)
            info.write(content)
            filesize -=1024
        else:
            content = conn.recv(filesize)
            if content:
                info.write(content)
conn.close()
sk.close()
print(time.time()-start_time)

发送大文件服务端

# -*- coding: utf-8 -*-
# @Time    : 2019/4/10 17:03
# @Author  : p0st
# @Site    :
# @File    : 传递大文件client.py
# @Software: PyCharm
import json
import struct
import socket
import os
sk = socket.socket()
ip_addr = (('127.0.0.1',9999))
sk.connect_ex(ip_addr)

file_path = input("请输入文件路径:>>>").strip()
file_name = os.path.basename(file_path) #获取文件名字
file_size = os.path.getsize(file_path)  #获取文件大小

s_dic = {'file_name':file_name,'file_size':file_size}
j_s_dic = json.dumps(s_dic)   #将字典序列化成json
b_s_dic = j_s_dic.encode('utf-8')  #将json转成bytes类脑,在网上传输
l_s_dic = len(b_s_dic)   #常看bytes类型的长度

lalala = struct.pack('i',l_s_dic)  #将bytes类型的长度通过struct的pack方法变成4个字节
sk.send(lalala)
sk.send(b_s_dic)
with open(file_path,'rb') as info:
    while file_size >= 1024:
        content = info.read(1024)
        sk.send(content)
        file_size -= 1024
    else:
        content = info.read(file_size)
        if content:
            sk.send(content)

sk.close()

#1.将字典转成json,再转成bytes类型,查看其长度发送给服务器端
#2.将bytes类型的发送给服务器

发送大文件客户端

使用struct解决黏包:该模块可以把一个类型,如数字,转成固定长度的bytes

我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

发送时                                接收时
  先发送struct转换好的数据长度4字节        先接受4个字节使用struct转换成数字来获取要接收的数据长度
  再发送数据                            再按照长度接收数据

并发的socketserver

import time
import socketserver

class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        for i in range(200):
            conn.send(('hello%s'%i).encode('utf-8'))
            print(conn.recv(1024))
            time.sleep(0.5)
            print(conn)

server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()

#sk.setblocking(False)  #默认不阻塞,不阻塞模型,django和flask等等,setblocking做到和socketserver对tcp协议的非阻塞
# 非阻塞模型是一个突破,udp协议不用阻塞和不阻塞,udp协议能同时提供对多个客户端进行连接

返回系列

python进阶之 网络编程的更多相关文章

  1. Python进阶之网络编程

    网络通信 使用网络的目的 把多方链接在一起,进行数据传递: 网络编程就是,让不同电脑上的软件进行数据传递,即进程间通信: ip地址 ip地址概念和作用 IP地址是什么:比如192.168.1.1 这样 ...

  2. python高级之网络编程

    python高级之网络编程 本节内容 网络通信概念 socket编程 socket模块一些方法 聊天socket实现 远程执行命令及上传文件 socketserver及其源码分析 1.网络通信概念 说 ...

  3. 第六篇:python高级之网络编程

    python高级之网络编程   python高级之网络编程 本节内容 网络通信概念 socket编程 socket模块一些方法 聊天socket实现 远程执行命令及上传文件 socketserver及 ...

  4. Python进阶:函数式编程实例(附代码)

    Python进阶:函数式编程实例(附代码) 上篇文章"几个小例子告诉你, 一行Python代码能干哪些事 -- 知乎专栏"中用到了一些列表解析.生成器.map.filter.lam ...

  5. Python之路 - 网络编程之粘包

    Python之路 - 网络编程之粘包 粘包

  6. Python之路 - 网络编程初识

    Python之路 - 网络编程初识 前言

  7. Python进阶之面向对象编程

    面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...

  8. 周末班:Python基础之网络编程

    一.楔子 你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好.但是如果这两个程序之间想要传递一个数据, ...

  9. python之路——网络编程

    一.楔子 你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好.但是如果这两个程序之间想要传递一个数据, ...

随机推荐

  1. mybatis打印完整的sql

    mybatis log plugin

  2. 如何在云端部署SAP HANA实战, Azure 上的 SAP HANA(大型实例)概述和体系结构

    什么是 Azure 上的 SAP HANA(大型实例)? Azure 上的 SAP HANA(大型实例)是一种针对 Azure 的独特解决方案. 除了提供 Azure 虚拟机以用于部署和运行 SAP ...

  3. dubbo系列一:dubbo介绍、dubbo架构、dubbo的官网入门使用demo

    一.dubbo介绍 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成.简单地说,dubbo是一个基于Spri ...

  4. Git:git diff 命令详解

    工作目录 vs 暂存区 $ git diff <filename> 意义:查看文件在工作目录与暂存区的差别.如果还没 add 进暂存区,则查看文件自身修改前后的差别.也可查看和另一分支的区 ...

  5. yii2的数据库读写分离配置

    简介 数据库读写分离是在网站遇到性能瓶颈的时候最先考虑优化的步骤,那么yii2是如何做数据库读写分离的呢?本节教程来给大家普及一下yii2的数据库读写分离配置. 两个服务器的数据同步是读写分离的前提条 ...

  6. 【Dubbo 源码解析】07_Dubbo 重试机制

    Dubbo 重试机制 通过前面 Dubbo 服务发现&引用 的分析,我们知道,Dubbo 的重试机制是通过 com.alibaba.dubbo.rpc.cluster.support.Fail ...

  7. 【IEEE会议论文】格式规范问题

    - Text area: The height of the text should not be much smaller than 23.5 cm  and the width should no ...

  8. Android学习:Notification状态栏通知

    Notification是显示在手机状态栏的通知,它代表一种具有全局效果的通知,程序一般通过NotificationManager服务来发送Notification.在小米手机上,手指在屏幕顶端向下划 ...

  9. Abp之工作单元与事务

    环境:Abp1.2 疑问:没有调用工作单元的SaveChanges方法引起的事务提交时机的问题. 例如:有一个应用服务代码如下: public void CreatePhrase(PhraseCrea ...

  10. java爬取网站信息和url实例

    https://blog.csdn.net/weixin_38409425/article/details/78616688(出自此為博主) 具體代碼如下: import java.io.Buffer ...