Programación de Redes con Sockets

  • I. Arquitectura Cliente/Servidor
  • II. Modelo OSI de siete capas
  • III. Capa de Sockets
  • IV. ¿Qué es un Socket?
  • V. Historia y clasificación de los sockets
  • VI. Flujo de trabajo de los sockets
  • VII. Sockets basados en TCP
  • VIII. Sockets basados en UDP
  • IX. Fenómeno de paquetes pegados
  • X. ¿Qué es el fenómeno de paquetes pegados?
  • XI. Soluciones básicas para paquetes pegados
  • XII. Métodos para resolver paquetes pegados
  • XIII. Autenticación de conexiones de clientes
  • XIV. Implementación de concurrencia con socketserver

I. Arquitectura Cliente/Servidor

  1. Arquitectura C/S de hardware (impresoras)
  2. Arquitectura C/S de software

Internet está lleno de arquitecturas C/S

Por ejemplo, los sitios web son servidores y tu navegador es un cliente (la arquitectura B/S es un tipo de C/S)

Tencent actúa como servidor para proporcionarte videos, y debes descargar su cliente de video para verlos

Relación entre C/S y sockets:

Aprendemos sockets para desarrollar aplicaciones C/S

II. Modelo OSI de siete capas

Introducción:

Un sistema informático completo consta de hardware, sistema operativo y software de aplicación. Con estos tres elementos, un sistema puede funcionar de forma autónoma (jugar juegos offline, jugar al Buscaminas, etc.)

Si quieres jugar con otros, necesitas conectarte a Internet. ¿Qué es Internet?

El núcleo de Internet está compuesto por una serie de protocolos. Un protocolo es un estándar, como el inglés que utilizan las personas para comunicarse a nivel mundial.

Si comparamos una computadora con una persona, los protocolos de Internet son el inglés del mundo de las computadoras. Cuando todas las computadoras aprenden estos protocolos, pueden comunicarse siguiendo estándares unificados.

Las personas han dividido lógicamente los protocolos de Internet en diferentes niveles según sus funciones.

Ver más sobre principios de comunicación de red: https://www.cnblogs.com/wangcheng9418/p/9268248.html

¿Por qué debemos aprender los protocolos de Internet antes de estudiar sockets?

1. Primero: el objetivo de este curso es enseñarte a programar con sockets para desarrollar tu propia aplicación C/S

2. Segundo: las aplicaciones C/S (que pertenecen a la capa de aplicación) se comunican a través de la red

3. Luego: el núcleo de la red es una colección de protocolos, y los protocolos son estándares. Si quieres desarrollar un software basado en comunicación de red, debes seguir estos estándares.

4. Finalmente: comencemos a estudiar estos estándares y emprendamos nuestro viaje de programación con sockets

Figura 1

III. Capa de Sockets

En la Figura 1, no vemos la sombra de Socket. Entonces, ¿dónde está? Usemos una imagen para dejarlo claro.

Figura 2

IV. ¿Qué es un Socket?

Socket es una capa de abstracción de software intermedia entre la capa de aplicación y la familia de protocolos TCP/IP. Es un conjunto de interfaces. En los patrones de diseño, Socket es en realidad un patrón Fachada (Facade), que oculta la compleja familia de protocolos TCP/IP detrás de las interfaces de Socket. Para el usuario, un conjunto simple de interfaces es todo, y Socket organiza los datos para que cumplan con los protocolos especificados.

Por lo tanto, no necesitamos entender profundamente los protocolos TCP/UDP, ya que Socket nos ha encapsulado. Solo necesitamos seguir las especificaciones de Socket para programar, y los programas que escribiremos naturalmente seguirán los estándares TCP/UDP.

Algunos también describen Socket como IP+puerto, donde IP identifica la ubicación de un host en Internet, y puerto identifica una aplicación en esa máquina. La dirección IP se configura en la tarjeta de red, mientras que el puerto se abre cuando se inicia la aplicación. La unión de IP y puerto identifica de forma única una aplicación en Internet.

El PID de un programa es un identificador para diferentes procesos o hilos en la misma máquina

Conceptos básicosV. Historia y clasificación de los sockets

Los sockets originaron en la década de 1970 en la versión Unix de la Universidad de California, Berkeley, conocida como BSD Unix. Por esta razón, a veces a los sockets se les llama "sockets Berkeley" o "BSD sockets". Inicialmente, los sockets se diseñaron para la comunicación entre múltiples aplicaciones en la misma máquina, lo que se conoce como comunicación entre procesos (IPC). Existen dos tipos (o razas) de sockets: los basados en archivos y los basados en red.

Sockets basados en archivos

Familia de sockets: AF_UNIX

En Unix, todo es un archivo. Los sockets basados en archivos utilizan el sistema de archivos subyacente para transferir datos. Dos procesos de socket en la misma máquina pueden comunicarse indirectamente accediendo al mismo sistema de archivos.

Sockets basados en redFamilia de sockets: AF_INET

(También existe AF_INET6 para IPv6, y otras familias de direcciones, pero la mayoría de ellas se usan solo en plataformas específicas, están obsoletas, rara vez se usan o simplemente no están implementadas. De todas las familias de direcciones, AF_INET es la más utilizada. Python admite muchas familias de direcciones, pero como solo nos interesa la programación de red, generalmente usamos solo AF_INET)

VI. Flujo de trabajo de los sockets

Imagina una situación de la vida real. Quieres llamar a un amigo, primero marcas el número, tu amigo escucha el timbre y levanta el teléfono, en ese momento se establece la conexión y pueden hablar. Cuando terminan la conversación, cuelgan el teléfono y finaliza la conversación. Este escenario de la vida real explica el principio de funcionamiento.

Figura 3

Comencemos con el lado del servidor. El servidor primero inicializa el Socket, luego lo enlaza a un puerto (bind), escucha en el puerto (listen), y llama a accept para bloquearse y esperar a que el cliente se conecte. En este momento, si un cliente inicializa un Socket y se conecta al servidor (connect), y si la conexión es exitosa, se establece la conexión entre el cliente y el servidor. El cliente envía una solicitud de datos, el servidor recibe la solicitud, la procesa y envía una respuesta al cliente, el cliente lee los datos y finalmente cierra la conexión, finalizando una interacción.

Uso de funciones del módulo socket()

 1 import socket
 2 socket.socket(socket_family,socket_type,protocal=0)
 3 socket_family puede ser AF_UNIX o AF_INET. socket_type puede ser SOCK_STREAM o SOCK_DGRAM. protocol generalmente no se llena, el valor predeterminado es 0.
 4 
 5 Obtener socket TCP/IP
 6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 7 
 8 Obtener socket UDP/IP
 9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
10 
11 Dado que el módulo socket tiene demasiados atributos, aquí hemos usado la declaración 'from module import *' como excepción. Usando 'from socket import *', llevamos todos los atributos del módulo socket a nuestro espacio de nombres, lo que puede acortar significativamente nuestro código.
12 Por ejemplo tcpSock = socket(AF_INET, SOCK_STREAM)

<em><strong>Funciones de socket del servidor</strong></em><br></br>s.bind()    Enlazar (host, puerto) al socket<br></br>s.listen()  Iniciar escucha TCP<br></br>s.accept()  Aceptar pasivamente conexiones TCP de clientes (bloqueante), esperando la conexión<br></br><br></br><em><strong>Funciones de socket del cliente</strong></em><br></br>s.connect()     Inicializar activamente la conexión al servidor TCP<br></br>s.connect_ex()  Versión extendida de la función connect(), devuelve el código de error en caso de error, en lugar de lanzar una excepción<br></br><br></br><em><strong>Funciones de socket de uso común</strong></em><br></br>s.recv()            Recibir datos TCP<br></br>s.send()            Enviar datos TCP (send pierde datos si la cantidad de datos a enviar es mayor que el espacio restante en el búfer del lado emisor)<br></br>s.sendall()         Enviar datos TCP completos (en esencia, es una llamada cíclica a send, sendall no pierde datos si la cantidad de datos a enviar es mayor que el espacio restante en el búfer del lado emisor, llama cíclicamente a send hasta enviar todo)<br></br>s.recvfrom()        Recibir datos UDP<br></br>s.sendto()          Enviar datos UDP<br></br>s.getpeername()     La dirección del extremo remoto conectado al socket actual<br></br>s.getsockname()     La dirección del socket actual<br></br>s.getsockopt()      Devolver los parámetros del socket especificado<br></br>s.setsockopt()      Establecer los parámetros del socket especificado<br></br>s.close()           Cerrar el socket<br></br><br></br><em><strong>Métodos de socket orientados a bloqueo</strong></em><br></br>s.setblocking()     Establecer el modo de bloqueo y no bloqueo del socket<br></br>s.settimeout()      Establecer el tiempo de espera para operaciones de socket bloqueantes<br></br>s.gettimeout()      Obtener el tiempo de espera para operaciones de socket bloqueantes<br></br><br></br><em><strong>Métodos de socket orientados a archivo</strong></em><br></br>s.fileno()          Descriptor de archivo del socket<br></br>s.makefile()        Crear un archivo relacionado con este socket

1: Describir rápidamente la comunicación de sockets usando el flujo de una llamada telefónica
2: Agregar comunicación en bucle basada en una conexión al servidor y cliente
3: El cliente envía vacío, se queda atascado, demostrar desde qué punto se queda atascado
Servidor:
from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.bind(('127.0.0.1',8081))
phone.listen(5)

conn,addr=phone.accept()
while True:
    data=conn.recv(1024)
    print('servidor===>')
    print(data)
    conn.send(data.upper())
conn.close()
phone.close()
Cliente:
from socket import *

phone=socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8081))

while True:
    msg=input('>>: ').strip()
    phone.send(msg.encode('utf-8'))
    print('cliente====>')
    data=phone.recv(1024)
    print(data)

Explicar la razón del atasco: cuando el búfer está vacío, recv se queda atascado, introduciendo el diagrama de principio



4. Demostrar qué sucede en el servidor cuando el cliente cierra la conexión, proporcionar un método de solución

5. Demostrar que el servidor no puede aceptar repetidamente conexiones, pero los servidores siempre están en funcionamiento aceptando continuamente conexiones de clientes

6: Demostrar simplemente udp
Servidor
from socket import *
phone=socket(AF_INET,SOCK_DGRAM)
phone.bind(('127.0.0.1',8082))
while True:
    msg,addr=phone.recvfrom(1024)
    phone.sendto(msg.upper(),addr)
Cliente
from socket import *
phone=socket(AF_INET,SOCK_DGRAM)
while True:
    msg=input('>>: ')
    phone.sendto(msg.encode('utf-8'),('127.0.0.1',8082))
    msg,addr=phone.recvfrom(1024)
    print(msg)

Demostrar concurrencia en el cliente udp
Demostrar que el cliente udp puede enviar vacío, explicar la diferencia entre recvfrom y recv, por ahora no mencionar los conceptos de flujo tcp y datagrama udp, dejarlo para el fenómeno de paquetes pegados

Flujo de experimentación con socketsVII. Sockets basados en TCP

TCP es basado en conexión, el servidor debe iniciarse primero, luego el cliente puede conectarse al servidor

Servidor TCP

1 ss = socket() # Crear socket del servidor
2 ss.bind()      # Enlazar dirección al socket
3 ss.listen()      # Escuchar conexiones
4 inf_loop:      # Bucle infinito del servidor
5     cs = ss.accept() # Aceptar conexión del cliente
6     comm_loop:         # Bucle de comunicación
7         cs.recv()/cs.send() # Conversación (recepción y envío)
8     cs.close()    # Cerrar socket del cliente
9 ss.close()        # Cerrar socket del servidor (opcional)

Cliente TCP

1 cs = socket()    # Crear socket del cliente
2 cs.connect()    # Intentar conectar al servidor
3 comm_loop:        # Bucle de comunicación
4     cs.send()/cs.recv()    # Conversación (envío/recepción)
5 cs.close()            # Cerrar socket del cliente

