Python M3 面向对象

source: alex - day 8 - socket编程进阶

网络架构的基础知识

计算机如何实现通信?是通过网卡,连上线,就可以实现通信,数据可传输。 数据的形式各异,包括图片,视频等。

不通类型的通信(email,收发图片等),是通过不同类型的协议来支持。通过协议,交换不通的数据流。

协议作用
http 网站
smtp email
dns 将域名解析成ip地址
ftp 下载文件
ssh  
smp 简单网络监控
icmp ping包,测试网络通不通 (网络层)
dhcp ip地址分配
... ...

数据的交换,无非就是两种方式:发(send) 和 收 (recieve)

OSI七层模型

  1. 应用
  2. 表示
  3. 会话
  4. 传输:能够传输的要求,满足 tcp/IP (三次握手,四次断开) 和 udp (不安全的数据传输,不关心对方是否在。一般很少用) 两种协议。同时,也必须标准的数据类型。
  5. 网络:认识ip地址
  6. 数据链路:只认识mac地址(物理地址:16进制的地址),不认识ip地址。
  7. 物理层

基本上的应用协议都在tcp/ip之上,icmp在网络层,其他基本都在应用层。不同的协议就像不通的语言。 本质上,不通的协议,都是发一条消息收一条消息。那么如果每个协议都需要自己在计算机底层上实现发和收(二进制),那就太麻烦了并且得深刻理解tcp/ip的基层原理。所以将复杂的东西进行封装,并只暴露给用户一个接口,让其他协议直接调用即可。 这个收和发的封装就叫做socket,它是所有的网络协议的基础。socket只干两件事,收 和 发。

详细介绍: OSI七层模型与TCP/IP五层模型 OSI七层模型详解

socket

运行原理

如何在两台机器中实现通信呢?

假设有A和B两个机器,B机器访问A机器。在访问时,机器B提供其ip地址和程序端口。 A机器上有很多程序(每个机器可开放65535个端口),假设有nginx(网站), mysql, alexTv等。 其流程是,B通过ip地址找到A机器,接着通过端口号找到需要访问的程序并擦作, 比如默认80就是nginx, mysql 3306等。 通过3306连上mysql, 数据返回机器B。B机器可能有多个程序包括qq, ssh, weixin等,通过最初的ip地址和端口返回。

socket是对所有上层协议的封装, 封装了tcpip及udp等

套接字 socket: 通信端点

套接字是计算机网络数据结构, 体现了“通信端点”的概念。建立一个socket必须至少有2端,一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求,连接建立之后,双方可以互发数据。 从上面来看,任何类型的通信开始之前,网络应用程序必须创建套接字。最初,套接字存在于同一主机中两个运行的程序进行通信,这就是进程间通信。套接字有两种类型:基于文件和面对网络的。

socket families 地址簇

地址家族描述
socket.AF_UNIX 本即进程间通讯,基于文件的
socket.AF_INET 因特网,基于网络的,使用最广泛
socket.AF_INET6 第6版因特网协议, 基于网络的

其他还有,AF_NETLINK, AF_TIPC

socket types 类型

不管用哪种socket families, 都会有两种不通风格的套接字连接。

  1. 面向连接的 - 虚拟电路 or 流套接字

意思是进行通信之前必须先建立连接。面对连接的通信提供序列化的,可靠的,不重复的数据交付,而没有记录边界。 实现这种连接类型的主要协议是TCP。为了创建TCP套接字,必须采用SOCK_STREAM作为套接字类型

  1. 无连接的套接字 - 数据报类型

不用连接。但数据传输的过程中,无法保证顺序性、可靠性或重复性, 但保存了记录边界,也就是说是整体发送,不是分成多个片段的。数据报的成本更低。 数据报连接类型的主要协议是UDP。创建UDP套接字,必须采用SOCK_DGRAM

  1. 其他

SOCK_RAW 原始套接字,普通的套接字无法处理ICMP, IGMP等网络报文,但是SOCK_RAW可以。也可以处理IPv4报文。 SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。

socket() 模块函数 - 创建套接字

  1. socket.socket(socket_family, socket_type, protocol=0)
# 创建TCP/IP套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 或在python 3.x, tcp/ip为默认
socket.socket()

# 创建UDP/IP套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1. socket属性直接引入命名空间
from socket import *

