<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Простейший &quot;мессенджер&quot; на python через протокол UDP]]></title><description><![CDATA[<p dir="auto">Изучаю сети и потребовалась простая программа которая будет гонять плейнтекст через сеть. Просто пинги - скучно. iperf3 - дает нагрузку но тоже не интересно. А здесь идет то что ты послал, и это можно посмотреть.</p>
<p dir="auto">Недолго думая написал сей велосипед. Надеюсь никому в голову не придет использовать его на серьезных основаниях: здесь нет никакой безопасности, нет и шифрования. Данные передаются открытым текстом без авторизации или прочих механизмов защиты. Нет даже банального отчета о том получил ли собеседник пакет.<br />
Предлагаю использовать это в образовательных целях, ну или как уж вашей душе угодно.</p>
<p dir="auto"><em>Я не сторонник копирайта, но один знакомый попросил применять к написанному коду ну хоть какую-то лицензию. Потому получите-распишитесь.</em></p>
<p dir="auto"><strong>Версия 0.1 Alpha</strong><br />
возможно я буду делать доработанные варианты.</p>
<pre><code>"""
 This is free and unencumbered software released into the public domain.

 Anyone is free to copy, modify, publish, use, compile, sell, or
 distribute this software, either in source code form or as a compiled
 binary, for any purpose, commercial or non-commercial, and by any
 means.

 In jurisdictions that recognize copyright laws, the author or authors
 of this software dedicate any and all copyright interest in the
 software to the public domain. We make this dedication for the benefit
 of the public at large and to the detriment of our heirs and
 successors. We intend this dedication to be an overt act of
 relinquishment in perpetuity of all present and future rights to this
 software under copyright law.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.

 For more information, please refer to &lt;http://unlicense.org/&gt;
"""
# Default settings. You may edit these to make startup easier.
destipaddr = "127.0.0.1"
username = "Anonymous"
destport = 15000
listport = 15000
DoInitSetup = False
inputprompt = (f"\x1b[1;32m{username}&gt; \x1b[0m")
maxmessagelength = 32768

import socket, threading, queue, time, sys, base64, os

BigMessage = ""
packetdatatype = "PlainText"

q = queue.Queue()

def UDPsend(ipaddr: str, portnumber: int, userdatagram: str) -&gt; None:   # Send a UDP packet with payload to an IP and a port
	payload = userdatagram.encode('utf-8')                              # We use unicode, more compatibility more better
	with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:         # It will use a random port to send
		s.sendto(payload, (ipaddr, portnumber))

def FormDatagram(usname = "Anonymous", packetnumber = "0", contents = "PlainText", thedatagram = "Placeholder."):
	completedatagram = f"Username: {usname.strip()} PacketNumber: {packetnumber.strip()} ContentType: {contents.strip()}\n{thedatagram}"
	return completedatagram

def IsValidIP(s: str) -&gt; bool:                                          # IP addr checker.
	if not isinstance(s, str) or not s:
		return False
	parts = s.split('.')
	if len(parts) != 4:
		return False
	for p in parts:                                                     # no empty parts, no leading '+' or '-' signs
		if not p or (p[0] in '+-'):
			return False
		if len(p) &gt; 1 and p[0] == '0':                                  # disallow leading zeros unless the part is exactly "0"
			return False
		if not p.isdigit():                                             # all characters must be digits
			return False
		val = int(p)
		if val &lt; 0 or val &gt; 255:                                        # numeric range 0-255
			return False

	return True

def initialsetup():                                                     # Initial setup dialog
	global destipaddr, username, destport, listport
	usname = input("Username: ").strip()
	if usname == "":
		print(f"None given, you'll be {username}")
	else:
		username = usname
		print(f"You'll be seen as {username}")
	destip = input("Destination IP address: ").strip()
	if destip=="":
		print(f"You'll talk to (IP: {destipaddr})")
	elif IsValidIP(destip):
		destipaddr = destip
	else:
		print("W.T.F. Did you just enter?! Screw you!")
		exit(1)
	dstport = input("Destination port: ").strip()
	if dstport =="":
		print(f"None given, using {destport}")
	elif any(not ch.isdigit() for ch in dstport):
		print("W.T.F. Did you just enter?! Screw you!")
		exit(1)
	else:
		destport = int(dstport)
	lstport = input("Listening port: ").strip()
	if lstport =="":
		print(f"None given, using {listport}")
	elif any(not ch.isdigit() for ch in lstport):
		print("W.T.F. Did you just enter?! Screw you!")
		exit(1)
	else:
		listport = int(lstport)

def base64ify(filepath):                                                # Encode a file into base64
	try:
		with open(filepath.strip(), "rb") as inputfile:
			return base64.b64encode(inputfile.read()).decode()
	except FileNotFoundError:
		print (f"File {filepath} does not exist.")
		return None
	except Exception as e:
		print ("Error encoding a file:", e)
		return None

def unbase64ify(s):                                                     # Decode base64 into bytes
	try:
		return base64.b64decode(s.strip())
	except Exception as e:
		print("Invalid base64 data:", e)
		return None

def savefile(path, data):                                               # Save bytes into file
	path = path.strip()
	if not path:
		print ("Error: wrong path!")
		return False
	if os.path.exists(path):
		print ("Error! File exists!")
		return False
	with open(path, "wb") as f:
		f.write(data)
	return True        

def input_thread():                                                     # Chat input prompt
	global packetdatatype
	while True:
		chatmessage = ""
		time.sleep(0.1)
		chatpart = input(inputprompt)
		if chatpart[:len("file://")] == "file://":
			packetdatatype = "Base64File"
			chatmessage=base64ify(chatpart[len("file://"):])
		else:
			packetdatatype = "PlainText"
			while not(chatpart == ""):
				chatmessage += (f"{chatpart}\n")
				chatpart = input()
		if not chatmessage == None:
			q.put(chatmessage)


if DoInitSetup:
	initialsetup()
else:
	print("Initial setup disabled. Built in defaults used:")
	print(f"Username: {username}\nDestination IP: {destipaddr}\nDestination port: {destport}\nListening port: {listport}")

t = threading.Thread(target=input_thread, daemon=True)
t.start()

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", int(listport)))
sock.setblocking(False)


while True:
	try:
		try:
			data, rcvaddr = sock.recvfrom(65535)
		except BlockingIOError:
			try:
				chatmessage = q.get_nowait()
			except queue.Empty:
				time.sleep(0.1)
				continue
		else:                                                           # We need to parse a message.
			gottext         = data.decode(errors="replace")
			possender       = gottext.find("Username: ")
			pospacketnum    = gottext.find("PacketNumber: ")
			posdatatype     = gottext.find("ContentType: ")
			posfirstnewline = gottext.find(f"\n")
			if possender == -1 or pospacketnum == -1 or posdatatype == -1 or posfirstnewline == -1:
				print("Got malformed or unintended packet.")
				continue
			sendername   = gottext[possender+len("Username: "):pospacketnum -1 ].strip()
			gotpacketnum = gottext[pospacketnum+len("PacketNumber: "):posdatatype - 1].strip()
			rcvdatatype  = gottext[posdatatype+len("ContentType: "):posfirstnewline].strip()
			gotdatagram  = gottext[posfirstnewline + 1:].rstrip()
			if gotpacketnum == "0":
				if rcvdatatype == "PlainText":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36m{gotdatagram}\x1b[0m")
				elif rcvdatatype == "Base64File":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36msent you a file!\x1b[0m")
					savefilepath = input("Enter file path and name to save, press enter to discard.\nfilepath: ")
					if not savefilepath == "":
						savefile(savefilepath,unbase64ify(gotdatagram))
			elif int(gotpacketnum) &gt;= 1:
				print (f"\x1b[36mGot partial content from\x1b[0m \x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36mGot part {gotpacketnum}\x1b[0m")
				BigMessage += gotdatagram
			elif int(gotpacketnum) == -1:
				print (f"\x1b[36mBig transmission complete!\x1b[0m")
				if rcvdatatype == "PlainText":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36m{BigMessage}\x1b[0m")
				elif rcvdatatype == "Base64File":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36msent you a file!\x1b[0m")
					savefilepath = input("Enter file path and name to save, press enter to discard.\nfilepath: ")
					if not savefilepath == "":
						savefile(savefilepath,unbase64ify(BigMessage))
				BigMessage = ""
			continue
		if chatmessage == "":
			time.sleep(0.1)
		else:
			try:
				if len(chatmessage) &gt;= maxmessagelength:
					print ("Message is too big! Slicing message!")
					sentlength = 0
					slicecounter = 1
					while not sentlength &gt; len(chatmessage):
						print (f"Sending slice number {slicecounter}")
						UDPsend(destipaddr, destport, FormDatagram(username, str(slicecounter), packetdatatype, chatmessage[sentlength:sentlength+maxmessagelength]))
						sentlength += maxmessagelength
						slicecounter += 1
						time.sleep(0.2)
					UDPsend(destipaddr, destport, FormDatagram(username, str(-1), packetdatatype, ""))	
				else:
					UDPsend(destipaddr, destport, FormDatagram(username, "0", packetdatatype, chatmessage))
			except Exception as e:
				print(f"[ERROR] {e}")
	except Exception as e:
		print(f"[ERROR] {e}")
	except KeyboardInterrupt:
		print(f"\nTerminated by the user.")
		exit (0)
	
</code></pre>
]]></description><link>https://digitalvintage.ru/forum/topic/36/простейший-мессенджер-на-python-через-протокол-udp</link><generator>RSS for Node</generator><lastBuildDate>Sun, 26 Jul 2026 18:02:47 GMT</lastBuildDate><atom:link href="https://digitalvintage.ru/forum/topic/36.rss" rel="self" type="application/rss+xml"/><pubDate>Sun, 14 Jun 2026 16:07:01 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Fri, 19 Jun 2026 21:10:23 GMT]]></title><description><![CDATA[<p dir="auto">хы, а чего нет, так то</p>
]]></description><link>https://digitalvintage.ru/forum/post/206</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/206</guid><dc:creator><![CDATA[protogen]]></dc:creator><pubDate>Fri, 19 Jun 2026 21:10:23 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Fri, 19 Jun 2026 20:49:37 GMT]]></title><description><![CDATA[<p dir="auto">Особенно в случае с сервером - да, похоже<br />
Ибо так как тут нет обратной связи, то после того как сервер тебя запомнил можно ему ничего не передавать а просто его слушать. Можно вручную вписать в базу сервера IP (юзернейм любой при этом. Я не реализовал механизма фильтрации по именам пользователя так как здесь нет авторизации а значит одинаковые юзернеймы вполне возможны, что к слову приведет к одному неприятному багу который я постараюсь устранить)<br />
Но суть примерно такова - сервер просто рассылает пакеты всем кого знает, кроме того кто отправил пакет (а точнее тут такой прикол что кроме любого юзернейма совпадающего с отправителем. Потому и надо сделать пару доработок.)<br />
Короче - если хочешь - поиграйся: это интересно. Программу для перехвата пакетов можно использовать wireshark - там реально увидишь что плейнтекстом все без кодировок и шифров.</p>
]]></description><link>https://digitalvintage.ru/forum/post/205</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/205</guid><dc:creator><![CDATA[technodragon]]></dc:creator><pubDate>Fri, 19 Jun 2026 20:49:37 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Fri, 19 Jun 2026 18:42:17 GMT]]></title><description><![CDATA[<p dir="auto">эта хрень похожа на работу в эфире</p>
]]></description><link>https://digitalvintage.ru/forum/post/204</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/204</guid><dc:creator><![CDATA[protogen]]></dc:creator><pubDate>Fri, 19 Jun 2026 18:42:17 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Thu, 18 Jun 2026 12:32:31 GMT]]></title><description><![CDATA[<p dir="auto">Кстати я думаю что лучше не цитировать сообщения в ответах - получается дубль текста.</p>
]]></description><link>https://digitalvintage.ru/forum/post/203</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/203</guid><dc:creator><![CDATA[technodragon]]></dc:creator><pubDate>Thu, 18 Jun 2026 12:32:31 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Thu, 18 Jun 2026 12:31:58 GMT]]></title><description><![CDATA[<p dir="auto">Ну, в любом случае рад стараться. Если хоть кому то это будет полезно это уже хорошо.</p>
]]></description><link>https://digitalvintage.ru/forum/post/202</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/202</guid><dc:creator><![CDATA[technodragon]]></dc:creator><pubDate>Thu, 18 Jun 2026 12:31:58 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Thu, 18 Jun 2026 07:15:11 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/forum/user/technodragon" aria-label="Profile: technodragon">@<bdi>technodragon</bdi></a> <a href="/forum/post/199">said</a>:</p>
<p dir="auto">Да ладно... Кажется ты мне льстишь: этот код очень далек от звания лаконичного. Или ты про саму концепцию?</p>
</blockquote>
<p dir="auto">Про концепцию в первую очередь. Но и сам код на мой не очень развитый вкус довольно аккуратный)</p>
]]></description><link>https://digitalvintage.ru/forum/post/201</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/201</guid><dc:creator><![CDATA[ereinion]]></dc:creator><pubDate>Thu, 18 Jun 2026 07:15:11 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Tue, 16 Jun 2026 12:37:59 GMT]]></title><description><![CDATA[<p dir="auto">Итак небольшое обновление как клиента так и новая часть - сервер.<br />
Сервер представляет собой довольно простую релейную программу которая просто пересылает пакеты всем кроме отправителя. Есть функция запрета пересылки крупных передач (одна такая передача может превратиться в кошмар для всех пользователей)</p>
<p dir="auto">клиент:</p>
<pre><code>"""
 This is free and unencumbered software released into the public domain.

 Anyone is free to copy, modify, publish, use, compile, sell, or
 distribute this software, either in source code form or as a compiled
 binary, for any purpose, commercial or non-commercial, and by any
 means.

 In jurisdictions that recognize copyright laws, the author or authors
 of this software dedicate any and all copyright interest in the
 software to the public domain. We make this dedication for the benefit
 of the public at large and to the detriment of our heirs and
 successors. We intend this dedication to be an overt act of
 relinquishment in perpetuity of all present and future rights to this
 software under copyright law.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.

 For more information, please refer to &lt;http://unlicense.org/&gt;
"""
# Default settings. You may edit these to make startup easier.
destipaddr = "127.0.0.1"
username = "owo"
destport = 15001
listport = 15002
DoInitSetup = False
inputprompt = (f"\x1b[1;32m{username}&gt; \x1b[0m")
maxmessagelength = 32768

class UserError(Exception):
    pass

class PacketError(Exception):
	pass

import socket, threading, queue, time, sys, base64, os

BigMessage = ""
packetdatatype = "PlainText"

q = queue.Queue()

def UDPsend(ipaddr: str, portnumber: int, userdatagram: str) -&gt; None:   # Send a UDP packet with payload to an IP and a port
	payload = userdatagram.encode('utf-8')                              # We use unicode, more compatibility more better
	with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:         # It will use a random port to send
		s.sendto(payload, (ipaddr, portnumber))

def FormDatagram(usname = "Anonymous", packetnumber = "0", contents = "PlainText", thedatagram = "Placeholder."):
	completedatagram = f"Username: {usname.strip()} PacketNumber: {packetnumber.strip()} ContentType: {contents.strip()}\n{thedatagram}"
	return completedatagram

def IsValidIP(s: str) -&gt; bool:                                          # IP addr checker.
	if not isinstance(s, str) or not s:
		return False
	parts = s.split('.')
	if len(parts) != 4:
		return False
	for p in parts:                                                     # no empty parts, no leading '+' or '-' signs
		if not p or (p[0] in '+-'):
			return False
		if len(p) &gt; 1 and p[0] == '0':                                  # disallow leading zeros unless the part is exactly "0"
			return False
		if not p.isdigit():                                             # all characters must be digits
			return False
		val = int(p)
		if val &lt; 0 or val &gt; 255:                                        # numeric range 0-255
			return False

	return True

def initialsetup():
	try:                                                                # Initial setup dialog
		global destipaddr, username, destport, listport, inputprompt
		usname = input("Username: ").strip()
		if usname == "":
			print(f"None given, you'll be {username}")
		elif len(usname) &lt;=3 or len(usname) &gt;= 24:
			 raise UserError("username is too long or too short. Please use a sane name.")
		elif any(not (ch.isalnum() or ch == '_') for ch in usname):
			raise UserError("Username may contain numbers, letters and underscore. Please use a sane name.")
		else:
			username = usname
			print(f"You'll be seen as {username}")
			inputprompt = (f"\x1b[1;32m{username}&gt; \x1b[0m")
		destip = input("Destination IP address: ").strip()
		if destip=="":
			print(f"You'll talk to (IP: {destipaddr})")
		elif IsValidIP(destip):
			destipaddr = destip
		else:
			raise UserError("This is not a valid IP address")
		dstport = input("Destination port: ").strip()
		if dstport =="":
			print(f"None given, using {destport}")
		elif any(not ch.isdigit() for ch in dstport):
			raise UserError("Non-numeric string entered.")
		elif int(dstport) &gt;= 65535 or int(dstport)&lt;= 0:
			raise UserError("Port number should be from 1 to 65534 but please keep it within reason.")
		else:
			destport = int(dstport)
		lstport = input("Listening port: ").strip()
		if lstport =="":
			print(f"None given, using {listport}")
		elif any(not ch.isdigit() for ch in lstport):
			raise UserError("Non-numeric string entered.")
		elif int(lstport) &gt;= 65535 or int(lstport)&lt;= 0:
			raise UserError("Port number should be from 1 to 65534 but please keep it within reason.")
		else:
			listport = int(lstport)
	except UserError as e:
		print (e)
		exit (1)

def base64ify(filepath):                                                # Encode a file into base64
	try:
		with open(filepath.strip(), "rb") as inputfile:
			return base64.b64encode(inputfile.read()).decode()
	except FileNotFoundError:
		print (f"File {filepath} does not exist.")
		return None
	except Exception as e:
		print ("Error encoding a file:", e)
		return None

def unbase64ify(s):                                                     # Decode base64 into bytes
	try:
		return base64.b64decode(s.strip())
	except Exception as e:
		print("Invalid base64 data:", e)
		return None

def savefile(path, data):                                               # Save bytes into file
	path = path.strip()
	if not path:
		print ("Error: wrong path!")
		return False
	if os.path.exists(path):
		print ("Error! File exists!")
		return False
	with open(path, "wb") as f:
		f.write(data)
	return True        

def input_thread():                                                     # Chat input prompt
	global packetdatatype
	while True:
		try:
			chatmessage = ""
			time.sleep(0.1)
			chatpart = input(inputprompt)
			if chatpart[:len("file://")] == "file://":
				packetdatatype = "Base64File"
				chatmessage=base64ify(chatpart[len("file://"):])
			elif chatpart[:len(":clear")] == ":clear":
				BigMessage = ""
				print ("sliced message buffer cleared.")
			else:
				packetdatatype = "PlainText"
				while not(chatpart == ""):
					chatmessage += (f"{chatpart}\n")
					chatpart = input()
			if not chatmessage == None:
				q.put(chatmessage)
		except Exception as e:
			print(f"[ERROR] {e}")


if DoInitSetup:
	print ("initial setup. You may adjust config to not do it all the time")
	initialsetup()
else:
	print("Initial setup disabled. Built in defaults used:")
	print(f"Username: {username}\nDestination IP: {destipaddr}\nDestination port: {destport}\nListening port: {listport}")

t = threading.Thread(target=input_thread, daemon=True)
t.start()

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", int(listport)))
sock.setblocking(False)


while True:
	try:
		try:
			data, rcvaddr = sock.recvfrom(65535)
		except BlockingIOError:
			try:
				chatmessage = q.get_nowait()
			except queue.Empty:
				time.sleep(0.1)
				continue
		else:                                                           # We need to parse a message.
			gottext         = data.decode(errors="replace")             # Decode UDP packet. It should be plain text.
			possender       = gottext.find("Username: ")                # We find first field of a header - the username
			pospacketnum    = gottext.find("PacketNumber: ")            # We find the packet number. 0 for single packets, non-zero real for multiple, -1 for a terminator.
			posdatatype     = gottext.find("ContentType: ")             # Type of contents in a packet. Currently there're PlainText and Base64File
			posfirstnewline = gottext.find(f"\n")                       # Actual contents begin from new line to the end of a packet
			if possender == -1 or pospacketnum == -1 or posdatatype == -1 or posfirstnewline == -1: # handle if header s bad.
				raise PacketError("Got malformed or unintended packet.")
			sendername   = gottext[possender+len("Username: "):pospacketnum -1 ].strip()
			gotpacketnum = gottext[pospacketnum+len("PacketNumber: "):posdatatype - 1].strip()
			rcvdatatype  = gottext[posdatatype+len("ContentType: "):posfirstnewline].strip()
			gotdatagram  = gottext[posfirstnewline + 1:].rstrip()
			if gotpacketnum == "0":
				if rcvdatatype == "PlainText":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36m{gotdatagram}\x1b[0m")
				elif rcvdatatype == "Base64File":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36msent you a file!\x1b[0m")
					savefilepath = input("Enter file path and name to save, press enter to discard.\nfilepath: ")
					if not savefilepath == "":
						savefile(savefilepath,unbase64ify(gotdatagram))
				else:
					raise PacketError("Wrong data type! Data dropped!")
			elif int(gotpacketnum) == 1:
				print (f"\x1b[36mGot partial content from\x1b[0m \x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36mGot part {gotpacketnum}\x1b[0m")
				BigMessage = gotdatagram
			elif int(gotpacketnum) &gt; 1:
				print (f"\x1b[36mGot partial content from\x1b[0m \x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36mGot part {gotpacketnum}\x1b[0m")
				BigMessage += gotdatagram
			elif int(gotpacketnum) == -1:
				print (f"\x1b[36mBig transmission complete!\x1b[0m")
				if rcvdatatype == "PlainText":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36m{BigMessage}\x1b[0m")
				elif rcvdatatype == "Base64File":
					print(f"\n\x1b[1;36m{sendername}@{rcvaddr[0]}:\x1b[0m \x1b[36msent you a file!\x1b[0m")
					savefilepath = input("Enter file path and name to save, press enter to discard.\nfilepath: ")
					if not savefilepath == "":
						savefile(savefilepath,unbase64ify(BigMessage))
				BigMessage = ""
			else:
				raise PacketError("Got wrong packet number. Buffer cleared.")
			continue
		if chatmessage == "":
			time.sleep(0.1)
		else:
			try:
				if len(chatmessage) &gt;= maxmessagelength:
					print ("Message is too big! Slicing message!")
					sentlength = 0
					slicecounter = 1
					while not sentlength &gt; len(chatmessage):
						print (f"Sending slice number {slicecounter}")
						UDPsend(destipaddr, destport, FormDatagram(username, str(slicecounter), packetdatatype, chatmessage[sentlength:sentlength+maxmessagelength]))
						sentlength += maxmessagelength
						slicecounter += 1
						time.sleep(0.2)
					UDPsend(destipaddr, destport, FormDatagram(username, "-1", packetdatatype, ""))	
				else:
					UDPsend(destipaddr, destport, FormDatagram(username, "0", packetdatatype, chatmessage))
			except Exception as e:
				print(f"[ERROR] {e}")
	except PacketError as e:
		gottext = ""
		BigMessage = ""
		del data
		print (e)
		continue
	except KeyboardInterrupt:
		print(f"\nTerminated by the user.")
		exit (0)
	except Exception as e:
		print(f"[ERROR] {e}")
	

</code></pre>
<p dir="auto">Ну и сервер:</p>
<pre><code># Server implementaion of UDPChat_aplha
# It's a relay server. It just forwards packets.
# Be sure to configure the server. Do not run defaults.

"""
 This is free and unencumbered software released into the public domain.

 Anyone is free to copy, modify, publish, use, compile, sell, or
 distribute this software, either in source code form or as a compiled
 binary, for any purpose, commercial or non-commercial, and by any
 means.

 In jurisdictions that recognize copyright laws, the author or authors
 of this software dedicate any and all copyright interest in the
 software to the public domain. We make this dedication for the benefit
 of the public at large and to the detriment of our heirs and
 successors. We intend this dedication to be an overt act of
 relinquishment in perpetuity of all present and future rights to this
 software under copyright law.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.

 For more information, please refer to &lt;http://unlicense.org/&gt;
"""

# Default settings. You may edit these to make startup easier.
servername = "server"
destport = 15002
listport = 15001
dbfile   = "userbase.txt"
allow_multipacket = False

class PacketError(Exception):
	pass

import socket, threading, queue, time, sys, os

def loadbase(dbpath):
	ipdatabase = {}
	try:
		if os.path.exists(dbfile):
			with open (dbfile, "r") as db:
				for lineno, line in enumerate(db, start=1):
					if not line.strip():
						continue
					try:
						username, ip = line.strip().split()
						ipdatabase[username]  =ip
					except ValueError:
						continue
			print ("Loaded user database: ", ipdatabase)
			return ipdatabase
	except Exception as e:
		print(e)
		return None

def updateaddr(ipbase, username, ipaddr):
	oldip = ipbase.get(username)
	if oldip == None:
		ipbase[username] = ipaddr
		return True
	elif not oldip == ipaddr:
		ipbase[username] = ipaddr
		return True
	return False

def savedb(ipbase,filename):
	with open (dbfile, "w") as db:
		for user, ip in sorted(ipbase.items()):
			db.write(f"{user} {ip}\n")

ipbase = {}
q = queue.Queue()
ipbase = loadbase(dbfile)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", int(listport)))
sock.setblocking(False)

print ("UDPChat server. Just a relay server.")

while True:
	try:
		try:
			data, rcvaddr = sock.recvfrom(65535)
		except BlockingIOError:
			try:
				chatmessage = q.get_nowait()
			except queue.Empty:
				time.sleep(0.1)
				continue
		else:                                                           # We need to parse a message.
			gottext         = data.decode(errors="replace")             # Decode UDP packet. It should be plain text.
			possender       = gottext.find("Username: ")                # We find first field of a header - the username
			pospacketnum    = gottext.find("PacketNumber: ")            # We find the packet number. 0 for single packets, non-zero real for multiple, -1 for a terminator.
			posdatatype     = gottext.find("ContentType: ")             # Type of contents in a packet. Currently there're PlainText and Base64File
			posfirstnewline = gottext.find(f"\n")                       # Actual contents begin from new line to the end of a packet
			if possender == -1 or pospacketnum == -1 or posdatatype == -1 or posfirstnewline == -1: # handle if header s bad.
				raise PacketError("Got malformed or unintended packet.")
			sendername   = gottext[possender+len("Username: "):pospacketnum -1 ].strip()
			gotpacketnum = gottext[pospacketnum+len("PacketNumber: "):posdatatype - 1].strip()
			rcvdatatype  = gottext[posdatatype+len("ContentType: "):posfirstnewline].strip()
			if int(gotpacketnum) == 1 and allow_multipacket:
				raise PacketError(f"Multi-packet transmission from {sendername}@{rcvaddr[0]}")
			elif int(gotpacketnum) &gt; 1:
				raise PacketError(f"Multi-packet transmission from {sendername}@{rcvaddr[0]}")
			elif int(gotpacketnum) == -1:
				with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
						s.sendto(bytes(f"Username: {servername} PacketNumber: 0 ContentType: PlainText\nMulti-packet transmissions are not allowed on this server.","utf-8"), (rcvaddr[0], destport))
				raise PacketError(f"Multi-packet transmission from {sendername}@{rcvaddr[0]}")
			if updateaddr(ipbase, sendername, rcvaddr[0]):              # Update db if we have a new user or user changed their ip
				savedb(ipbase, dbfile)
				print (f"New user or user changed IP: {sendername}@{rcvaddr[0]}")
			for user, ip in sorted(ipbase.items()):                     # relay to all other users
				if not user == sendername:
					with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
						s.sendto(data, (ip, destport))
	except PacketError as e:
		print (e)
		continue
	except KeyboardInterrupt:
		print("saving and exiting.")
		savedb(ipbase, dbfile)
		exit (0)
	except Exception as e:
		print(e)
	

</code></pre>
]]></description><link>https://digitalvintage.ru/forum/post/200</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/200</guid><dc:creator><![CDATA[technodragon]]></dc:creator><pubDate>Tue, 16 Jun 2026 12:37:59 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Mon, 15 Jun 2026 15:08:55 GMT]]></title><description><![CDATA[<p dir="auto">Да ладно... Кажется ты мне льстишь: этот код очень далек от звания лаконичного. Или ты про саму концепцию?</p>
]]></description><link>https://digitalvintage.ru/forum/post/199</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/199</guid><dc:creator><![CDATA[technodragon]]></dc:creator><pubDate>Mon, 15 Jun 2026 15:08:55 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Mon, 15 Jun 2026 08:17:35 GMT]]></title><description><![CDATA[<p dir="auto">Прикольное! И лаконичное!</p>
]]></description><link>https://digitalvintage.ru/forum/post/198</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/198</guid><dc:creator><![CDATA[ereinion]]></dc:creator><pubDate>Mon, 15 Jun 2026 08:17:35 GMT</pubDate></item><item><title><![CDATA[Reply to Простейший &quot;мессенджер&quot; на python через протокол UDP on Sun, 14 Jun 2026 19:00:38 GMT]]></title><description><![CDATA[<p dir="auto">лол, забавная чухня</p>
]]></description><link>https://digitalvintage.ru/forum/post/197</link><guid isPermaLink="true">https://digitalvintage.ru/forum/post/197</guid><dc:creator><![CDATA[protogen]]></dc:creator><pubDate>Sun, 14 Jun 2026 19:00:38 GMT</pubDate></item></channel></rss>