El flujo de comunicación de sockets es similar al de una llamada telefónica. Usemos una llamada telefónica como ejemplo para implementar una comunicación básica con sockets

#_*_coding:utf-8_*_

import socket
ip_port=('127.0.0.1',9000)  # Tarjeta SIM
BUFSIZE=1024                # Tamaño de recepción/envío de mensajes
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # Comprar teléfono
s.bind(ip_port) # Insertar tarjeta SIM
s.listen(5)     # Teléfono en modo espera


conn,addr=s.accept()            # Contestar llamada
# print(conn)
# print(addr)
print('Llamada recibida de %s' %addr[0])

msg=conn.recv(BUFSIZE)             # Escuchar mensaje
print(msg,type(msg))

conn.send(msg.upper())          # Enviar mensaje

conn.close()                    # Colgar

s.close()                       # Apagar teléfono

Servidor``` #coding:utf-8

import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect_ex(ip_port) # Marcar número

s.send('linhaifeng nb'.encode('utf-8')) # Enviar mensaje (solo puede enviar bytes)

feedback=s.recv(BUFSIZE) # Recibir mensaje print(feedback.decode('utf-8'))

s.close() # Colgar


ClienteAñadiendo bucles de conexión y comunicación


#coding:utf-8

import socket ip_port=('127.0.0.1',8081)# Tarjeta SIM BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # Comprar teléfono s.bind(ip_port) # Insertar tarjeta SIM s.listen(5) # Teléfono en modo espera

while True: # Nuevo bucle para recibir conexiones, puede contestar llamadas continuamente conn,addr=s.accept() # Contestar llamada # print(conn) # print(addr) print('Llamada recibida de %s' %addr[0]) while True: # Nuevo bucle de comunicación, puede comunicarse continuamente, recibir y enviar mensajes msg=conn.recv(BUFSIZE) # Escuchar mensaje

    # if len(msg) == 0:break        # Si no se agrega, cuando un cliente conectado se desconecta repentinamente, recv ya no se bloquea, causando un bucle infinito

    print(msg,type(msg))

    conn.send(msg.upper())          # Enviar mensaje

conn.close()                    # Colgar

s.close() # Apagar teléfono


Servidor mejorado```
#_*_coding:utf-8_*_

import socket
ip_port=('127.0.0.1',8081)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect_ex(ip_port)           # Marcar número

while True:                             # Nuevo bucle de comunicación, el cliente puede enviar y recibir mensajes continuamente
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    s.send(msg.encode('utf-8'))         # Enviar mensaje (solo puede enviar bytes)

    feedback=s.recv(BUFSIZE)                           # Recibir mensaje
    print(feedback.decode('utf-8'))

s.close()                                       # Colgar

Cliente mejoradoProblema:

A veces al reiniciar el servidor, puedes encontrar

Esto se debe a que tu servidor todavía está en el estado de四次挥手 (four-way handshake) TIME_WAIT, ocupando la dirección (si no entiendes esto, investiga profundamente: 1. Tres apretones de manos TCP, cuatro despedidas TCP 2. Ataque SYN flood 3. Métodos de optimización para alta concurrencia en servidores con muchos estados TIME_WAIT)

Solución:

# Agregar una configuración de socket, reutilizar IP y puerto

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # Es esto, antes de bind
phone.bind(('127.0.0.1',8080))

Método 1``` Descubrir que el sistema tiene muchas conexiones en estado TIME_WAIT, resolver ajustando parámetros del kernel de linux, vi /etc/sysctl.conf

Editar el archivo, agregar lo siguiente: net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30

Luego ejecuta /sbin/sysctl -p para que los parámenes surtan efecto.

net.ipv4.tcp_syncookies = 1 significa habilitar SYN Cookies. Cuando ocurre una sobrecarga de la cola de espera SYN, se usan cookies para manejar, puede defenderse de ataques SYN menores, el valor predeterminado es 0, lo que significa deshabilitado;

net.ipv4.tcp_tw_reuse = 1 significa habilitar reutilización. Permite reutilizar sockets TIME-WAIT para nuevas conexiones TCP, el valor predeterminado es 0, lo que significa deshabilitado;

net.ipv4.tcp_tw_recycle = 1 significa habilitar el rápido reciclaje de sockets TIME-WAIT en conexiones TCP, el valor predeterminado es 0, lo que significa deshabilitado.

net.ipv4.tcp_fin_timeout modifica el tiempo de espera predeterminado del sistema


Método 2VIII. Sockets basados en UDP
-----------

***UDP es sin conexión, iniciar cualquier extremo primero no causará errores***

Servidor UDP


1 ss = socket() # Crear un socket del servidor 2 ss.bind() # Enlazar socket del servidor 3 inf_loop: # Bucle infinito del servidor 4 cs = ss.recvfrom()/ss.sendto() # Conversación (recepción y envío) 5 ss.close() # Cerrar socket del servidor


Cliente UDP


cs = socket() # Crear socket del cliente comm_loop: # Bucle de comunicación cs.sendto()/cs.recvfrom() # Conversación (envío/recepción) cs.close() # Cerrar socket del cliente


***Ejemplo simple de socket UDP***


#coding:utf-8

import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 udp_serv_cli=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_serv_cli.bind(ip_port)

while True: msg,addr=udp_serv_cli.recvfrom(BUFSIZE) print(msg,addr)

udp_serv_cli.sendto(msg.upper(),addr)


Servidor UDP```
#_*_coding:utf-8_*_

import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
udp_serv_cli=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
    msg=input('>>: ').strip()
    if not msg:continue

    udp_serv_cli.sendto(msg.encode('utf-8'),ip_port)

    back_msg,addr=udp_serv_cli.recvfrom(BUFSIZE)
    print(back_msg.decode('utf-8'),addr)

Cliente UDPChat QQ (como UDP es sin conexión, múltiples clientes pueden comunicarse con el servidor simultáneamente)

#_*_coding:utf-8_*_
__author__ = 'WangCheng'
import socket
ip_port=('127.0.0.1',8081)
udp_serv_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # Comprar teléfono
udp_serv_sock.bind(ip_port)

while True:
    msg,addr=udp_serv_sock.recvfrom(1024)
    print('Mensaje de [%s:%s]:\033[1;44m%s\033[0m' %(addr[0],addr[1],msg.decode('utf-8')))
    back_msg=input('Responder mensaje: ').strip()

    udp_serv_sock.sendto(back_msg.encode('utf-8'),addr)

Servidor UDP``` #coding:utf-8author = 'WangCheng' import socket BUFSIZE=1024 udp_cli_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

nom_dic={ 'PerroAlex':('127.0.0.1',8081), 'Burloco':('127.0.0.1',8081), 'Arbol':('127.0.0.1',8081), 'WuDalang':('127.0.0.1',8081), }

while True: nom=input('Seleccionar contacto: ').strip() while True: msg=input('Escribir mensaje, Enter para enviar: ').strip() if msg == 'quit':break if not msg or not nom or nom not in nom_dic:continue udp_cli_sock.sendto(msg.encode('utf-8'),nom_dic[nom])

    back_msg,addr=udp_cli_sock.recvfrom(BUFSIZE)
    print('Mensaje de [%s:%s]:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))

udp_cli_sock.close()


Cliente UDP 1```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
import socket
BUFSIZE=1024
udp_cli_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

nom_dic={
    'PerroAlex':('127.0.0.1',8081),
    'Burloco':('127.0.0.1',8081),
    'Arbol':('127.0.0.1',8081),
    'WuDalang':('127.0.0.1',8081),
}


while True:
    nom=input('Seleccionar contacto: ').strip()
    while True:
        msg=input('Escribir mensaje, Enter para enviar: ').strip()
        if msg == 'quit':break
        if not msg or not nom or nom not in nom_dic:continue
        udp_cli_sock.sendto(msg.encode('utf-8'),nom_dic[nom])

        back_msg,addr=udp_cli_sock.recvfrom(BUFSIZE)
        print('Mensaje de [%s:%s]:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))

udp_cli_sock.close()

Cliente UDP 2Resultado del servidor

Resultado del cliente 1

Resultado del cliente 2

Servidor de tiempo

#_*_coding:utf-8_*_
__author__ = 'WangCheng'
from socket import *
from time import strftime

ip_port=('127.0.0.1',9000)
bufsize=1024

udp_serv=socket(AF_INET,SOCK_DGRAM)
udp_serv.bind(ip_port)

while True:
    msg,addr=udp_serv.recvfrom(bufsize)
    print('===>',msg)
    
    if not msg:
        time_fmt='%Y-%m-%d %X'
    else:
        time_fmt=msg.decode('utf-8')
    back_msg=strftime(time_fmt)

    udp_serv.sendto(back_msg.encode('utf-8'),addr)

udp_serv.close()

Servidor NTP``` #coding:utf-8author = 'WangCheng' from socket import * ip_port=('127.0.0.1',9000) bufsize=1024

udp_cli=socket(AF_INET,SOCK_DGRAM)

while True: msg=input('Ingresar formato de tiempo (ej %Y %m %d)>>: ').strip() udp_cli.sendto(msg.encode('utf-8'),ip_port)

data=udp_cli.recv(bufsize)

print(data.decode('utf-8'))

udp_cli.close()


Cliente NTPIX. Fenómeno de paquetes pegados
------

Basémonos en TCP para crear primero un programa de ejecución remota de comandos (1: ejecutar comando incorrecto 2: ejecutar ls 3: ejecutar ifconfig)

**Atención atención atención:**

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

La codificación del resultado de la lectura depende del sistema actual. Si es Windows, entonces **res.stdout.read() lee en codificación GBK**, y en el receptor se debe **decodificar con GBK**

**Y solo se puede leer el resultado una vez del pipe**

Nota: El resultado del comando ls -l ; lllllll ; pwd contiene tanto el resultado correcto de stdout como el error de stderr


#coding:utf-8author = 'WangCheng' from socket import * import subprocess

ip_port=('127.0.0.1',8080) BUFSIZE=1024

tcp_sock_serv=socket(AF_INET,SOCK_STREAM) tcp_sock_serv.bind(ip_port) tcp_sock_serv.listen(5)

while True: conn,addr=tcp_sock_serv.accept() print('Cliente',addr)

while True:
    cmd=conn.recv(BUFSIZE)
    if len(cmd) == 0:break

    res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                     stdout=subprocess.PIPE,
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE)

    stderr=res.stderr.read()
    stdout=res.stdout.read()
    conn.send(stderr)
    conn.send(stdout)


Servidor```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))
    act_res=s.recv(BUFSIZE)

    print(act_res.decode('utf-8'),end='')

ClienteEl programa anterior es basado en socket TCP, y al ejecutarse ocurre el fenómeno de paquetes pegados

Ahora creemos un programa de ejecución remota de comandos basado en UDP

#_*_coding:utf-8_*_
__author__ = 'WangCheng'

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
import subprocess

ip_port=('127.0.0.1',9003)
bufsize=1024

udp_serv=socket(AF_INET,SOCK_DGRAM)
udp_serv.bind(ip_port)

while True:
    # Recibir mensaje
    cmd,addr=udp_serv.recvfrom(bufsize)
    print('Comando de usuario ----->',cmd)

    # Procesamiento lógico
    res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
    stderr=res.stderr.read()
    stdout=res.stdout.read()

    # Enviar mensaje
    udp_serv.sendto(stderr,addr)
    udp_serv.sendto(stdout,addr)
udp_serv.close()

Servidor``` from socket import * ip_port=('127.0.0.1',9003) bufsize=1024

udp_cli=socket(AF_INET,SOCK_DGRAM)

while True: msg=input('>>: ').strip() udp_cli.sendto(msg.encode('utf-8'),ip_port)

data,addr=udp_cli.recvfrom(bufsize)
print(data.decode('utf-8'),end='')


ClienteEl programa anterior es basado en socket UDP, y al ejecutarse nunca ocurre el fenómeno de paquetes pegados

X. ¿Qué es el fenómeno de paquetes pegados?
-------

Debes saber: solo TCP tiene el fenómeno de paquetes pegados, UDP nunca tendrá paquetes pegados, ¿por qué? Escúchame atentamente

Primero debes comprender el principio de recepción/envío de mensajes de socket

El emisor puede enviar datos de 1K en 1K, mientras que la aplicación receptora puede tomar datos de 2K en 2K, o también puede tomar 3K o 6K de datos a la vez, o incluso tomar solo unos pocos bytes de datos a la vez. Esto significa que lo que la aplicación ve es un todo, o un flujo (stream). No sabe cuántos bytes tiene un mensaje. Por lo tanto, el protocolo TCP es un protocolo orientado a flujo, y esta es la razón por la que容易出现 paquetes pegados. UDP, en cambio, es un protocolo orientado a mensajes. Cada segmento UDP es un mensaje, y la aplicación debe extraer datos en unidades de mensajes. No puede extraer un número arbitrario de bytes a la vez, lo cual es muy diferente de TCP. ¿Cómo se define un mensaje? Se puede considerar que los datos enviados por el otro lado con una sola write/send forman un mensaje. Debes entender que cuando el otro lado envía un mensaje, sin importar cómo se segmente a nivel inferior, la capa del protocolo TCP ordenará los datos que componen el mensaje completo antes de presentarlos en el búfer del kernel.

Por ejemplo, en un socket TCP basado en cliente/servidor para cargar archivos, el contenido del archivo se envía como flujos de bytes segmentados. Para el receptor, no sabe dónde comienza el flujo de bytes del archivo ni dónde termina.

El fenómeno de paquetes pegados se debe principalmente a que el receptor no conoce los límites entre mensajes y no sabe cuántos bytes extraer a la vez.

Además, los paquetes pegados causados por el emisor se deben al protocolo TCP en sí. Para mejorar la eficiencia de transmisión, el emisor a menudo debe recopilar suficientes datos antes de enviar un segmento TCP. Si hay varias transmisiones consecutivas de datos muy pequeñas, generalmente el protocolo TCP optimizará y combinará estos datos en un bloque grande y luego lo enviará como un solo segmento TCP. De esta forma, el receptor recibirá datos pegados.

1. TCP (Protocolo de Control de Transmisión) es orientado a conexión, orientado a flujo y proporciona servicios de alta fiabilidad. Tanto el emisor como el receptor deben tener pares de sockets uno a uno, por lo que el emisor, para enviar múltiples paquetes al receptor de manera más efectiva, utiliza un método de optimización (algoritmo Nagle), que combina datos pequeños y con intervalos cortos en un bloque de datos grande y luego lo empaqueta. De esta manera, el receptor tiene dificultad para distinguirlos y debe proporcionar un mecanismo científico de desempaquetado. Es decir, la comunicación orientada a flujo no tiene límites de protección de mensajes.
2. UDP (Protocolo de Datagrama de Usuario) es sin conexión, orientado a mensajes y proporciona servicios de alta eficiencia. No utiliza el algoritmo de optimización de combinación de bloques, y como UDP admite un modelo de uno a muchos, el búfer de socket (skbuff) del receptor utiliza una estructura de cadena para registrar cada paquete UDP que llega. En cada paquete UDP, hay un encabezado de mensaje (información como la dirección de origen del mensaje, puerto, etc.). De esta manera, para el receptor, es fácil distinguir y procesar. **Es decir, la comunicación orientada a mensajes tiene límites de protección de mensajes.**
3. **tcp se basa en flujos de datos, por lo tanto, los mensajes enviados y recibidos no pueden estar vacíos, lo que requiere que tanto el cliente como el servidor agreguen un mecanismo de manejo de mensajes vacíos para evitar que el programa se quede atascado, mientras que udp se basa en datagramas, incluso si ingresas contenido vacío (solo presionas Enter), no es un mensaje vacío, el protocolo udp ayudará a encapsular el encabezado del mensaje, omitiré la experimentación**

recvfrom de udp es bloqueante, un recvfrom(x) debe corresponder a un único sendinto(y), al recibir x bytes de datos se considera completado, si y > x se pierden datos, lo que significa que udp simplemente no tiene paquetes pegados, pero puede perder datos, no es confiable

tcp no pierde datos de protocolo, si no se han recibido todos los paquetes, la próxima recepción continuará recibiendo desde donde se quedó. El lado emisor solo borra el contenido del búfer cuando recibe un ack. Los datos son confiables, pero pueden pegarse.

***Se producen paquetes pegados en dos situaciones.***

El emisor espera a que el búfer se llene antes de enviar, causando paquetes pegados (el intervalo de tiempo para enviar datos es muy corto y los datos son muy pequeños, se combinarán, produciendo paquetes pegados)


#coding:utf-8author = 'WangCheng' from socket import * ip_port=('127.0.0.1',8080)

tcp_sock_serv=socket(AF_INET,SOCK_STREAM) tcp_sock_serv.bind(ip_port) tcp_sock_serv.listen(5)

conn,addr=tcp_sock_serv.accept()

data1=conn.recv(10) data2=conn.recv(10)

print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8'))

conn.close()


Servidor```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello'.encode('utf-8'))
s.send('feng'.encode('utf-8'))

ClienteEl receptor no recibe los paquetes del búfer a tiempo, causando múltiples recepciones (el cliente envió un segmento de datos, el servidor solo recibió una pequeña parte, cuando el servidor recibe la siguiente vez, todavía tomará los datos restantes del búfer de la vez anterior, produciendo paquetes pegados)

#_*_coding:utf-8_*_
__author__ = 'WangCheng'
from socket import *
ip_port=('127.0.0.1',8080)

tcp_sock_serv=socket(AF_INET,SOCK_STREAM)
tcp_sock_serv.bind(ip_port)
tcp_sock_serv.listen(5)


conn,addr=tcp_sock_serv.accept()


data1=conn.recv(2) # No se recibió completamente de una vez
data2=conn.recv(10)# La próxima vez que se reciba, primero se tomarán los datos antiguos, luego los nuevos

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

Servidor``` #coding:utf-8author = 'WangCheng' import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port)

s.send('hello world'.encode('utf-8'))


Cliente***Ocurrencia de desempaquetado***

Cuando la longitud del búfer del emisor es mayor que el MTU de la tarjeta de red, tcp dividirá los datos enviados esta vez en varios paquetes para enviar.

***Pregunta complementaria 1: ¿Por qué tcp es transmisión confiable y udp no es confiable?***

La transmisión de datos basada en tcp puede referirse a mi otro artículo http://www.cnblogs.com/linhaifeng/articles/5937962.html. Durante la transmisión de datos tcp, el emisor primero envía los datos a su propio búfer, luego el protocolo controla el envío del contenido del búfer al otro extremo. El otro extremo devuelve un ack=1, y el emisor borra los datos del búfer. Si el otro extremo devuelve ack=0, los datos se envían nuevamente, por lo que tcp es confiable.

Cuando udp envía datos, el otro extremo no devuelve información de confirmación, por lo tanto no es confiable

***Pregunta complementaria 2: send(flujodebytes) y recv(1024) y sendall***

El 1024 especificado en recv significa tomar 1024 bytes de datos del búfer a la vez

El flujo de bytes enviado por send primero se coloca en el búfer del lado emisor, y luego el protocolo controla el envío del contenido del búfer al otro extremo. Si el flujo de bytes a enviar es mayor que el espacio restante del búfer, los datos se pierden. Con sendall se llamará cíclicamente a send, y los datos no se perderán

XI. Soluciones básicas para paquetes pegados
----------------

La raíz del problema es que el receptor no sabe la longitud total del flujo de bytes que el emisor va a enviar, por lo que el método para resolver los paquetes pegados se centra en cómo hacer que el emisor, antes de enviar los datos, informe al receptor la longitud total del flujo de bytes que va a enviar, y luego el receptor entra en un bucle infinito para recibir todos los datos

Solución de versión básica


#coding:utf-8author = 'WangCheng' import socket,subprocess ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

s.bind(ip_port) s.listen(5)

while True: conn,addr=s.accept() print('Cliente',addr) while True: msg=conn.recv(1024) if not msg:break res=subprocess.Popen(msg.decode('utf-8'),shell=True,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE) err=res.stderr.read() if err: ret=err else: ret=res.stdout.read() data_length=len(ret) conn.send(str(data_length).encode('utf-8')) data=conn.recv(1024).decode('utf-8') if data == 'recv_ready': conn.sendall(ret) conn.close()


Servidor```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))
    length=int(s.recv(1024).decode('utf-8'))
    s.send('recv_ready'.encode('utf-8'))
    send_size=0
    recv_size=0
    data=b''
    while recv_size < length:
        data+=s.recv(1024)
        recv_size+=len(data)


    print(data.decode('utf-8'))

Cliente¿Por qué es básico:

La velocidad de ejecución del programa es mucho más rápida que la velocidad de transmisión de la red, por lo que antes de enviar un flujo de bytes, se usa send para enviar la longitud de ese flujo de bytes, este método amplificará la pérdida de rendimiento causada por la latencia de la red

XII. Métodos para resolver paquetes pegados

Añadir un encabezado de longitud fija personalizada al flujo de bytes, el encabezado contiene la longitud del flujo de bytes, luego enviarlo de una vez al otro extremo, y el receptor al recibir, primero extrae del búfer la longitud fija del encabezado, luego extrae los datos reales

Módulo struct

Este módulo puede convertir un tipo, como un número, en bytes de longitud fija

>>> struct.pack('i',1111111111111)

。。。。。。。。.

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 # este es el rango

import json,struct
# Supongamos que se carga un archivo de 1T:1073741824000 a.txt a través del cliente

# Para evitar paquetes pegados, debe personalizar el encabezado
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} # 1T de datos, ruta del archivo y valor md5

# Para que este encabezado pueda ser transmitido, necesita ser serializado y convertido a bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') # Serializar y convertir a bytes para transmisión

# Para que el cliente sepa la longitud del encabezado, use struck para convertir la longitud del encabezado en un número de longitud fija: 4 bytes
head_len_bytes=struct.pack('i',len(head_bytes)) # Estos 4 bytes contienen solo un número, que es la longitud del encabezado

# El cliente comienza a enviar
conn.send(head_len_bytes) # Primero enviar la longitud del encabezado, 4 bytes
conn.send(head_bytes) # Luego enviar el formato de bytes del encabezado
conn.sendall(contenido del archivo) # Luego enviar el formato de bytes del contenido real

# El servidor comienza a recibir
head_len_bytes=s.recv(4) # Primero recibir 4 bytes del encabezado, obtener el formato de bytes de la longitud del encabezado
x=struct.unpack('i',head_len_bytes)[0] # Extraer la longitud del encabezado

head_bytes=s.recv(x) # Recibir el formato de bytes del encabezado según la longitud x
header=json.loads(json.dumps(header)) # Extraer el encabezado

# Finalmente, según el contenido del encabezado, extraer los datos reales, por ejemplo
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)

Uso detallado de struct:

#_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
__author__ = 'WangCheng'
import struct
import binascii
import ctypes

values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')

print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Antes : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)


s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)

print('Después de empaquetar',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))

s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('Después de empaquetar',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))

Uso detallado de struct``` import socket,struct,json import subprocess tel=socket.socket(socket.AF_INET,socket.SOCK_STREAM) tel.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # Es esto, antes de bind

tel.bind(('127.0.0.1',8080))

tel.listen(5)

while True: conn,addr=tel.accept() while True: cmd=conn.recv(1024) if not cmd:break print('cmd: %s' %cmd)

    res=subprocess.Popen(cmd.decode('utf-8'),
                         shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    err=res.stderr.read()
    print(err)
    if err:
        back_msg=err
    else:
        back_msg=res.stdout.read()


    conn.send(struct.pack('i',len(back_msg))) # Primero enviar la longitud de back_msg
    conn.sendall(back_msg) # Luego enviar el contenido real

conn.close()


Servidor (encabezado personalizado)```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
import socket,time,struct

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))



    l=s.recv(4)
    x=struct.unpack('i',l)[0]
    print(type(x),x)
    # print(struct.unpack('I',l))
    r_s=0
    data=b''
    while r_s < x:
        r_d=s.recv(1024)
        data+=r_d
        r_s+=len(r_d)

    # print(data.decode('utf-8'))
    print(data.decode('gbk')) # Codificación GBK predeterminada en Windows

Cliente (encabezado personalizado)Podemos hacer que el encabezado sea un diccionario, el diccionario contiene información detallada de los datos reales que se van a enviar, luego serializar con json, y luego usar struck para empaquetar la longitud de los datos serializados en 4 bytes (4 bytes son suficientes)

Al enviar:

Primero enviar la longitud del encabezado

Luego codificar el contenido del encabezado y enviarlo

Finalmente enviar el contenido real

Al recibir:

Primero recibir la longitud del encabezado, usar struct para extraer

Según la longitud extraída, recibir el contenido del encabezado, luego decodificar, deserializar

Del resultado deserializado, extraer la información detallada de los datos a recibir, luego recibir el contenido real de los datos

import socket,struct,json
import subprocess
tel=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tel.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # Es esto, antes de bind

tel.bind(('127.0.0.1',8080))

tel.listen(5)

while True:
    conn,addr=tel.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()

        headers={'data_size':len(back_msg)}
        head_json=json.dumps(headers)
        head_json_bytes=bytes(head_json,encoding='utf-8')

        conn.send(struct.pack('i',len(head_json_bytes))) # Primero enviar la longitud del encabezado
        conn.send(head_json_bytes) # Luego enviar el encabezado
        conn.sendall(back_msg) # Finalmente enviar el contenido real

    conn.close()

Servidor: Encabezado personalizado un poco más complejo``` from socket import * import struct,json

ip_port=('127.0.0.1',8080) cli=socket(AF_INET,SOCK_STREAM) cli.connect(ip_port)

while True: cmd=input('>>: ') if not cmd:continue cli.send(bytes(cmd,encoding='utf-8'))

head=cli.recv(4)
head_json_len=struct.unpack('i',head)[0]
head_json=json.loads(cli.recv(head_json_len).decode('utf-8'))
data_len=head_json['data_size']

recv_size=0
recv_data=b''
while recv_size < data_len:
    recv_data+=cli.recv(1024)
    recv_size+=len(recv_data)

print(recv_data.decode('utf-8'))
#print(recv_data.decode('gbk')) # Codificación GBK predeterminada en Windows


ClienteXIII. Autenticación de conexiones de clientes
--------------

Si quieres implementar una función de autenticación de conexión de cliente simple en un sistema distribuido, sin la complejidad de SSL, entonces puedes usar el método HMAC + sal


#coding:utf-8author = 'WangCheng' from socket import * import hmac,os

clave_secreta=b'linhaifeng bang bang bang' def conn_auth(conn): ''' Autenticar la conexión del cliente :param conn: :return: ''' print('Comenzando a verificar la legitimidad de la nueva conexión') msg=os.urandom(32) conn.sendall(msg) h=hmac.new(clave_secreta,msg) digest=h.digest() respone=conn.recv(len(digest)) return hmac.compare_digest(respone,digest)

def data_handler(conn,bufsize=1024): if not conn_auth(conn): print('La conexión no es válida, cerrando') conn.close() return print('Conexión válida, comenzando comunicación') while True: data=conn.recv(bufsize) if not data:break conn.sendall(data.upper())

def server_handler(ip_port,bufsize,backlog=5): ''' Solo manejar conexiones :param ip_port: :return: ''' tcp_sock_serv=socket(AF_INET,SOCK_STREAM) tcp_sock_serv.bind(ip_port) tcp_sock_serv.listen(backlog) while True: conn,addr=tcp_sock_serv.accept() print('Nueva conexión [%s:%s]' %(addr[0],addr[1])) data_handler(conn,bufsize)

if name == 'main': ip_port=('127.0.0.1',9999) bufsize=1024 server_handler(ip_port,bufsize)


Servidor```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
from socket import *
import hmac,os

clave_secreta=b'linhaifeng bang bang bang'
def conn_auth(conn):
    '''
    Verificar la conexión del cliente al servidor
    :param conn:
    :return:
    '''
    msg=conn.recv(32)
    h=hmac.new(clave_secreta,msg)
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_sock_cli=socket(AF_INET,SOCK_STREAM)
    tcp_sock_cli.connect(ip_port)

    conn_auth(tcp_sock_cli)

    while True:
        data=input('>>: ').strip()
        if not data:continue
        if data == 'quit':break

        tcp_sock_cli.sendall(data.encode('utf-8'))
        respone=tcp_sock_cli.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_sock_cli.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)

Cliente (válido)``` #coding:utf-8author = 'WangCheng' from socket import *

def client_handler(ip_port,bufsize=1024): tcp_sock_cli=socket(AF_INET,SOCK_STREAM) tcp_sock_cli.connect(ip_port)

while True:
    data=input('>>: ').strip()
    if not data:continue
    if data == 'quit':break

    tcp_sock_cli.sendall(data.encode('utf-8'))
    respone=tcp_sock_cli.recv(bufsize)
    print(respone.decode('utf-8'))
tcp_sock_cli.close()

if name == 'main': ip_port=('127.0.0.1',9999) bufsize=1024 client_handler(ip_port,bufsize)


Cliente (inválido: no conoce el método de cifrado)```
#_*_coding:utf-8_*_
__author__ = 'WangCheng'
from socket import *
import hmac,os

