day8-python-socket编程

一、TCP/IP协议

第一层:物理层:机械、电子、定时接口通信信道上的原始比特流传输;

第二层:数据链路层:物理寻址,同时将原始比特流转变为逻辑传输线路;

第三层:网络层:控制子网的运行,如逻辑选址、分组传输、路由选择;

第四层:传输层:接受上一层的数据,在必要的时候对数据进行分割,并将这些数据交给网络层,并保证这些数据段有效到达对端;

第五层:会话层:不同机器上的用户之间建立及管理会话;

第六层:表示层:信息的语法语义以及它们的关联,如加密解密、转换翻译、压缩解压缩;

第七层:应用层:各种应用程序协议,如HTTP、FTP、SMTP、POP3。

二、socket概念

socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

 

建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。 

 

 

 socket是计算机网络连接的端点。今天,大多数计算机之间的通信是基于Internet协议的;因此,大多数网络套接字都是Internet的socket。更准确地说,socket是一个句柄(抽象引用),本地程序可以将其传递给网络应用程序编程接口(API)来使用连接,例如“在socket上发送此数据”。socket在内部通常是简单的数字来标识要使用哪个连接。

Socket socket = getSocket(type = "TCP")
connect(socket, address = "1.2.3.4", port = "80")
send(socket, "Hello, world!")
close(socket)

Socket API是一种应用程序编程接口(API),通常由操作系统提供,它允许应用程序控制和使用socket。Internet socket api通常基于Berkeley sockets标准。在Berkeley sockets标准中,socket是文件描述符(文件句柄)的一种形式,这是由于Unix哲学“一切都是文件”,以及socket和文件之间的类比:你可以读、写、打开和关闭它们。在实践中,这种差异意味着这种类比是不恰当的,而在a上使用不同的接口(发送和接收)。

Socket address是IP地址和端口号的组合,很像电话连接的一端是电话号码和特定扩展名的组合。socket不需要地址(例如,仅用于发送数据),但是如果程序将套接字绑定到某个地址,则可以使用套接字接收发送到该地址的数据。基于此地址,internet socket将传入的数据包传递到适当的应用程序进程或线程。

Socket Families(地址簇)

socket.AF_UNIX unix本机进程间通信 

socket.AF_INET IPV4 

socket.AF_INET6  IPV6

Socket Types

socket.SOCK_STREAM  #for tcp

socket.SOCK_DGRAM   #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 方法

socket.socket(family=AF_INETtype=SOCK_STREAMproto=0fileno=None)

使用给定的地址族、套接字类型和协议号创建一个新的套接字。scoket familly应该是AF_INET(默认)、AF_INET6、AF_UNIX、AF_CAN或AF_RDS。socket type应该是SOCK_STREAM(默认)、SOCK_DGRAM、SOCK_RAW或其他SOCK常量之一。协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应该是CAN RAW或CAN BCM之一。如果指定了fileno,则忽略其他参数,从而导致具有指定文件描述符的套接字返回。不像socket.fromfd (), fileno

socket.socketpair([family[, type[, proto]]])

使用给定的地址族、套接字类型和协议号构建一对连接的套接字对象。地址族、套接字类型和协议编号与上面的套接字()函数相同。如果在平台上定义,默认的家庭是AF UNIX;否则,默认是AF INET。

 

socket.create_connection(address[, timeout[, source_address]])

连接到监听Internet地址的TCP服务(a 2-tuple (host, port)),并返回SOCKET对象。connect()是一个比socket.connect()更高层次的函数:如果host是一个非数字主机名,它会尝试同时为AF_INET和AF_INET6解析它,然后尝试依次连接到所有可能的地址,直到连接成功。这使得编写兼容IPv4和IPv6的客户端变得很容易。传递可选的超时参数将在尝试连接之前设置套接字实例的超时。如果没有提供超时,则使用全局缺省超时设置

socket.getaddrinfo(hostportfamily=0type=0proto=0flags=0) #获取要连接的对端主机地址

sk.bind(address)

  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

      backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
      这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

  接收TCP 客户的连接(阻塞式)等待连接的到来

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表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

  返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

  返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

  套接字的文件描述符

socket.sendfile(fileoffset=0count=None)

     发送文件 ,但目前多数情况下并无什么卵用。

例子 

sock_server_ssh.py

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

import socket,os,time
server = socket.socket()
server.bind(('localhost',9999))

server.listen()

while True:
    conn, addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()    #接收字符串,执行结果也是字符串
        print("before send",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output..."
        conn.send(str(len(cmd_res.encode())).encode("utf-8"))    #先发大小给客户端
        # time.sleep(0.5)
        client_ack = conn.recv(1024)    #wait client to confirm
        print("ack from client:",client_ack)
        conn.send(cmd_res.encode("utf-8"))
        print("send done")

server.close()

sock_client_ssh.py

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

import socket
client = socket.socket()

client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:  continue
    client.send(cmd.encode("utf-8"))
    cmd_res_size = client.recv(1024)    #接收命令结果的长度
    # cmd_res = client.recv(1024)
    print("命令结果大小",cmd_res_size)
    client.send("准备好接收了,loser可以发了".encode("utf-8"))
    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()):
        data = client.recv(1024)
        received_size += len(data)#每次收到的有可能小于1024,所以必须用len判断
        # print(data.decode())
        # print(received_size)
        received_data += data
    else:
        print("cmd res receive done...",received_size)
        print(received_data.decode())

client.close()

ftp server
1. 读取文件名
2. 检测文件是否存在
3. 打开文件
4. 检测文件大小
5. 发送文件大小给客户端
6. 等客户端确认
7. 开始边读边发数据
8. 发送md5

sock_server_ftp.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import hashlib
import socket,os,time
server = socket.socket()
server.bind(('localhost',9999))

server.listen()

while True:
    conn, addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        cmd,filename = data.decode().strip()
        print(filename)
        if os.path.isfile(filename):
            f = open(filename,"rb")
            m = hashlib.md5()
            file_size = os.stat(filename).st_size
            conn.send(str(file_size).encode())  #send file size
            conn.recv(1024) #wait for ack
            for line in f:
                m.update(line)
                conn.send(line)
            print("file md5", m.hexdigest())
            f.close()
            conn.send(m.hexdigest().encode())   #send md5

        print("send done")

server.close()

sock_client_ftp.py

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

import socket
import hashlib
client = socket.socket()

client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0: continue
    if cmd.startswith("get"):
        client.send(cmd.encode())
        server_response = client.recv(1024)
        print("server response:",server_response)
        client.send(b"ready to recv file")
        file_total_size = int(server_response.decode())
        received_size = 0
        filename = cmd.split()[1]
        f = open(filename + ".new","wb")
        m = hashlib.md5()
        while received_size < file_total_size:
            if file_total_size - received_size >1024:   #要收不止一次
                size = 1024
            else:   #最后一次了,剩多少收多少
                size = file_total_size - received_size

            data = client.recv(1024)
            received_size += len(data)
            m.update(data)
            f.write(data)
            # print(file_total_size,received_size)
        else:
            new_file_md5 = m.hexdigest()
            print("file recv done",received_size,file_total_size)
            f.close()
            server_file_md5 = client.recv(1024)
            print("server file md5:",server_file_md5)
            print("client file md5:",new_file_md5)

client.close()

三、SocketServer

socketserver模块简化了编写网络服务器的任务。

有四个基本的具体服务器类:

class socketserver.TCPServer(server_addressRequestHandlerClassbind_and_activate=True)

它使用Internet TCP协议,该协议提供了客户机和服务器之间的连续数据流。如果bind和activate为真,构造函数将自动尝试调用server bind()和server activate()。其他参数传递给BaseServer基类。

 

class socketserver.UDPServer(server_addressRequestHandlerClassbind_and_activate=True)

它使用的是数据报,这是离散的信息包,在传输过程中可能会出现混乱或丢失。参数与TCPServer相同。

class socketserver.UnixStreamServer(server_addressRequestHandlerClassbind_and_activate=True)

class socketserver.UnixDatagramServer(server_addressRequestHandlerClass,bind_and_activate=True)

这些不常用的类类似于TCP和UDP类,但是使用Unix域套接字;它们在非unix平台上不可用。参数与TCPServer相同。这四个类同步处理请求;在可以启动下一个请求之前,必须完成每个请求。如果每个请求都需要很长时间才能完成,这是不合适的,因为它需要大量的计算,或者因为它返回大量的数据,而客户机处理这些数据的速度很慢。解决方案是创建一个单独的进程或线程来处理每个请求,可以使用ForkingMixIn和ThreadingMixIn混合-in类来支持异步行为。

继承关系图中有五个类,其中四个表示四种类型的同步服务器

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+

请注意,UnixDatagramServer派生自UDPServer,而不是来自UnixStreamServer。
class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn
可以使用这些混合类创建每种类型服务器的分叉和线程版本。例如,ThreadingUDPServer按如下方式创建:
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

首先是mix-in类,因为它覆盖了UDPServer中定义的一个方法。设置各种属性也会改变底层服务器机制的行为
class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer
这些类是使用mix-in类预先定义的。

Request Handler Objects

class socketserver.BaseRequestHandler

这是所有请求处理程序对象的超类。它定义了如下所示的接口。具体的请求处理程序子类必须定义一个新的handle()方法,并且可以覆盖任何其他方法。为每个请求创建一个子类的新实例。

setup()

在handle()方法之前调用,以执行所需的任何初始化操作。默认实现什么也不做。

handle()

此函数必须完成服务请求所需的所有工作。默认实现什么也不做。它有几个实例属性;请求可以作为self.request;客户端地址为self.client_address;服务器实例为self.server,以防需要访问每个服务器的信息。 self.request对于数据报或流服务的请求是不同的。流服务,self.request是一个套接字对象;对于数据报服务,self.request是一对字符串和套接字。

finish()

在handle()方法之后调用,以执行所需的清理操作。默认实现什么也不做。如果setup()引发异常,则不会调用此函数。

socketserver.TCPServer Example

serverside

import socketserver
 
class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.
 
    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """
 
    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())
 
if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
 
    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
 
    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()

client side

import socket
import sys
 
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
 
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
try:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))
 
    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")
finally:
    sock.close()
 
print("Sent:     {}".format(data))
print("Received: {}".format(received))

上面这个例子你会发现,依然不能实现多并发,哈哈,在server端做一下更改就可以了

server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

改成

server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

 

posted @ 2019-10-12 11:34  罐头1992  阅读(126)  评论(0编辑  收藏  举报