tcpSock = socket(AF_INET, SOCK_STREAM)

套接字对象(内置)方法

名称描述
服务器套接字方法  
s.bind() 将地址(主机名,端口号对)绑定到套接字上
s.listen() 设置并启动TCP监听器
s.accept() 被动接受TCP客户端连接,一直等待直到连接达到(阻塞)。返回两个值:连接的标记 和 对方的IP地址
客户端套接字方法  
s.connect() 主动发起TCP服务器连接
s.connect_ex() connect()扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常
普通的套接字方法  
s.recv() 接受TCP消息
s.recv_into() 接受TCP消息到指定的缓冲区
s.send() 发送TCP消息
s.sendall() 完整的发送TCP消息
s.recvfrom() 接受UDP消息
s.recvfrom_into() 接受UDP消息到指定的缓冲区
s.sendto() 发送UDP消息
s.getpeername() 连接到TCP的远程地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回给定套接字选项的值
s.setsockopt() 设置给定套接字选项的值
s.shutdown() 关闭连接
s.close() 关闭套接字
s.detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符
s.ioctl() 控制套接字模式(只支持windows)
面向阻塞的套接字方法  
s.setblocking() 设置套接字的阻塞或非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 获取阻塞套接字操作的超时时间
面向文件的套接字方法  
s.fileno() 套接字的文件描述符
s.makefile() 创建与套接字关联的文件对象
数据属性  
s.family 套接字家族
s.type 套接字类型
s.proto 套接字协议

socket模块属性

除了socket.socket(), socket模块的其他属性

属性名称描述
数据属性  
AF_UNIX, AF_INET, AF_INET6、AF_NETLINK、AF_TIPC Python 支持的套接字家族
SO_STREAM, SO_DGRAM 套接字类型 (TCP = 流, UDP = 数据报)
has_ipv6 表示是否支持IPv6 的布尔标志
异常  
error 套接字相关错误
herrora 主机和地址相关的错误
gaierror 地址相关的错误
timeout 超时
函数  
socket() 用指定的地址家族,套接字类型和协议类型(可选)创建一个套接字对象
socketpair() 用指定的地址家族,套接字类型和协议类型(可选)创建一对套接字对象
create_connection() 常规函数, 它接受一个地址(主机名,端口名)对,返回套接字对象
fromfd() 用一个已经打开的文件描述符创建一个套接字对象
ssl() 在套接字初始化一个安全套接字层(SSL)。不做证书验证。
getaddrinfo() 得到地址信息
getnameinfo() 给定一个套接字地址,返回(主机名,端口号)二元组
getfqdn() 返回完整的域的名字
gethostname() 得到当前主机名
gethostbyname() 由主机名得到对应的ip 地址
gethostbyname_ex() gethostbyname()的扩展版本,返回主机名,主机所有的别名和IP 地址列表。
gethostbyaddr() 由IP 地址得到DNS 信息,返回一个类似gethostbyname_ex()的3 元组。
getprotobyname() 由协议名(如'tcp')得到对应的号码。
getservbyname()/getservbyport() 由服务名得到对应的端口号或相反,两个函数中,协议名都是可选的。
ntohl()/ntohs() 把一个整数由网络字节序转为主机字节序
htonl()/htons() 把一个整数由主机字节序转为网络字节序
inet_aton()/inet_ntoa() 把IP 地址转为32 位整型,以及反向函数。(仅对IPv4 地址有效)
inet_pton()/inet_ntop() 把IP 地址转为二进制格式以及反向函数。(仅对IPv4 地址有效)
getdefaulttimeout()/ setdefaulttimeout() 得到/设置默认的套接字超时时间,单位秒(浮点数)

socket 网络编程

创建tcp服务器的逻辑

 
 发送端的伪代码如下
import socket 
socket.TCP/IP      # 定义操作类型
connect(a.ip, a.port)   # 访问A 
socket.send("hello")

接收端的伪代码:

import socket
socket.TCP/IP  
listen(0.0.0.0, 6969)   # 具体的网卡的ip, 6969端口号
waiting()  # 为了不让数据卡着
recv()
send

最后,发送端

socket.close()

示例1: 最简单的连接 - 客户端和服务器运行在一台计算机上。

import socket

