风清扬

导航

Python网络编程之路(一)——Socket初识

本节内容

  1. Socket 概念
  2. Socket 语法
  3. 基于TCP的套接字
  4. 基于UDP的套接字
  5. 比较TCP、UDP
  6. 网络编程
  7. socketserver模块

一、Socket 概念

套接字(Socket):一套接口规范,用于规范化对象与对象的沟通。编程领域一般分两大类:

  • IPC:解决同一台计算机不同程序间通讯,也叫Unix domain socket
  • Network socket: 解决不同计算机通过网络通讯<本节说明对象>

For example, to send "Hello, world!" via TCP to port 80 of the host with address 1.2.3.4, one might get a socket, connect it to the remote host, send the string, then close the socket.

实现一个socket至少要分以下几步,(伪代码)

Socket socket = getSocket(type = "TCP")  #设定好协议类型
connect(socket, address = "1.2.3.4", port = "80") #连接远程机器
send(socket, "Hello, world!") #发送消息
close(socket) #关闭连接

A socket API : is an application programming interface (API), usually provided by the operating system, that allows application programs to control and use network sockets. Internet socket APIs are usually based on the Berkeley sockets standard. In the Berkeley sockets standard, sockets are a form of file descriptor (a file handle), due to the Unix philosophy that "everything is a file", and the analogies between sockets and files: you can read, write, open, and close both. 

socket address is the combination of an IP address and a port number, much like one end of a telephone connection is the combination of a phone number and a particular extension. Sockets need not have an address (for example for only sending data), but if a program binds a socket to an address, the socket can be used to receive data sent to that address. Based on this address, internet sockets deliver incoming data packets to the appropriate application process or thread.

AddressFamily   

socket.AF_UNIX   Unix IPC

socket.AF_INET   IPV4 

socket.AF_INET6   IPV6

These constants represent the address (and protocol) families, used for the first argument to socket(). If the AF_UNIX constant is not defined then this protocol is unsupported. More constants may be available depending on the system.

socket.SOCK_STREAM  TCP

socket.SOCK_DGRAM   UDP

socket.SOCK_RAW

socket.SOCK_RDM

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 语法

The Python interface is a straightforward transliteration of the Unix system call and library interface for sockets to Python’s object-oriented style: the socket() function returns a socket object whose methods implement the various socket system calls. 

socket 对象

import socket 

socket.socket(family=AF_INETtype=SOCK_STREAM[socket.SOCK_DGRAM]proto=0fileno=None)

常用方法

  • socket.bind(address)      绑定socket

    Bind the socket to address. The socket must not already be bound. (The format of address depends on the address family — see above.)

  • socket.listen([backlog])  监听socket

    Enable a server to accept connections. If backlog is specified, it must be at least 0 (if it is lower, it is set to 0); it specifies the number of unaccepted connections that the system will allow before refusing new connections. If not specified, a default reasonable value is chosen.

  • socket.connect(address)        建立socket连接

    Connect to a remote socket at address. (The format of address depends on the address family)

  • socket.recv(bufsize[, flags])    收消息

    Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified by bufsize. Note:For best match with hardware and network realities, the value of bufsize should be a relatively small power of 2, for example, 4096.

  • socket.send(bytes[, flags])       发消息

  Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.

三、基于TCP的套接字

通信模型

            《TCPSOCKET工作原理图》

TCP编程框架

服务端伪代码:

ss = socket() # 创建服务器套接字
ss.bind() # 套接字与地址绑定
ss.listen() # 监听连接
inf_loop: # 服务器无限循环
 cs = ss.accept() # 接受客户端连接
 comm_loop: # 通信循环
  cs.recv()/cs.send() # 对话(接收/发送)
 cs.close() # 关闭客户端套接字
ss.close() # 关闭服务器套接字#(可选)

客户端伪代码:

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

实现一个类ssh工具  

#编写一个类ssh 工具
from socket import *
import os
import subprocess

#服务端
def server():
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(sock)
    server.listen(5)
    while True:
        print("服务器运行中,等待连接...")
        conn,addr = server.accept()
        print("已连接来自: ",addr)
        while True:
            recv_cmd = conn.recv(buf_size)
            if not recv_cmd:
                print("客户端已断开连接")
                break
            cmd = recv_cmd.decode('utf-8')
            # res_cmd = os.popen(cmd).read() #与下边等同 str
            #平台自动编码,windows:gbk,linux:utf-8
            res_cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
            if not res_cmd: res_cmd ='此条命令没有返回值'.encode('gbk')

            #防止粘包,提前告知对方包的大小
            conn.send(str(len(res_cmd)).encode('utf-8'))
            data = conn.recv(buf_size)
            if not data:
                print("客户端已断开连接")
                break
            conn.sendall(res_cmd)
        conn.close()

#客户端
def client():
    client = socket(AF_INET,SOCK_STREAM)
    client.connect(sock)
    while True:
        cmd = input('>').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8')) #有可能存在中文
        #防止粘包,提前确认包的大小
        res_len = int(client.recv(buf_size))
        client.send(f"已经接收到大小为:{res_len}的数据发吧".encode('utf-8'))
        receive_size = 0 # 已接受的数据大小
        res_cmd = b''
        while receive_size < res_len:
            data = client.recv(buf_size)
            receive_size += len(data)
            res_cmd +=data
        print(res_cmd.decode('gbk'))
    client.close()

if __name__ == '__main__':
    sock = ('127.0.0.1',23456)
    buf_size = 512 #buf调小,可以测试粘包现象
    choice = input('chooose the node:>')
    if choice == 's':
        server()
    elif choice == 'c':
        client()
    else:
        print("输入有误")
        exit(0)  

四、基于UDP的套接字

通信模型

UDP编程框架

服务端伪代码:

ss = socket() # 创建服务器套接字
ss.bind() # 绑定服务器套接字
inf_loop: # 服务器无限循环
 cs = ss.recvfrom()/ss.sendto() # 关闭(接收/发送)
ss.close() # 关闭服务器套接字 

客户端伪代码:

cs = socket() # 创建客户端套接字
comm_loop: # 通信循环
 cs.sendto() / cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户端套接字  

五、比较TCP、UDP

  • TCP基于连接(通讯前先连接),UDP无连接
  • TCP更消耗资源
  • TCP就传输层比UDP更可靠
  • TCP基于流,UDP基于数据报文。TCP:缓存可以看作一个蓄水池,发和收一头是流入一头是流出,相对独立,即你可以发送多次但我收一次。UDP为无连接的,数据组装成报文格式,你发一个报文一般情况下就要收一个(尽可能多的收取)

注:这里仅指传输层协议特点比较。并不是说UDP协议开发的程序就不可靠而是它的可靠性交由上层程序来控制

六、网络编程

按照ISO7层协议,涉及每一层的代码编写都应该属于网络编程。但传输层及其以下内容由硬件厂家和操作系统完成了,用户只需编写各自的应用即可

Python文档中把Socket 编程定义为: Low-level networking interface。so一般网络编程包含:

  • 基于Socket类编程:邮件服务器、FTP服务器、Web服务器等等
  • Web服务:已有Web服务器程序(容器),用户编写业务类程序,通常也叫"后台业务程序"

 

七、socketserver

The socketserver module simplifies the task of writing network servers. 即:socket编程的框架,用面向对象来实现,简化了socket编程工作

简述

  • TCPServer/UDPServer:创建网络同步的TCP/UDP服务器
  • ThreadTCPServer/UDPServer:可创建多线程TCP/UDP服务器,ForkingMixIn 和上边类的组合
  • BaseRequstHander:处理通讯循环业务

 

多线程同步TCP服务器

#server 端

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    #相当于一个connect,每个客户端来都实例化一次。内部编写通讯循环
    def handle(self):
        print(self)
        print("{} wrote:".format(self.client_address))
        while True:
            self.data = self.request.recv(1024).strip()
            if not self.data :break
            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
    # server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) #单线程
    server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) #多线程
    server.serve_forever()

多线程异步TCP服务器

import socket
import threading
import socketserver

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        data = self.request.recv(1024)
        cur_thread = threading.current_thread()
        response = "{}: {}".format(cur_thread.name, data)
        self.request.sendall(response.encode('utf-8'))

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

def client(ip, port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    try:
        sock.sendall(message.encode('utf-8'))
        response = sock.recv(1024)
        print("Received: {}".format(response))
    finally:
        sock.close()

if __name__ == "__main__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "localhost", 9999
    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    ip, port = server.server_address

    # Start a thread with the server -- that thread will then start one
    # more thread for each request
    server_thread = threading.Thread(target=server.serve_forever)
    # Exit the server thread when the main thread terminates
    server_thread.daemon = True
    server_thread.start()
    print("Server loop running in thread:", server_thread.name)

    client(ip, port, "Hello World 1")
    client(ip, port, "Hello World 2")
    client(ip, port, "Hello World 3")

    server.shutdown()
    server.server_close()

  

posted on 2019-01-30 22:06  卜戈的博客  阅读(285)  评论(0)    收藏  举报