clave_secreta=b'linhaifeng bang bang bang1111'
def conn_auth(conn):
    '''
    Verificar la conexión del cliente al servidor
    :param conn:
    :return:
    '''
    msg=conn.recv(32)
    h=hmac.new(clave_secreta,msg)
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_sock_cli=socket(AF_INET,SOCK_STREAM)
    tcp_sock_cli.connect(ip_port)

    conn_auth(tcp_sock_cli)

    while True:
        data=input('>>: ').strip()
        if not data:continue
        if data == 'quit':break

        tcp_sock_cli.sendall(data.encode('utf-8'))
        respone=tcp_sock_cli.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_sock_cli.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)

Cliente (inválido: no conoce la clave_secreta)XIV. Implementación de concurrencia con socketserver

Basado en sockets TCP, lo clave son dos bucles: un bucle de conexión y un bucle de comunicación

En el módulo socketserver, hay dos categorías principales: clases Server (para resolver problemas de conexión) y clases Request (para resolver problemas de comunicación)

Clases Server:

Clases Request:

Relación de herencia:

Tomando el siguiente código como ejemplo, analicemos el código fuente de socketserver:

servidor_ftp=socketserver.ThreadingTCPServer(('127.0.0.1',8080),ServidorFtp)<br></br>servidor_ftp.serve_forever()

Orden de búsqueda de atributos: ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

  1. Al instanciar para obtener servidor_ftp, primero busca init en la clase ThreadingTCPServer, se encuentra en TCPServer, y luego ejecuta server_bind, server_active
  2. Buscar serve_forever en servidor_ftp, se encuentra en BaseServer, y luego ejecuta self._handle_request_noblock(), este método también está en BaseServer
  3. Ejecutar self._handle_request_noblock() y luego ejecutar request, client_address = self.get_request() (es decir, self.socket.accept() en TCPServer), luego ejecutar self.process_request(request, client_address)
  4. En ThreadingMixIn se encuentra process_request, se inicia un hilo para manejar la concurrencia, y luego se ejecuta process_request_thread, ejecutando self.finish_request(request, client_address)
  5. Las cuatro partes anteriores completaron el bucle de conexión, esta parte comienza a manejar la comunicación, en BaseServer se encuentra finish_request, se activa la instanciación de nuestra clase definida, se busca init, y nuestra clase definida no tiene este método, entonces se busca en su clase padre, es decir, BaseRequestHandler....

Resumen del aálisis de código fuente:

Basado en socketserver TCP, en nuestra clase definida:

  1. self.server es el objeto socket
  2. self.request es una conexión
  3. self.client_address es la dirección del cliente

Basado en socketserver UDP, en nuestra clase definida:

  1. self.request es una tupla (el primer elemento son los datos enviados por el cliente, el segundo es el objeto socket UDP del servidor), como (b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
  2. self.client_address es la dirección del cliente
import socketserver
import struct
import json
import os
class ServidorFtp(socketserver.BaseRequestHandler):
    codificacion='utf-8'
    dir_servidor='archivo_subido'
    max_paquete=1024
    BASE_DIR=os.path.dirname(os.path.abspath(__file__))
    def handle(self):
        print(self.request)
        while True:
            data=self.request.recv(4)
            data_len=struct.unpack('i',data)[0]
            head_json=self.request.recv(data_len).decode(self.codificacion)
            head_dic=json.loads(head_json)
            # print(head_dic)
            cmd=head_dic['cmd']
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(head_dic)
    def put(self,args):
        ruta_archivo = os.path.normpath(os.path.join(
            self.BASE_DIR,
            self.dir_servidor,
            args['nombre_archivo']
        ))

        tamano_archivo = args['tamano_archivo']
        recv_size = 0
        print('----->', ruta_archivo)
        with open(ruta_archivo, 'wb') as f:
            while recv_size < tamano_archivo:
                recv_data = self.request.recv(self.max_paquete)
                f.write(recv_data)
                recv_size += len(recv_data)
                print('recvsize:%s tamano_archivo:%s' % (recv_size, tamano_archivo))


servidor_ftp=socketserver.ThreadingTCPServer(('127.0.0.1',8080),ServidorFtp)
servidor_ftp.serve_forever()

ServidorFtp``` import socket import struct import json import os

class MiClienteTCP: familia_direccion = socket.AF_INET

tipo_socket = socket.SOCK_STREAM

permitir_reutilizar_direccion = False

max_tamano_paquete = 8192

codificacion='utf-8'

cola_solicitudes = 5

def __init__(self, dir_servidor, conectar=True):
    self.dir_servidor=dir_servidor
    self.socket = socket.socket(self.familia_direccion,
                                self.tipo_socket)
    if conectar:
        try:
            self.client_conectar()
        except:
            self.client_cerrar()
            raise

def client_conectar(self):
    self.socket.connect(self.dir_servidor)

def client_cerrar(self):
    self.socket.close()

def run(self):
    while True:
        entrada=input(">>: ").strip()
        if not entrada:continue
        l=entrada.split()
        cmd=l[0]
        if hasattr(self,cmd):
            func=getattr(self,cmd)
            func(l)


def put(self,args):
    cmd=args[0]
    nombre_archivo=args[1]
    if not os.path.isfile(nombre_archivo):
        print('archivo:%s no existe' %nombre_archivo)
        return
    else:
        tamano_archivo=os.path.getsize(nombre_archivo)

    head_dic={'cmd':cmd,'nombre_archivo':os.path.basename(nombre_archivo),'tamano_archivo':tamano_archivo}
    print(head_dic)
    head_json=json.dumps(head_dic)
    head_json_bytes=bytes(head_json,encoding=self.codificacion)

    head_struct=struct.pack('i',len(head_json_bytes))
    self.socket.send(head_struct)
    self.socket.send(head_json_bytes)
    send_size=0
    with open(nombre_archivo,'rb') as f:
        for line in f:
            self.socket.send(line)
            send_size+=len(line)
            print(send_size)
        else:
            print('carga exitosa')

cliente=MiClienteTCP(('127.0.0.1',8080))

cliente.run()


ClienteFtp

Etiquetas: programación de redes sockets tcp UDP concurrencia

Publicado el 6-5 01:33