Python成长之路 socket网络编程

Socket网络编程

socket通常被称作"套接字",应用程序通过"套接字"向网络发出请求或者应答网络请求,是主机或一台计算机上的进程可以通信。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,服务器和客户端各自维护一个"文件",在建立连接打开后,各自的"文件"可以被对方读取和可以向自己的"文件"写入内容 。通讯结束时,关闭各自的"文件"。socket是实现TCP,UDP协议的接口,便于使用TCP、UDP。

socket简单的例子

服务器(server)

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

server = socket.socket()  # 声明socket类型,同时创建socket实例
server.bind(('localhost', 8888))  # 为socket实例绑定IP地址和端口号。

server.listen(5)  # 启动监听,等待客户端的接入请求

while True:
    print('等待客户端接入...')
    conn, addr = server.accept() # Accept阻塞,直到有客户端连接进来
client_data = conn.recv(1024).decode('utf-8')  #  接收客户端的信息
print('客户端的消息:',client_data) 
conn.send('已连接'.encode('utf-8'))  # 向客户端发送消息
server.close() # 关闭连接

客户端 (client)

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

client = socket.socket()  # 创建一个实例
client.connect(('localhost', 8888))  # 连接到服务器


client.send('请求连接'.encode())  # 发送信息给服务器

client_data = client.recv(1024).decode('utf-8')  # 接受服务器反馈的信息
print(client_data)
 client.close()  # 关闭连接

Socket对象常用的方法

服务器端的方法:

  • s.bind() #绑定IP地址和端口(host,port)到套接字,在AF_INET下以元组(host,port)的方式表示
  • s.listen() #开启TCP监听,backlog指定在拒绝连接之前,操作系统的最大挂起数量,至少1一般都为5
  • s.accept() # 被动等待TCP客户端连接,默认为阻塞,等待有客户端连接,程序才会向下走

客户端的方法:

  • s.connect() #主动初始化TCP服务器连接,一般address的格式化为元组(host,port),如果连接错误,返回socket.error错误
  • s.connect() # connect()函数的扩展版本,出错时犯会错误代码,不会抛出异常

公用的方法:

  • s.recv()  #接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略
  • s.send() #发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
  • s.sendall() #完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
  • s.recvfrom() #接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址
  • s.sendto() #发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
  • s.close() #关闭套接字
  • s.getpeername() # 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port) 这个方法以及下面的方法我是没用所以就不详细说了,了解一下就好
  • s.getsockname() #返回套接字自己的地址。通常是一个元组(ipaddr,port)   
  • s.setsockopt(level,optname,value ) #设置给定套接字选项的值。  
  • s.getsockopt(level,optname,value ) # 返回套接字选项的值   
  • s.settimeout(timeout)  # 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
  • s.gettimeout() # 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
  • s.fileno() #返回套接字的文件描述符。
  • s.setblocking(flag) #如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
  • s.makefile() #创建一个与该套接字相关连的文件

使用socket实现简单的远程输入系统命令返回结果值

服务器

import socket
import os

server = socket.socket()
server.bind(('localhost', 8888))
server.listen()
print('等待客户端接入')
while True:
    conn, addr = server.accept()
    print('new conn', addr)
    while True:
        data = conn.recv(1024).decode("utf-8")
        if not data:
            print("客户端断开")
            break
        print("执行指令:", data)
        cmd_res = os.popen(data).read().encode("utf-8")
        if len(cmd_res) == 0:
            cmd_res = '输入的命令不合法'.encode('utf-8')
        conn.send(str(len(cmd_res)).encode('utf-8'))  # 向客户端发送它要接受的数据的大小
        print(str(len(cmd_res)))
        check_res = conn.recv(1024)  # 主要是为了解决粘包的问题
        conn.send(cmd_res)
server.close()

客户端

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

import socket

client = socket.socket()
client.connect(('localhost', 8888))
while True:
    cmd = input('>>>').strip().encode("utf-8")
    if cmd == 'exit'.encode('utf-8'):
        break
    if len(cmd) == 0:
        continue
    client.send(cmd)
    len_res = client.recv(1024).decode("utf-8")
    client.send(bytes('ok',encoding='utf8'))  # 主要是为了在数据太多的时候发生粘包的情况
    # print('返回数据的大小', len_res) 查看要接受多少数据
    received_num = 0
    received_data = ''
    while received_num != int(len_res):  # 主要是实现数据在一次返回中全部接收完毕
        recv_num = client.recv(1024)
        received_num += len(recv_num)
        received_data += str(recv_num)
    print(eval(received_data).decode('utf-8'))
    # cmd_res = client.recv(1024).decode("utf-8")
    # print(cmd_res)

client.close()

粘包;主要是为了解决上面的check_res和下面的返回的数据粘在一起,导致我们的结果不正确。所以在发送数据大小后立马实现接收客户端对数据大小的确认,这样就会是数据产生粘包。

