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. centos6 通过 kvm 安装 centos7

    参考: http://blog.51cto.com/wzlinux/1731324 http://blog.csdn.net/ztynet/article/details/54952425 一.检查C ...

  2. 你可能不知道的IDEA高级调试技巧

    一.条件断点 循环中经常用到这个技巧,比如:遍历1个大List的过程中,想让断点停在某个特定值. 参考上图,在断点的位置,右击断点旁边的小红点,会出来一个界面,在Condition这里填入断点条件即可 ...

  3. docker的/var/lib/docker/overlay文件夹突然占满了硬盘

    场景描述 事情是这样的,前两天客户的服务器突然断电了,导致用docker部署的服务也挂了.昨天去现场,重启了docker,同时准备更新一下服务.结果发现有一台节点硬盘满了- 还是觉得有些奇怪的,毕竟d ...

  4. 我们为什么要在Android中使用RxJava

    本文翻译来自–>Why should we use RxJava on Android 另外: 微凉一季 再另外: 微凉一季 感觉RxJava近期风生水起,不学习一下都不好意思了.洒家也是初学R ...

  5. C++实现景区信息管理系统

    景区信息管理系统 实现了: 1.1 建立主程序应用菜单选项 主程序应用菜单选项包含所实现的所有功能,并且对选项采用数字标识进行选择,对其他错误输入可以进行判别,提示输入错误. 1.2 导游线路图的创建 ...

  6. K好数(DP)

    问题描写叙述 假设一个自然数N的K进制表示中随意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数. 求L位K进制数中K好数的数目. 比如K = 4,L = 2的时候.全部K好数为11.13.2 ...

  7. game 角色相关记录

    GameServer启动 (role, misc, mail, offline)从共享内存中加载数据到m_mBlob中如果共享内存没有则从DB加载 主要是修改了同步共享内存,共享内存同步数据库{//r ...

  8. LeetCode - 868. Binary Gap

    Given a positive integer N, find and return the longest distance between two consecutive 1's in the ...

  9. HTTP 请求头 WIKI 地址

    https://en.wikipedia.org/wiki/List_of_HTTP_header_fields

  10. 【转载】C++中替代sprintf的std::ostringstream输出流详解

    一.简单介绍 ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中.ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓 ...