In [1]:
import sys
sys.version


Out[1]:
'3.6.0 (default, Dec 24 2016, 08:01:42) \n[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]'

Интернет, сетевые протоколы, клиент-серверные приложения

Requests for Comment (RFCs) публикуются организацией Internet Engineering Task Force (IETF).


In [2]:
import socket

IP

Internet Assigned Numbers Authority (IANA) занимается распределением ip-адресов.

Назначается интерфейсу статически или динамически (DHCP).

IPv4-адрес

32-битное число:

Например, 93.184.216.34

127.0.0.1 - Каждый девайс имеет виртуальный интерфейс - loopback interface.

Приватные адреса:

  • 10.0.0.0 to 10.255.255.255
  • 172.16.0.0 to 172.31.255.255
  • 192.168.0.0 to 192.168.255.255

NAT - Network Address Translation - преобразует трафик с приватных адресов в трафик, исходящий с одного публичного ip.

Пакеты:

Структура IPv4 пакета:

IPv6-адрес

128-битное число:

2606:2800:220:1:248:1893:25c8:1946

::1

Структура заголовка IPv6 пакета:

DNS (Domain Name System)

Распределенная база соответствия доменных имен и IP-адресов.


In [12]:
import socket
hostname = 'httpbin.org'
addr = socket.gethostbyname(hostname)
print(addr)


23.21.55.239

In [11]:
import socket
hostname = 'httpbin.org'
addr_info = socket.getaddrinfo(hostname, 80)
print(addr_info)


[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('23.21.55.239', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('54.243.173.79', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('54.225.70.24', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('174.129.214.98', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('23.21.55.239', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('54.243.173.79', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('23.21.245.33', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('23.21.245.33', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('23.23.118.21', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('50.17.234.140', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('174.129.203.239', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('174.129.203.239', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('54.225.70.24', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('50.17.234.140', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('174.129.214.98', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('23.23.118.21', 80))]

In [13]:
import socket
print (socket.gethostbyaddr(addr))


('ec2-23-21-55-239.compute-1.amazonaws.com', ['239.55.21.23.in-addr.arpa'], ['23.21.55.239'])

Порты

Данные прибывают на интерфейс с определенным IP - но что с ними делать дальше? Каждая программа должна проверить их? Конечно, нет. Существует концепция портов - процесс, взаимодействующий с сетью, может занять определенный номер порта в промежутке от 1 до 65535. Порт входит в заголовок пакета - т.о. протокол знает какому приложению направить данные.

UDP (User Datagram Protocol)

UDP чрезвычайно простой протокол - он не предлагает никаких доп фич помимо доставки данных удаленному процессу. Никакого постоянного соединения. Никакой гарантии доставки и доставки в порядку отправления.


In [43]:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print('Socket created')
s


Socket created
Out[43]:
<socket.socket fd=62, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('0.0.0.0', 0)>

In [47]:
s.sendto(bytes("data", encoding="utf-8"), ("127.0.0.1", 3000))


Out[47]:
4

In [1]:
s.sendto(b"data", ("127.0.0.1", 3000))


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-0ffce73abe6c> in <module>()
----> 1 s.sendto(b"data", ("127.0.0.1", 3000))

NameError: name 's' is not defined

Упражнение #1: отправьте свое имя и фамилию по UDP


In [ ]:

TCP (Transmission Control Protocol)

  • Доставка пакетов в правильном порядке
  • Подтверждение доставки
  • Проверка ошибок и переотправление пакетов
  • Контроль потока сообщений (чтобы предотвратить заспамление получателя)

In [6]:
import socket #create a TCP socket (SOCK_STREAM) 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Socket created')
s


Socket created
Out[6]:
<socket.socket fd=59, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>

In [9]:
s.connect(("127.0.0.1", 9999))


---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-9-a92e03e4fa45> in <module>()
----> 1 s.connect(("127.0.0.1", 9999))
      2 s.send(b"pooo")
      3 msg = s.recv(1024)
      4 s.send(msg)

OSError: [Errno 56] Socket is already connected

In [10]:
# запись в сокет
s.send(b"data")
# s.send(bytes("data", encoding="utf-8"))


Out[10]:
6

In [11]:
# чтение из сокета
s.recv(1024)


Out[11]:
b''

упражнение #2 - отправьте свои имя и фамилию по TCP


In [ ]:

упражнение #3 коннект-фамилия-вопрос-ответ


In [ ]:

HTTP (HyperText Transfer Protocol)

Работает поверх TCP

Пример запроса клиента:

GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Host: httpbin.org
User-Agent: HTTPie/0.3.1

Пример ответа сервера:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 12150
Content-Type: text/html; charset=utf-8
Date: Sun, 30 Oct 2016 13:48:11 GMT
Server: nginx

<!DOCTYPE html>
<html>
<head>
...

HTTP2

UDP-cервер

Создание сокета


In [ ]:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

Биндинг на интерфейс и порт


In [ ]:
sock.bind(("127.0.0.1", 6668))

Получение данных от клиента


In [ ]:
data, addr = sock.recvfrom(1024)
print("received message:", data, "from", addr)

Закрываем сокет


In [37]:
sock.close()


received message: b'data' from ('127.0.0.1', 64937)

TCP-сервер


In [ ]:
# Показать telnet!!!
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

In [ ]:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

In [ ]:
sock.bind(("127.0.0.1", 6667))
sock.listen(5)
client_sock, addr = sock.accept()
print("connected", addr)
data = client_sock.recv(1024)
print(data)
client_sock.close()
sock.close()

Примеры TCP и UDP серверов

Примеры в папке с лекцией:

  • простой UDP echo сервер
  • UDP echo сервер на asyncio
  • простой TCP echo сервер
  • TCP echo сервер на asyncio
  • многопоточный TCP чат сервер
  • асинхронный TCP чат сервер с Event Loop'ом
  • асинхронный TCP чат сервер с Event Loop'ом и протоколом сообщений

модуль struct


In [1]:
import struct

struct.pack("i", 123)


Out[1]:
b'{\x00\x00\x00'

In [2]:
struct.unpack("i", b'{\x00\x00\x00')


Out[2]:
(123,)

HTTP-клиент


In [62]:
import urllib.request
f = urllib.request.urlopen('http://www.python.org/', timeout=3)
print(f.read().decode('utf-8')[:100])


<!doctype html>
<!--[if lt IE 7]>   <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9">   <![endif]-->
<!-

In [57]:
import requests
resp = requests.get('http://www.python.org/', timeout=3)
print(resp.text[:100])


<!doctype html>
<!--[if lt IE 7]>   <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9">   <![endif]-->
<!-

Пример, демонстрирующий почему requests гораздо удобнее в использовании:


In [53]:
import urllib.request

auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(
    realm='PDQ Application',
    uri='https://mahler:8092/site-updates.py',
    user='user',
    passwd='pass'
)
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
#urllib.request.urlopen('http://www.example.com/login.html')

vs


In [54]:
import requests

requests.get('https://api.github.com/user', auth=('user', 'pass'))


Out[54]:
<Response [401]>

HTTP-сервер

  • Принимает TCP-соединение
  • Читает запрос, разбирает его - заголовки и тело запроса
  • Формирует ответ
  • Отправляет ответ клиенту - заголовки и тело ответа
  • Закрывает соединение (не обязательно)

Что почитать?

Dr. M. O. Faruque Sarker, Sam Washington: Learning Python Network Programming:

https://www.packtpub.com/networking-and-servers/learning-python-network-programming


In [ ]: