Python Socket网络编程

1. Socket 参数介绍

服务器端

  server = socket.socket(AF.INET,sock.SOCK_STREAM)

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 #废弃了

  These constants represent the socket types, used for the second argument to socket(). More constants may be available depending on the system. (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

 

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)  必会

  Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6AF_UNIXAF_CAN or AF_RDS. The socket type should beSOCK_STREAM (the default), SOCK_DGRAMSOCK_RAW or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted or in the case where the address family is AF_CAN the protocol should be one of CAN_RAW or CAN_BCM. If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd(), fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close().

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

Build a pair of connected socket objects using the given address family, socket type, and protocol number. Address family, socket type, and protocol number are as for the socket() function above. The default family is AF_UNIX if defined on the platform; otherwise, the default is AF_INET.

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

Connect to a TCP service listening on the Internet address (a 2-tuple (host, port)), and return the socket object. This is a higher-level function than socket.connect(): if host is a non-numeric hostname, it will try to resolve it for both AF_INET and AF_INET6, and then try to connect to all possible addresses in turn until a connection succeeds. This makes it easy to write clients that are compatible to both IPv4 and IPv6.

Passing the optional timeout parameter will set the timeout on the socket instance before attempting to connect. If no timeout is supplied, the global default timeout setting returned by getdefaulttimeout() is used.

If supplied, source_address must be a 2-tuple (host, port) for the socket to bind to as its source address before connecting. If host or port are ‘’ or 0 respectively the OS default behavior will be used.

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=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(file, offset=0, count=None)

用socket写一个简单的类似ssh的工具:

  • TCP服务端
    • 创建套接字,绑定套接字到本地IP与端口
    • 开始监听连接
    • 进入循环,不断接受客户端的连接请求
    • 然后接收传来的数据,并发送给对方数据
    • 传输完毕后,关闭套接字
  • TCP客户端
    • 创建套接字,连接远端地址
    • 连接后发送数据和接收数据
    • 传输完毕后,关闭套接字
    • 多个用户交互 

cllient端:

import socket
client = socket.socket() # 声明socket类型,同时生成socket连接对象
client.connect(("localhost",6969))  # 连接端口
while True:
    # client.send(b"Hello World!") # 发送数据,只能发bytes类型数据
    # client.send("发送中文了a".encode("utf-8")) # 不能直接发中文,需要encode转换一下。
    cmd = input(">>>:").strip()
    if len(cmd) == 0:continue      # 判断是否为空
    client.send(cmd.encode("utf-8"))
    res = client.recv(1024)    # 接受返回数据,接受1024字节
    print("recv:",res.decode())
client.close()

 

server端:

send 和 sendall 区别:
  • send 发送数据的大小有限。
  • sendall 循环发送文件。
import socket
server = socket.socket()    # 声明socket类型,同时生成socket连接对象
server.bind(("localhost",6969))  # 绑定要监听的端口
server.listen(5) # 监听,5 最大允许5个连接排队
print("我要开始等电话了")
while True:
    conn,addr = server.accept() # 等待数据:conn 标记位/实例,addr 地址是谁发过来的数据
    while True:
        data = conn.recv(1024)
        if not data:    # 判断接受数据为空退出,
            print("client has lost...")
            break
        print("recv:",data.decode())
        conn.send(data.upper())
     # conn.sendalll(data.upper()) server.close()

 

1.1 循环接受数据

服务器发送的数据大与客户端接受的数据时需要循环接受数据。

输入windows命令:dir ipconfig

  发现输入的是乱的。

  send把数据全部发给客户端,客户端直接收了1024个,剩下的数据放在缓冲区里。在调用send时,先发送缓冲区数据。

import socket,os

client = socket.socket()
client.connect(('127.0.0.1',9999))

while True:
        cmd = input(">>:").strip()
        if len(cmd) == 0:continue
        client.send(cmd.encode("utf-8"))
        cmd_res = client.recv(1024)
        print(cmd_res.decode())
client.close()
客户端
import socket,os

server = socket.socket()
server.bind(('127.0.0.1',9999))

server.listen()

while True:
    conn,addr = server.accept()
    print("一个新的连接:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已经断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("send before")
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output......"
        conn.send(cmd_res.encode())
        print("send done")
server.close()
服务器端

 

解决:接受大小不一致可能时中文的原因,一个中文三个字节

import socket,os

client = socket.socket()
client.connect(('127.0.0.1',8889))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode("utf-8"))
    cmd_res_size = client.recv(1024)    # 接受数据的大小
    print("数据的大小:",cmd_res_size)
    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()):   # 循环接受
        data = client.recv(1024)      # 接受数据
        received_size += len(data)    # 每次接收到的有可能小于1024,所以必须len判断
        received_data += data

    else:
        print("cmd res receive done..... ",received_size)
        print(received_data.decode())   # 接受完成打印数据
client.close()
客户端
import socket,os

server = socket.socket()
server.bind(('127.0.0.1',8889))
server.listen()

while True:
    conn,addr = server.accept()
    print("一个新的连接:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已经断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("before before",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output......"
        # cmd_res.encode() 先encode()在判断长度。
        conn.send( str(len(cmd_res.encode())).encode("utf-8"))   # 发送大小给客户端
        conn.send(cmd_res.encode("utf-8"))  # 发送数据
        print("send done")
server.close()
服务器端

 

1.2 粘包

粘包:连续发送两次send,有时会把两次数据连在一起发送过去。

示例:

conn.send( str(len(cmd_res.encode())).encode("utf-8"))
conn.send(cmd_res.encode("utf-8"))

 

解决方法一:在两次send之间插入一次交互

import socket,os

client = socket.socket()
client.connect(('127.0.0.1',8889))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode("utf-8"))
    cmd_res_size = client.recv(1024)    # 接受数据的大小
    client.send(f"准备好了,数据大小:{cmd_res_size}".encode("utf-8"))   # 客户端回应服务器
    print("数据的大小:",cmd_res_size)
    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()):   # 循环接受
        data = client.recv(1024)      # 接受数据
        received_size += len(data)    # 每次接收到的有可能小于1024,所以必须len判断
        received_data += data
    else:
        print("cmd res receive done..... ",received_size)
        print(received_data.decode())   # 接受完成打印数据
client.close()
客户端
import socket,os

server = socket.socket()
server.bind(('127.0.0.1',8889))
server.listen()

while True:
    conn,addr = server.accept()
    print("一个新的连接:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已经断开")
            break
        print("执行指令:",data)
        cmd_res = os.popen(data.decode()).read()
        print("before before",len(cmd_res))
        if len(cmd_res) == 0:
            cmd_res = "cmd has no output......"
        # cmd_res.encode() 先encode()在判断长度。
        conn.send( str(len(cmd_res.encode())).encode("utf-8"))   # 发送大小给客户端
        client_ack = conn.recv(1024)    # 等待客户端响应
        print("ack from client:",client_ack.decode())
        conn.send(cmd_res.encode("utf-8"))  # 发送数据
        print("send done")
server.close()
服务器

 

方法二:接受数据,如果最后一次只send 700,最后就接受700

发送文件练习:输入:get 空格 加 文件名 (get a.mp4)

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("servr 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
                print("last receive:", size)

            data = client.recv(size)
            received_size += len(data)
            m.update(data)
            f.write(data)
        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()
客户端
import hashlib
import socket ,os
server = socket.socket()
server.bind(('0.0.0.0',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().split()
        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())  # 发送 md5
        print("send done")
server.close()
服务器

 

2. SocketServer

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

socketserver一共有这么几种类型:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. If bind_and_activate is true, the constructor automatically attempts to invoke server_bind() andserver_activate(). The other parameters are passed to the BaseServer base class.
 
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer.

 

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)
These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer.

 

关于SocketServer的使用:

  • 必须自己创建一个请求处理类,并且这个类要继承BaseRequestHandler并且还要重写父类里的handle()
  • 必须实例化TCPServer,并且传递server ip和上面创建的请求处理类给这个TCPServer
  • server.handle_request()只处理一个请求
  • server.server_forever()处理多个请求,永远执行
  • 最后关闭socket

下面是一个socketserver的一个例子:

 1 #AUTHOR:FAN
 2 
 3 import socketserver
 4 #对应上面所说的自己创建一个请求处理类并继承BaseRequestHandler
 5 class MyTCPHandler(socketserver.BaseRequestHandler):
 6 
 7 #对应上面的重写父类里的handle(),切记所有的交互都在handle()里面切记
 8 def handle(self):  
 9         while True:
10             try:
11                 self.data = self.request.recv(1024).strip()
12                 print("{} wrote:".format(self.client_address[0]))
13                 print(self.data)
14                 self.request.sendall(self.data.upper())
15             except ConnectionResetError as e:
16                 print(e)
17                 break
18 if __name__ == "__main__":
19         HOST,PORT = '127.0.0.1',9999
20 #这里对应上面的实例化TCPServer,并传递server ip和上面创建的请求处理类,也就是MyTCPHandler
21 server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)
22 server.serve_forever()
服务器

 

3. 断言

import importlib
obj = importlib.import_module("lib.aa") 
obj = aa.C()
assert type(obj.name) is str
print("dddddd")

  

4.断点续传

#server

import os
import socket

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

home = os.path.join(BASE_DIR,"home/zxg")
ip_port = ('127.0.0.1',9999)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)

while True:
    print("waitting...")
    conn,addr = sk.accept()
    conn.sendall(bytes("欢迎登录",encoding='utf-8'))
    flag = True
    while flag:
        client_bytes = conn.recv(1024)
        client_str = str(client_bytes,encoding='utf-8')

        func,file_name,file_byte_size,target_pat = client_str.split("|",3)
        file_byte_size = int(file_byte_size)
        path = os.path.join(home,file_name)
        has_received = 0

        if os.path.exists(path):
            conn.sendall(bytes("2003",encoding='utf-8'))
            #自定义2003代表该文件已存在
            is_continue = str(conn.recv(1024),encoding='utf-8')
            if is_continue == "2004":
                #2004代表续传
                has_file_size = os.stat(path).st_size
                conn.sendall(bytes(str(has_file_size),encoding='utf-8'))
                has_received += has_file_size
                f = open(path,'ab')
            else:
                f = open(path, 'wb')
        else:
            conn.sendall(bytes("2002",encoding='utf-8'))
            f = open(path,'wb')

        while has_received < file_byte_size:
            try:
                data = conn.recv(1024)
                if not data:
                    raise Exception
            except Exception:
                flag = False
                break

            f.write(data)
            has_received += len(data)

        f.close()
        print("ending !!!")

 

#client

import sys
import socket
import re
import os

ip_port = (('127.0.0.1',9999))
sk =socket.socket()
sk.connect(ip_port)
print(str(sk.recv(1024),encoding='utf-8'))

def bar(num=1,sum=100):
    rate = float(num) / float(sum)
    rate_num = int(rate * 100)
    temp = '\r%d %%' % (rate_num)
    sys.stdout.write(temp)
    sys.stdout.flush()

while True:
        inp = input("pleas input:").strip()
        func,file_path = inp.split("|",1)
        local_path,target_path = re.split('\s*',file_path,1)
        file_byte_size = os.stat(local_path).st_size
        file_name = os.path.basename(local_path)

        post_info = "post|%s|%s|%s"%(file_name,file_byte_size,target_path)
        sk.sendall(bytes(post_info,encoding='utf-8'))

        result_exist = str(sk.recv(1024),encoding='utf-8')
        has_sent = 0
        if result_exist == '2003':
            inp = input("文件存在,是否续传?Y/N:").strip()
            if inp.upper() == "Y":
                sk.sendall(bytes('2004',encoding='utf-8'))
                result_continue_pos = str(sk.recv(1024),encoding='utf-8')
                print(result_continue_pos)
                has_sent = int(result_continue_pos)
            else:
                sk.sendall(bytes("2005",encoding='utf-8'))

        file_obj = open(local_path,"rb")
        file_obj.seek(has_sent)

        while has_sent < file_byte_size:
            data = file_obj.read(1024)
            sk.sendall(data)
            has_sent += len(data)
            bar(has_sent,file_byte_size)
        file_obj.close()
        print("上传成功 !!!")

 

posted @ 2016-09-11 11:24  Crazy丶迷恋  阅读(327)  评论(0编辑  收藏  举报