在上面创建socket实例的时候(socket.socket())后面的括号有三个参数

第一个参数:地址簇

  • socket.AF_INET         代表的是使用IPV4(默认)
  • socket.AF_INET6            代表的是IPv6

第二个参数:类型

  • socket.SOCK_STREAM       使用的是TCP(默认)
  • socket.SOCK_DGRAM         使用的是UDP

第三个参数:协议

  • 0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

 下面的列子是根据上面的例子实现远程下载

FTPserver

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

import socket
import os,subprocess
import hashlib

server = socket.socket()
server.bind(('localhost', 8888))
server.listen()
print('等待客户端接入')
while True:
    conn, addr = server.accept()
    print('new conn', addr)
    while True:
        data = conn.recv(1024).decode("utf-8")
        if not data:
            print("客户端{}断开".format(addr))
            break
        cmd, filename = data.split()
        if os.path.isfile(filename):
            f = open(filename, 'rb')
            m = hashlib.md5()
            file_size = str(os.stat(filename).st_size).encode('utf-8')
            conn.send(file_size)
            ack = conn.recv(1024)  # 粘包
            for line in f:
                m.update(line)
                conn.send(line)
            f.close()
            conn.send(m.hexdigest().encode('utf-8'))
        # cmd_res = subprocess.getoutput(data).encode('utf-8')
        # cmd_len = str(len(cmd_res)).encode('utf-8')
        # conn.send(cmd_len)

        # ack = conn.recv(1024)  # 粘包
        # conn.send(cmd_res)
server.close()
client
#!/usr/bin/env python
#-*-coding:utf-8-*-

import socket, hashlib

client = socket.socket()
client.connect(('192.168.132.66', 8888))
while True:
    cmd = input('>>>').strip().encode("utf-8")
    if cmd == 'exit'.encode('utf-8'):
        break
    if len(cmd) == 0:
        continue
    if cmd.decode('utf-8').startswith('get'):
        client.send(cmd)
        file_size = int(client.recv(1024).decode('utf-8'))
        client.send(b'ok')
        filename = cmd.decode('utf-8').split()[1]
        f = open(filename + '.new','wb')
        m = hashlib.md5()
        received_data = 0
        while received_data != file_size:
            if file_size - received_data > 1024:
                size = 1024
            else:
                size = file_size - received_data
            data = client.recv(size)
            f.write(data)
            received_data += len(data)
            m.update(data)
        else:
            print('file recv done',received_data,file_size)
            received_md5 = client.recv(1024).decode('utf-8')
            tota_md5 = m.hexdigest()
            print('发送',received_md5)
            print("接收",tota_md5)
        f.close()
   

client.close()

以上的实例中,客户端必须排队与服务器进行通讯,只有当正在通讯的客户端断开连接才能够到下一个客户端通讯,下面我们将说道多个客户端可以同时和服务器通讯,也就是客户端并发。

多并发服务器

#!/usr/bin/env python
# -*-coding:utf-8-*-
import socketserver  # 实现并发的需要的模块


class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        while True:
            try:
                self.data = self.request.recv(1024).strip().decode("utf-8")
                # self.data 为接收到的信息并转换为utf-8的格式
                print("{} wrote:".format(self.client_address[0]))
                # self.client_address[0]表示的是客户端的IP地址与socket的addr是一样的
                # self.client_address[1]表示的是客户端的端口号与socket的port是一样的
                print(self.data)
                if not self.data:
                    break
                # 上面的判断是客户端自动结束程序关闭连接通道的判断
                self.request.send(self.data.upper().encode('utf-8'))
            except ConnectionResetError as e:
                print("erro", e)
                break
            # 上面的错误处理是直接断开程序做的判断捕获的异常
if __name__ == "__main__":
    HOST, PORT = "localhost", 8888  # 服务器绑定的IP和端口号
    server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
    # 这个实现多并发的关键就在ThreadingTCPServer()的方法实现了可以同时和多个会话
    # 传入端口地址和我们新建的继承自socketserver模块下的BaseRequestHandler类 实例化对象
    server.serve_forever()  # 通过调用对象的serve_forever()方法来激活服务端

 客户端就用简单的客户端就好

#!/usr/bin/env python
# -*-coding:utf-8-*-
import socket
client = socket.socket()
client.connect(("localhost", 8888))

while True:
    msg = input(">>>").strip()
    if not msg:
        continue
    if msg == 'exit':
        break
    client.send(msg.encode("utf-8"))
    received = client.recv(1024)
    print(received.decode("utf-8"))

client.close()

 上面的就是一些基础的socket的应用,可以在上面的基础上深入的使用socket。这些是关于我对socket的简单的初期认识。

原文地址:https://www.cnblogs.com/yang-China/p/8302780.html