client = socket.socket()
# 创建套接字,声明socket类型,同时生成socket连接对象
client.connect(("localhost", 6969))


client.send(b"hello world")  # python 3.x只能发bytes类型,只能接受ascii码类型;所以不能直接传中文。 传中文见下
# client.send("如何传中文呢?",encode("utf-8"))    

data = client.recv(1024)
print("recv", data)

client.close()
客户端
import socket

server = socket.socket()

server.bind(("localhost", 6969))  # 绑定监听的端口
server.listen()  # 监听

print("start to listen")
conn, addr = server.accept()  # 等电话,返回连接的标记位 和 对方的ip地址
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn, addr)
print("connected")

data = conn.recv(1024)
print("recv", data)
# 传中文: print("recv", data.decode())
conn.send(data.upper())

server.close()
server端

提示:

  1. 值示主机“localhost”的代码和输出, 或者看到“127.0.0.1”的ip地址,这是代表客户端和服务器在一台计算机上运行。
  2. 传中文时,要encode和decode

以上代码有个问题,就是连接到一个客户端时,只能接受一个request就跳出了。之后需要再次连接。

示例3: 最简单完整的通信连接示例

目的:创建一个tcp服务器,它接受来自客户端的消息,然后将消息加上时间戳前缀并发送回客户端。

from socket import *

from time import ctime

HOST = ''
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST,PORT)


tcpSerSock = socket(AF_INET, SOCK_STREAM)
# 等于 tcpSerSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 因为上文 from socket import *, 所以已经socket属性引入到了命名空间。
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)

while True:
    print("waiting for connection...")
    tcpCliSock, addr = tcpSerSock.accept()
    print("...connected from:", addr)

    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            break
        tcpCliSock.send(b'[%s] %s' %(
            bytes(ctime(), 'utf-8'), data))

    tcpCliSock.close()

tcpSerSock.close()
from socket import *

HOST = "127.0.0.1" # or "localhost"
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

while True:
    data = input(">>>:")
    if not data:
        break
    tcpCliSock.send(data.encode())
    data = tcpCliSock.recv(BUFSIZ)
    if not data:
        break
    print(data.decode("utf-8"))

tcpCliSock.close()

示例4: 模拟ssh访问

以下代码,是在Linux下环境,而且还是python2.7的环境,如果是python 3的话,需要编码和解码。

1. server端

import socket,os   #导入os模块
 
sever = socket.socket()
sever.bind(("127.0.0.1",6969))
sever.listen(5)  #最大允许有多少个链接
while True:
    conn,address = sever.accept()
    print("电话来了")
    count = 0
    while True:
        data = conn.recv(1024)
        if not data:break
        res = os.popen(data).read()   # 调用linux命令
        conn.send(res)   #执行的命令返回值
 
sever.close()

2. 客户端

import  socket
 
client = socket.socket()
client.connect(("localhost",6969))
 
while True:
    msg = raw_input(">>>:")
    if len(msg) == 0:continue
    client.send(msg)
    data  = client.recv(1024)
    print(data)
 
client.close()

conn.send(res)这边如果需要发送全部的话,需要conn.sendall(res)

示例5: 下载文件

1. server端

import socket
 
sever = socket.socket()
sever.bind(("127.0.0.1",6969))
sever.listen()
conn,address = sever.accept()
print("电话来了")
while True:
    data = conn.recv(1024)
    if not data:
        print("数据为空")
        break
    with open("test","rb") as test_file:
        all_data_bytes = test_file.read()   #读取需要下载的文件,发送给客户端
    conn.sendall(all_data_bytes)
 
sever.close()

2. 客户端

import  socket
 
client = socket.socket()
client.connect(("localhost",6969))
while True:
    msg = input(">>>:")
    if len(msg) == 0:continue
    client.send(msg.encode())
    data  = client.recv(1024000)   #这边设置的大一点,防止文件的内容接收不到
    with open("test_put","wb") as test_put_file:   #把下载下来的内容写入到文件中
        test_put_file.write(data)
 
client.close()

注意:这边客户端的接收时有限制的,如果超出了客户端的限制,客户端只接收自己的一部分,而剩余的会在还是在缓冲去,下一次服务端再send的时候,不会发新数据,先把缓冲区剩下的数据发送到客户端。

原文地址:https://www.cnblogs.com/lg100lg100/p/8253949.html