Python:网络编程

套接字

套接字是一种“通信端点”概念的计算机网络数据结构。用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过”套接字”向网络发出请求或者应答网络请求。他们的底层结构都是基于文件系统来支持的。

面向连接与无连接

面向连接的协议是TCP协议(传输控制协议,TCP协议是面向连接、保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达)传输层协议),这个协议通信之前需要三次握手。要创建TCP套接字就得在创建的时候指定套接字的类型为SOCK_STREAM,这个名字表达了他作为流套接字的特点。
与之相反的连接就是无连接的套接字,这时,数据到达的顺序,可靠性以及不重复性就无法保证了,适用于视频这些要求速度的场景。实现这种连接的协议就是UDP(用户数据报协议),要创建UDP套接字就必须在创建的时候指定套接字类型为SOCK_DGRAM。

网络编程

socket模块

首先使用socket.socket()函数来创建套接字
socket.socket(socket_family,socket_type,protocol=0)
参数一:地址簇
  socket.AF_INET IPv4(默认)
  socket.AF_INET6 IPv6
  socket.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 可靠的连续数据包服务
  
参数三  
protocol一般不填,默认为0,特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

套接字对象的内建方法

s=socket.socket()

服务器端套接字函数

s.bind(address)
绑定地址元组(主机名,端口号)到套接字

s.listen(backlog))
开始tcp监听

s.accept()
被动接受tcp客户端连接,(阻塞式)等待连接的到来,返回客户端套接字对象和客户端地址

客户端套接字函数

s.connect(address)
主动初始化TCP服务器连接

s.connect_ex( address)
connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数

s.recv(buffersize, flags=None)
接受tcp数据

s.send(data, flags=None)
发生tcp数据

s.sendall(data, flags=None)
完整发生tcp数据

s.recvfrom( buffersize, flags=None)
接受udp数据

s.sendto( data, flags=None, *args, **kwargs)
发生udp数据

s.getpeername()
连接到当前套接字的远端地址

s.getsockname()
当前套接字的地址

s.getsockopt(level, option, buffersize=None)
返回指定套接字的参数

s.setsockopt()
设置指定套接字的参数

s.close()
关闭套接字

s.ioctl(cmd, option)
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
限定一个系统接口

面向模块的套接字函数

s.setblocking( flag)
设置套接字的阻塞与非阻塞模式

s.settimeout()
设置阻塞套接字操作的超时时间

s.gettimeout(timeout)
得到阻塞套接字的超时时间

面向文件的套接字函数

s.fileno()
套接字的文件描述符

s.makefile()
创建一个与该套接字的关联的文件对象

创建TCP客户端与服务器通信

client.py

import socket

con=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
con.connect(('192.168.88.132',8888))

while True:
    try:
        user=raw_input("\033>>>\033:")
        con.sendall(user)
    except Exception:
        break
    print str(con.recv(1024))
con.close()

server.py

#coding:utf-8

import socket

server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

server.bind(('0.0.0.0',8888))
server.listen(5)


while True:
    print "listenning...."
    con,add=server.accept()
    while True:
        try:
            print con.recv(1024)
            message=raw_input("\033>>>\033:")
            con.sendall(message)
        except Exception:
            break
    con.close()

运行效果
这里写图片描述
这里写图片描述

写一个简易是ssh通信
client.py

#coding:utf-8

import socket

con=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
con.connect(('192.168.88.132',8888))

while True:
    try:
        user=raw_input("\033>>>\033:")
        if  not user:
            con.close()
            exit(0)
        con.sendall(user)
    except Exception:
        break
    src=""
    #下面这段代码其实是由问题的,测试一下就知道了
    while True:
        data=con.recv(1024)
        src+=data
        if len(data)<1024:
            break
    print src
con.close()

server.py

#! /usr/bin/env python
#coding:utf-8

import socket
import subprocess

server=socket.socket()

server.bind(('0.0.0.0',8888))
server.listen(5)

while  True:
    print("start listenning....")
    try:
        conn,addr=server.accept()

    except Exception :
        continue
    while True:
        try:
            cmd=conn.recv(1024)
            if not cmd:
                print "connect break"
                break
        except:
            break
        cmd=cmd.strip()

        out=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE)
        out=out.stdout.read()
        if not out:
            out="command show  null"
        conn.sendall(out)

    conn.close()

这个简易的ssh有一个bug,那就是当服务器需要回显的数据特别多的时候(比如输入 top -bn 2 这条命令的时候),由于网络质量的原因,客户端无法接受完全,当我们再次输入命令的时候,服务器会将上一次没有返回完的数据继续返回,这就不是我们所期望的了。正常这样问题的原因就是因为虽然我们在代码中指定了每次接受的数据为1024,但是由于网络或者其他原因,有可能其中一些数据达不到1024,代码就中断了,导致没有接受完服务器的返回的所有数据
改进版,就是在接受数据前,先让客户端知道接收的数据的大小,然后通过接收数据的大小老判断是否完成数据接收
client.py

#coding:utf-8


import socket

con=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
con.connect(('192.168.88.132',8888))

while True:
    try:
        user=raw_input("\033>>>\033:")
        if  user is None:
            con.close()
            exit(0)
        con.sendall(user)

    except Exception:
        break
    #########################################
    content=con.recv(500)  #先接受数据大小

    #确认准备好接收数据
    ack=con.send('ack')  #防止连包
    if ack!='ack':
        break
    #获取数据的大小
    contentList=content.split('|')
    if contentList[0]=='content_size':
        contentSize=int(contentList[1])  
    else:
        contentSize=0
    if contentSize==0:
        print "recv error"
        continue
   #开始接受数据
    src=""
    recvSize=0
    while recvSize<contentSize: #循环接受数据
        data=con.recv(1024)
        src+=data
        recvSize+=len(data)
########################################################
    print src
con.close()

server.py

#! /usr/bin/env python
#coding:utf-8

import socket
import subprocess

server=socket.socket()

server.bind(('0.0.0.0',8888))
server.listen(5)

while  True:
    print("start listenning....")
    try:
        conn,addr=server.accept()

    except Exception :
        continue
    while True:
        try:
            cmd=conn.recv(1024)
            if not cmd:
                print "connect break"
                break
        except:
            break
        cmd=cmd.strip()

        out=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE)
        ######################################
        out=out.stdout.read()
        if not out:
            out="command show  null"
        conn.sendall("content_size|%s"%len(out))  #先发送数据大小
        conn.recv(10)  #防止python中的连包,就是连续两个包一起发送
        ######################################
        conn.sendall(out)

    conn.close()

Socket模块属性

数据属性

AF_UNIX,AF_INET,AF_INET6
Python支持的套接字家族

SO_STREAM,SO_DGRAM
套接字类型

has_ipv6
表示是否支持ipv6的布尔类型标志

函数

socket()
用指定的地址家族,套接字类型和协议类型来创建一个套接字对象

socketpair()
用指定的地址家族,套接字类型和协议类型创建一对套接字对象

fromfd()
用一个已经打开的文件描述符创建一个套接字对象

ssl()
在套接字上发起一个安全套接字层(ssl),不能证书验证

getaddrinfo(host, port, family=None, socktype=None, proto=None, flags=None)
得到地址信息

>>> socket.getaddrinfo('www.baidu.com',80)
[(2, 0, 0, '', ('180.97.33.108', 80)), (2, 0, 0, '', ('180.97.33.107', 80))]
>>>

getfqdn(name=”)
返回完整的域的名字

gethostname()
得到当前主机名

>>> socket.gethostname()
'cmustard'
>>>

gethostbyname(host)
由主机名得到对应的Ip地址

>>> socket.gethostbyname('cmustard')
'169.254.185.214'
>>>

gethostbyname_ex(host)
返回主机名,主机所有的别名个ip地址列表

'169.254.185.214'
>>> socket.gethostbyname_ex('cmustard')
('cmustard', [], ['169.254.185.214', '192.168.88.1', '192.168.152.1', '172.17.135.13
'])
>>>

gethostbyaddr(host)
由ip地址得到DNS信息,返回一个三元组

getprotobyname(name)
由协议名得到对应的号码

>>>
>>> socket.getprotobyname('tcp')
6
>>> socket.getprotobyname('udp')
17
>>> socket.getprotobyname('icmp')
1

ntoh1(integer)/ntohs(integer)
把一个整型由网络字节序转换成主机字节序

htonl(integer)/htons(integer)
把一个整型由主机字节序转换成网络字节序

inet_aton(string)/inet_ntoa(packed_ip)
把ip地址转换成32位整型,会反之

inet_pton()/inet_ntop()
把ip地址转换成二进制格式或反之

SocketServer模块

SocketServer是标准库中一个高级别的模块,用于简化简化实现网络客户端与服务器的代码

SocketServer模块的类

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

BaseServer
这个类只用于派生

TCPServer/UDPServer
基本的网络同步TCP/UDP服务器

UnixStreamServer/UnixDatagramServer
基本的基于文件同步的TCP/UDP服务器

ForkingMinIn/ThreadingMixIn
实现核心进程化和线程化的功能,作为混合类,与服务器类一并使用以提供一些异步特性

ForkingTCPServer/ForkingUDPServer
ForkingMinIn和TCPServer/UDPServer的组合(多进程)

ThreadingTCPServer(server_address, RequestHandlerClass)/ThreadingUDPServer(server_address, RequestHandlerClass)
ThreadingMaxIn和TCPServer/UDPServer的组合(多线程)常用
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互

BaseRequestHandler
包含处理服务请求的核心功能,这个类只用于派生

StreamRequestHandler/DatagramRequestHandler
用于TCP/UDP服务器的服务处理工具

from SocketServer import ThreadingTCPServer,BaseRequestHandler


class MyTCPServer(BaseRequestHandler):
    #必须重写这个方法
    def handle(self):
        print "come from:%s"%self.client_address
        while True:
            data=self.request.recv(1024)
            if not data:
                self.request.close()
                print "Bingo."
                break
            print ">>>",str(data)
            self.request.sendall('hello')


if __name__ == '__main__':
    print "start listenning...."
    s=ThreadingTCPServer(('0.0.0.0',8088),MyTCPServer)
    #一次处理一个请求直到shutdown
    #serve_forever(poll_interval=0.5)
    s.serve_forever()

接下里解析一些这个BaseRequestHandler类

class BaseRequestHandler:

    """Base class for request handler classes.
    首先这个类是所有用于请求处理的类的基类
    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request(这个构造器__init__设置这个实例的请求变量), client_address(包括客户端地址,包括ip和端口)
    and server(服务器的套接字对象), and then calls the handle() method(然后我们需要申明一个handle方法).  To implement a
    specific service(这个方法是为了实现我们自定义的数据的接受和其他功能), all you need to do is to derive a class which
    defines a handle() method.(因此这个handle方法必须重写的)

    The handle() method can find the request as self.request(self.request可以用来处理客户端的数据,接受和发送), the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define arbitrary other instance variariables.

    """

    def __init__(self, request, client_address, server):
        self.request = request   #这个用来处理客户端的
        self.client_address = client_address #这个是表示客户端的地址和端口
        self.server = server  #这个是表示服务端的
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass

然后就是ThreadingTCPServer类

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
继承自ThreadingMixInTCPServer

class ThreadingMixIn:
    """Mix-in class to handle each request in a new thread."""
    #这个类是对于每一个新的请求启用一个新的线程


class TCPServer(BaseServer)   
"Base class for various socket-based server classes. "  
#这个类是一系列基于socket服务的基类

ThreadingTCPServer类启动服务端程序
执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 …
当客户端连接到达服务器
执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
执行 ThreadingMixIn.process_request_thread 方法
执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值