28、初识socket(subprocess模块)

经过近一个半月的学习我们已经度过了python基础的阶段,今天我们开始学习python网络编程,没有难以理解的逻辑,更注重的是记忆。

 

本篇导航:

 

对网络协议和基础没有概念的可以在阅读本文前预习计算机基础3、网络协议:http://www.cnblogs.com/liluning/p/7170799.html

一、客户端/服务器架构

1、C/S结构,即Client/Server(客户端/服务器)结构

2、我们在互联网中处处可见c/s架构比如说浏览器,qq,lol,视频软件。。。

3、我们学习socket就是为了c/s架构的开发


 

二、scoket与网络协议

首先我们需要实现网络通信必然需要多网络协议有着深刻的了解。而我们做开发如果把每一点都搞清楚太过于耗费精力,所以我们才有了scoket

socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的


 

三、套接字

 套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

1、基于文件类型的套接字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

2、基于网络类型的套接字:AF_INET

还有AF_INET6被用于ipv6,还有一些其他的地址家族,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET

3、套接字函数的认识

首先我们要知道所有的函数使用都需要导入socket模块

1)socket()模块

import socket
获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

2)服务端套接字函数

bind()    绑定(主机,端口号)到套接字
listen()  开始TCP监听
accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

3)客户端套接字函数

connect()     主动初始化TCP服务器连接
connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

4)公共用途的套接字函数

recv()            接收TCP数据
send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
recvfrom()        接收UDP数据
sendto()          发送UDP数据
getpeername()     连接到当前套接字的远端的地址
getsockname()     当前套接字的地址
getsockopt()      返回指定套接字的参数
setsockopt()      设置指定套接字的参数
close()           关闭套接字

5)面向锁的套接字函数

setblocking()     设置套接字的阻塞与非阻塞模式
settimeout()      设置阻塞套接字操作的超时时间
gettimeout()      得到阻塞套接字操作的超时时间

6)面向文件的套接字函数

fileno()          套接字的文件描述符
makefile()        创建一个与该套接字相关的文件

 

四、基于tcp的套接字代码实现

前面一次性近二十个函数是不是看蒙蔽了 来看看实际应用其实应用很简单

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

tcp服务端

ss = socket()      #创建服务器套接字
ss.bind()      #把地址绑定到套接字
ss.listen()      #监听链接
while True :      #服务器无限循环
    conn,client_addr=ss.accept()      #接受客户端链接
    while True :      #通讯循环
        conn.recv()/cs.send()      #对话(接收与发送)
    conn.close()      #关闭客户端套接字
ss.close()      #关闭服务器套接字(可选)

tcp客户端

cs = socket()    # 创建客户套接字
cs.connect()    # 尝试连接服务器
while True :        # 通讯循环
    cs.send()/cs.recv()    # 对话(发送/接收)
cs.close()            # 关闭客户套接字

1、socket通信流程与打电话流程类似以此为例:

服务端:

import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #买手机
s.bind('127.0.0.1',8080)  #手机插卡
s.listen(5)  #开机

#print('starting...')
conn,addr=s.accept()  #等电话 (链接,客户的的ip和端口组成的元组)
#print(conn,addr)

data=conn.recv(1024)  #接收
print('client data: <%s>' %data)
conn.send(data.upper())  #发送

conn.close()  #挂电话
s.close()  #关机

客户端:

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
phone.connect(('127.0.0.1',8080)) #绑定手机卡

#发,收消息
phone.send('hello'.encode('utf-8'))
data=phone.recv(1024)
print('server back res:<%s>' %data)

phone.close()

2、加上链接循环与通信循环

服务端:

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
phone.bind(('127.0.0.1',8081)) #绑定手机卡
phone.listen(5) #开机 最多挂起5个服务

print('starting...')
while True: #链接循环
    conn,client_addr=phone.accept() #等电话 (链接,客户的的ip和端口组成的元组)
    print('-------->',conn,client_addr)

    #收,发消息
    while True:#通信循环
        try:
            data=conn.recv(1024)
            if not data:break #针对linux
            print('client data: <%s>' %data)
            conn.send(data.upper())
        except Exception:
            break
    conn.close() #挂电话
phone.close() #关机

客户端(可以有几个客户端一起链接后面的被挂起前一个断开后立马链接):

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
phone.connect(('127.0.0.1',8081)) #绑定手机卡

#发,收消息
while True:
    msg=input('>>: ').strip()
    if not msg:continue
    phone.send(msg.encode('utf-8'))
    data=phone.recv(1024)
    print('server back res:<%s>' %data)

phone.close()

3、模拟ssh远程模拟命令

服务端:

import socket
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8082))
phone.listen(5)

print('starting...')
while True:
    conn,client_addr=phone.accept()
    print('-------->',conn,client_addr)

    while True:
        try:
            cmd=conn.recv(1024)
            #if not cmd:break #针对linux
            #执行cmd命令,拿到cmd的结果,结果应该是bytes类型
            #。。。。
            res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout=res.stdout.read()
            stderr=res.stderr.read()

            #发送命令的结果
            conn.send(stdout+stderr)
        except Exception:
            break
    conn.close() #挂电话
phone.close() #关机

客户端:

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
phone.connect(('127.0.0.1',8082))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    cmd_res=phone.recv(1024)
    print(cmd_res.decode('gbk'))
phone.close()

 

五、异常解决(了解)

解决方法:

#加入一条socket配置,重用ip和端口

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf

编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
 
然后执行 /sbin/sysctl -p 让参数生效。
 
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
linux系统方法

 

六、subprocess模块(简单介绍)

允许你去创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等

Popen类

subprocess模块中定义了一个Popen类,通过它可以来创建进程,并与其进行复杂的交互。查看一下它的构造函数:

__init__(self, args, bufsize=0, executable=None, 
stdin=None, stdout=None, stderr=None, preexec_fn=None, 
close_fds=False, shell=False, cwd=None, env=None, 
universal_newlines=False, startupinfo=None, 
creationflags=0)

主要参数说明: 
args:args should be a string, or a sequence of program arguments.也就是说必须是一个字符串或者序列类型(如:字符串、list、元组),用于指定进程的可执行文件及其参数。如果是一个序列类型参数,则序列的第一个元素通常都必须是一个可执行文件的路径。当然也可以使用executeable参数来指定可执行文件的路径。

stdin,stdout,stderr:分别表示程序的标准输入、标准输出、标准错误。有效的值可以是PIPE,存在的文件描述符,存在的文件对象或None,如果为None需从父进程继承过来,stdout可以是PIPE,表示对子进程创建一个管道,stderr可以是STDOUT,表示标准错误数据应该从应用程序中捕获并作为标准输出流stdout的文件句柄。

shell:如果这个参数被设置为True,程序将通过shell来执行。 
env:它描述的是子进程的环境变量。如果为None,子进程的环境变量将从父进程继承而来。

实例化:

res = subprocess.Popen(r'dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

 

posted @ 2017-08-21 19:35  布吉岛丶  阅读(1077)  评论(0编辑  收藏  举报