Laboratory Work 1¶
Задание 1¶
Описание¶
Реализовать клиентскую и серверную часть приложения. Клиент отсылает серверу сообщение «Hello, server». Сообщение должно отразиться на стороне сервера. Сервер в ответ отсылает клиенту сообщение «Hello, client». Сообщение должно отобразиться у клиента.
- Обязательно использовать библиотеку
socket
- Реализовать с помощью протокола UDP
Полезные ссылки:
# %load "Task 1/server.py"
import socket
# UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Ensures that port is always ready to be used again
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', 12346))
# Makes keyboard interrupt possible at all times
sock.settimeout(1.0)
print(
f"Started server at udp://{sock.getsockname()[0]}:{sock.getsockname()[1]}")
while True:
try:
connection, client_address = None, None
try:
connection, client_address = sock.recvfrom(2048)
# Handle timeout
except IOError:
continue
data = connection.decode('utf-8')
print('Recived:', data)
sock.sendto(b"Hello, client", client_address)
except KeyboardInterrupt:
print("Stopping server...")
if connection:
connection.close()
break
sock.close()
Started server at udp://127.0.0.1:12346 Recived: Hello, server Recived: Hello, server Recived: Hello, server Stopping server...
Client¶
Так как Jupyter notebooks не хотят запускать cells в параллели, клиент запускается через командную строку:
python "./students/k33401/Reingeverts_Vadim/Lr1/Task 1/client.py"
# %load "Task 1/client.py"
import socket
# UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('localhost', 12346))
sock.send(b"Hello, server")
try:
connection = sock.recv(2048)
data = connection.decode('utf-8')
print('Recived:', data)
except ConnectionResetError:
print("Could not connect to the server")
sock.close()
Recived: Hello, client
Задание 2¶
Описание¶
Реализовать клиентскую и серверную часть приложения. Клиент запрашивает у сервера выполнение математической операции, параметры, которые вводятся с клавиатуры. Сервер обрабатывает полученные данные и возвращает результат клиенту. Варианты:
- Теорема Пифагора
- Решение квадратного уравнения.
- Поиск площади трапеции.
- Поиск площади параллелограмма.
Вариант выбирается в соответствии с порядковым номером в журнале. Пятый студент получает вариант 1 и т.д.
- Обязательно использовать библиотеку
socket
- Реализовать с помощью протокола TCP
# %load "Task 2/server.py"
import socket
from math import sqrt
def calc_pythagorean_equation(solveFor, x, y):
solution = None
x = float(x)
y = float(y)
if solveFor == "a":
solution = sqrt(y**2 - x**2)
elif solveFor == "b":
solution = sqrt(y**2 - x**2)
else:
solution = sqrt(x**2 + y**2)
return solution
# TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Ensures that port is always ready to be used again
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', 12346))
# Makes keyboard interrupt possible at all times
sock.settimeout(1.0)
sock.listen(10)
print(
f"Started server at tcp://{sock.getsockname()[0]}:{sock.getsockname()[1]}")
while True:
try:
connection, client_address = None, None
try:
connection, client_address = sock.accept()
# Handle timeout
except IOError:
continue
data = connection.recv(2048)
data = data.decode('utf-8')
print('Recived:\n' + data)
solveFor, x, y, _ = data.split("\n")
solution = calc_pythagorean_equation(solveFor, x, y)
print("Sending response:", solution)
connection.send(str(solution).encode('utf-8'))
except KeyboardInterrupt:
print("Stopping server...")
if connection:
connection.close()
break
sock.close()
Started server at tcp://127.0.0.1:12346 Recived: a 20.0 30.0 Sending response: 22.360679774997898 Stopping server...
Client¶
Так как Jupyter notebooks не хотят запускать cells в параллели, клиент запускается через командную строку:
python "./students/k33401/Reingeverts_Vadim/Lr1/Task 2/client.py"
# %load "Task 2/client.py"
import socket
def get_decimal_input(decimalName):
decimalNum = None
while True:
try:
decimalNum = float(input(f"Enter value for {decimalName}: "))
except ValueError:
print("Not a number.")
continue
if decimalNum < 0:
print(f"{decimalName} must be a positive number.")
continue
else:
break
return decimalNum
# TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(('localhost', 12346))
print("\nPythagorean theorem solver.")
print("""
+
|\\
| \\ C
A | \\
| \\
+----+
B
""")
message = ""
option1 = ""
while True:
option1 = input("Choose to solve for (A, B or C): ").lower()
if option1 not in ('a', 'b', 'c'):
print("Not an appropriate choice.")
else:
message += option1 + "\n"
break
if (option1 == "a"):
b = get_decimal_input("B")
c = get_decimal_input("C")
message += str(b) + "\n"
message += str(c) + "\n"
elif (option1 == "b"):
a = get_decimal_input("A")
c = get_decimal_input("C")
message += str(a) + "\n"
message += str(c) + "\n"
else:
a = get_decimal_input("A")
b = get_decimal_input("B")
message += str(a) + "\n"
message += str(b) + "\n"
sock.send(message.encode("utf-8"))
connection = sock.recv(2048)
data = connection.decode('utf-8')
print(f'\nSolution for {option1.upper()} is:', data)
except ConnectionRefusedError:
print("Could not connect to the server")
sock.close()
Pythagorean theorem solver. + |\ | \ C A | \ | \ +----+ B Solution for A is: 22.360679774997898
Задание 3¶
Описание¶
Реализовать серверную часть приложения. Клиент подключается к серверу. В ответ
клиент получает http-сообщение, содержащее html-страницу, которую сервер
подгружает из файла index.html
.
Полезные ссылки:
- http://zetcode.com/python/socket/
- Обязательно использовать библиотеку
socket
# %load "Task 3/server.py"
import socket
from os import path
from pathlib import Path
import webbrowser
# Makes consistent path to work directory in case of
# 1. Running .py file directly `python server.py`
# 2. Running .py file from another directory `python ./someComplicatedPath/server.py`
# 3. Running cell from .ipynb notebook
curr_dirname = None
ipynb_path = "./Task 3"
if "__file__" in globals():
dirname = path.dirname(__file__)
else:
dirname = path.abspath("") + ipynb_path
index_file = Path(dirname) / 'index.html'
# TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Ensures that port is always ready to be used again
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', 0))
# Makes keyboard interrupt possible at all times
sock.settimeout(1.0)
sock.listen(10)
url = f'http://{sock.getsockname()[0]}:{sock.getsockname()[1]}'
print(
f"Started server at {url}")
webbrowser.open(url)
while True:
try:
connection, client_address = None, None
try:
connection, client_address = sock.accept()
# Handle timeout
except IOError:
continue
print("Incoming connection from:", client_address)
response_type = "HTTP/1.1 200 OK\n"
headers = "Content-Type: text/html; charset=utf-8\n\n"
body = None
with open(index_file, 'r', encoding="utf-8") as file:
body = file.read()
response = response_type + headers + body
connection.sendall(response.encode('utf-8'))
except KeyboardInterrupt:
print("Stopping server...")
if connection:
connection.close()
break
sock.close()
Started server at http://127.0.0.1:64921 Incoming connection from: ('127.0.0.1', 64922) Incoming connection from: ('127.0.0.1', 64924) Incoming connection from: ('127.0.0.1', 64925) Stopping server...
Задание 4¶
Описание¶
Реализовать двухпользовательский или многопользовательский чат. Реализация многопользовательского часа позволяет получить максимальное количество баллов. Обязательно использовать библиотеку
Полезные ссылки:
- Реализовать с помощью протокола TCP – 100% баллов, с помощью UDP – 80%.
- Обязательно использовать библиотеку
threading
. - Для реализации с помощью UDP, thearding использовать для получения сообщений у клиента.
- Для применения с TCP необходимо запускать клиентские подключения И прием и отправку сообщений всем юзерам на сервере в потоках. Не забудьте сохранять юзеров, чтобы потом отправлять им сообщения.
# %load "Task 4/server.py"
import socket
import threading
import json
from threading import Thread
# ref: https://stackoverflow.com/a/43936317
class SocketServer(socket.socket):
clients = []
def __init__(self, host='localhost', port=12344):
# TCP
socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)
# Ensures that port is always ready to be used again
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.bind((host, port))
self.listen(10)
# Makes keyboard interrupt possible at all times
self.settimeout(1.0)
def run(self):
print(
f"Started server at tcp://{self.getsockname()[0]}:{self.getsockname()[1]}")
try:
self.accept_clients()
except KeyboardInterrupt:
pass
finally:
self.stop()
def stop(self):
print("Stopping server...")
for client in self.clients:
client.close()
self.close()
print("Server has stopped.")
def accept_clients(self):
while True:
try:
connection, client_address = self.accept()
# Handle timeout
except TimeoutError:
continue
# Adding client to clients list
self.clients.append(connection)
# Client Connected
self.on_open(connection)
# Receiving data from client
thread = Thread(target=self.recieve, args=(connection,))
thread.start()
def recieve(self, connection):
while True:
try:
data = connection.recv(2048)
# Handle timeout
except TimeoutError:
continue
except ConnectionAbortedError:
break
if data == b"":
break
# Message Received
self.on_message(connection, data)
# Removing client from clients list
self.clients.remove(connection)
# Client Disconnected
self.on_close(connection)
# Closing connection with client
connection.close()
print("Current clients:", len(self.clients))
return
def broadcast(self, message):
# Sending message to all clients
for client in self.clients:
print("Sending client a message...")
client.send(message)
def on_open(self, connection):
pass
def on_message(self, connection, message):
pass
def on_close(self, connection):
pass
class ChatServer(SocketServer):
def __init__(self):
SocketServer.__init__(self)
def on_message(self, connection, message):
msg_dict = json.loads(message) # data loaded
msg_dict["id"] = f"{connection.getpeername()[0]}:{connection.getpeername()[1]}"
print("Recived:", msg_dict)
# Serialize dict
serialized = json.dumps(msg_dict).encode("utf-8")
# Sending message to all clients
self.broadcast(serialized)
def on_open(self, connection):
print("Client Connected")
def on_close(self, connection):
print("Client Disconnected")
if __name__ == "__main__":
server = ChatServer()
server.run()
Started server at tcp://127.0.0.1:12344 Client Connected Recived: {'name': 'Anonymous', 'text': 'Hello', 'id': '127.0.0.1:61826'} Sending client a message... Recived: {'name': 'Anonymous', 'text': 'What are you doing?', 'id': '127.0.0.1:61826'} Sending client a message... Client Connected Recived: {'name': 'Mike', 'text': 'Hello?', 'id': '127.0.0.1:54863'} Sending client a message... Sending client a message... Recived: {'name': 'Anonymous', 'text': 'hi', 'id': '127.0.0.1:61826'} Sending client a message... Sending client a message... Recived: {'name': 'Mike', 'text': 'whats up?', 'id': '127.0.0.1:54863'} Sending client a message... Sending client a message... Recived: {'name': 'Anonymous', 'text': 'nothing', 'id': '127.0.0.1:61826'} Sending client a message... Sending client a message... Client Disconnected Current clients: 1 Recived: {'name': 'Mike', 'text': 'ok', 'id': '127.0.0.1:54863'} Sending client a message... Client Disconnected Current clients: 0 Stopping server... Server has stopped.
Client¶
Так как Jupyter notebooks не хотят запускать cells в параллели, клиент запускается через командную строку:
python "./students/k33401/Reingeverts_Vadim/Lr1/Task 4/client.py"
# %load "Task 4/client.py"
import os
import socket
from threading import Thread
import json
def destructure(dict):
return (t[1] for t in dict.items())
class ChatClient(socket.socket):
name = ""
id = ""
is_connected = False
def __init__(self):
# TCP
socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM)
# Ensures that port is always ready to be used again
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Makes keyboard interrupt possible at all times
self.settimeout(0.1)
self.name = input("Create a nickname: ") or "Anonymous"
def client_connect(self, host='localhost', port=12344):
try:
self.connect((host, port))
self.id = f"{self.getsockname()[0]}:{self.getsockname()[1]}"
self.is_connected = True
except IOError:
print("Could not connect to the server.")
self.client_disconnect()
self.talk_with_server()
def talk_with_server(self):
thread1 = Thread(target=self.client_send)
thread2 = Thread(target=self.client_receive)
thread1.start()
thread2.start()
def client_send(self):
while True:
try:
text = input("")
print()
msg_dict = {"name": self.name, "text": text}
# Serialize dict
serialized = json.dumps(msg_dict).encode("utf-8")
self.send(serialized)
except TimeoutError:
continue
except (EOFError, IOError, OSError):
break
except KeyboardInterrupt:
break
self.client_disconnect()
def client_receive(self):
while True:
try:
data = self.recv(2048)
# Deserialize dict
msg_dict = json.loads(data)
name, text, id = destructure(msg_dict)
local_name = "You" if id == self.id else name
print(f"{local_name}: {text}")
except TimeoutError:
continue
except (ConnectionResetError, ConnectionAbortedError):
print("\nServer closed connection.")
break
except OSError:
break
except KeyboardInterrupt:
break
self.client_disconnect()
def client_disconnect(self):
if (self.is_connected):
self.close()
self.is_connected = False
os._exit(0)
if __name__ == "__main__":
client = ChatClient()
client.client_connect()
You: Hello Mike: hey Mike: whats up
The Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details.
Задание 5¶
Описание¶
Необходимо написать простой web-сервер для обработки GET и POST http запросов средствами Python и библиотеки socket.
Базовый класс для простейшей реализации web-сервера доступен Google Doc
Подробный мануал по работе доступен iximiuz - Python Web Server
Задание: сделать сервер, который может:
- Принять и записать информацию о дисциплине и оценке по дисциплине.
- Отдать информацию обо всех оценах по дисциплине в виде html-страницы.
# %load "Task 5/server.py"
import socket
from os import path
from pathlib import Path
import webbrowser
import sys
from urllib.parse import urlparse, parse_qs
class MyHTTPServer:
# Параметры сервера
def __init__(self, host='localhost', port=0, name="My HTTP Server"):
self.host = host
self.port = port
self.name = name
self.data = {}
def serve_forever(self):
# 1. Запуск сервера на сокете, обработка входящих соединений
# TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Ensures that port is always ready to be used again
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((self.host, self.port))
# Makes keyboard interrupt possible at all times
sock.settimeout(1.0)
sock.listen(10)
url = f'http://{sock.getsockname()[0]}:{sock.getsockname()[1]}'
print(
f"Started server at {url}")
webbrowser.open(url)
while True:
try:
connection = self.serve_client(sock)
# Handle timeout
except IOError:
continue
except KeyboardInterrupt:
print("Stopping server...")
if connection:
connection.close()
break
sock.close()
def serve_client(self, sock):
# 2. Обработка клиентского подключения
try:
connection, client_address = sock.accept()
# Handle timeout
except IOError:
raise IOError
print("Incoming connection from:", client_address)
self.handle_request(connection)
return connection
def parse_request(self, connection):
# 3. функция для обработки заголовка http+запроса. Python, сокет
# предоставляет возможность создать вокруг него некоторую обертку,
# которая предоставляет file object интерфейс. Это дайте возможность
# построчно обработать запрос. Заголовок всегда - первая строка.
# Первую строку нужно разбить на 3 элемента (метод + url + версия протокола).
# URL необходимо разбить на адрес и параметры (isu.ifmo.ru/pls/apex/f?p=2143,
# где isu.ifmo.ru/pls/apex/f, а p=2143 - параметр p со значением 2143)
method, path, protocol, body = self.parse_headers(connection)
href = path.split('?')[0]
if method == 'GET':
query_dict = parse_qs(urlparse(path).query)
elif method == 'POST':
query_dict = parse_qs(body)
return method, href, query_dict
def parse_headers(self, connection):
# 4. Функция для обработки headers. Необходимо прочитать все заголовки после
# первой строки до появления пустой строки и сохранить их в массив.
data = connection.recv(2048)
data = data.decode('utf-8')
method, path, protocol = data.split('\n')[0].split(' ')
body = data.split('\r\n\r\n')[1]
return method, path, protocol, body
def handle_request(self, connection):
# 5. Функция для обработки url в соответствии с нужным методом. В случае
# данной работы, нужно будет создать набор условий, который обрабатывает GET
# или POST запрос. GET запрос должен возвращать данные. POST запрос должен
# записывать данные на основе переданных параметров.
method, href, query_dict = self.parse_request(connection)
if (method == "GET" and href == "/"):
pass
elif (method == "POST" and href == "/add"):
for key, value in query_dict.items():
if key not in self.data:
self.data[key] = value
else:
self.data[key].append(value[0])
self.send_response(connection)
def send_response(self, connection):
# 6. Функция для отправки ответа. Необходимо записать в соединение status line
# вида HTTP/1.1 <status_code> <reason>. Затем, построчно записать заголовки и
# пустую строку, обозначающую конец секции заголовков.
response_type = "HTTP/1.1 200 OK\n"
headers = "Content-Type: text/html; charset=utf-8\n\n"
with open(index_file, 'r', encoding="utf-8") as file:
body = file.read()
parsed_body = self.insert_template_variables(
body, {"table": generate_table(self.data)})
response = response_type + headers + parsed_body
connection.sendall(response.encode('utf-8'))
def insert_template_variables(self, body, variables={"table": "hello", "wtf": "1"}):
cursor = 0
while True:
index_start = body.find("{{", cursor)
if (index_start != -1):
index_end = body.find("}}", cursor)
if (index_end == -1):
raise Exception(
f"Could not find closing brackets at {cursor}")
var = body[index_start + 2:index_end].strip(' ')
cursor = index_end + 2
if var in variables:
body = body[:index_start] + \
variables[var] + body[index_end + 2:]
cursor = index_start + len(variables[var])
else:
break
return body
def generate_table(data={}):
table = ""
if (data):
table_headings = []
table_row_cells = []
for key, value in data.items():
table_headings.append(f'<th>{key.title()}</th>')
for cell in value:
table_row_cells.append(f'<td>{cell}</td>')
split_every = len(table_row_cells) // len(table_headings)
rows = [table_row_cells[i::split_every] for i in range(split_every)]
nl = '\n'
table_rows = []
for row in rows:
table_row = "<tr>" + nl.join(row) + "</tr>"
table_rows.append(table_row)
table = f"""
<table>
<tr>
{nl.join(table_headings)}
</tr>
{nl.join(table_rows)}
</table>
"""
return table
if __name__ == '__main__':
# Makes consistent path to work directory in case of
# 1. Running .py file directly `python server.py`
# 2. Running .py file from another directory `python ./someComplicatedPath/server.py`
# 3. Running cell from .ipynb notebook
ipynb_path = "./Task 5"
if "__file__" in globals():
dirname = path.dirname(__file__)
else:
dirname = Path(path.abspath("") + ipynb_path)
index_file = Path(dirname) / 'index.html'
serv = MyHTTPServer()
serv.serve_forever()
Started server at http://127.0.0.1:64055 Incoming connection from: ('127.0.0.1', 64056) Incoming connection from: ('127.0.0.1', 57258) Incoming connection from: ('127.0.0.1', 57259) Incoming connection from: ('127.0.0.1', 57261) Stopping server...