I am writing a P2P messenger

Amid the discussion of the future Internet messenger and reading the article "Why is it your favorite instant messenger must die", decided to share her experience of creating P2P applications to communicate regardless of third-party servers. More precisely — it's just processing, transmitting one message from the client to the server, further extends the functionality depends only on Your imagination.
In this publication we will write 3 simple application for P2P communication from any point of the globe — client, server, and alarm server.
We will need:
— single server with white static IP address;
— 2 computers behind a NAT with connection type is Full Cone NAT (or 1 computer with 2 virtual machines);
— A STUN server.
Full Cone NAT is a type of NAT in which there is a clear stream between pairs of "internal address: internal port" and "public address: public port".
Here's what we can read about a STUN server on the Wiki:
"There are protocols that use UDP packets to transfer voice, image or text over IP networks. Unfortunately, if both communicating parties are behind NAT ohms, the connection cannot be established in the usual way. It is here that STUN and is useful. It allows a client behind the address translation server (or several such servers) to identify your external IP address, the method of transmission address and port of the external network associated with a specific internal port number."
When solving the problem we used the following Python modules: socket, twisted, stun, sqlite3, os, sys.
For data exchange between Server and Client, and between Server, Client and the Signaling Server uses the UDP Protocol.
In General terms, the functioning of the mechanism looks like this:
Server <-> STUN server
Client <-> STUN server
Server <-> Signal the Server
Client <-> Signal the Server
Client -> Server
1. The client being behind a NAT with connection type, Full Cone NAT, it sends a message to the STUN server, and receives a response in the form of its external IP and open PORT;
2. The server being behind a NAT with connection type is Full Cone NAT, it sends a message to the STUN server, and receives a response in the form of its external IP and open PORT;
In this case, the Client and Server is known to the external (white) IP and PORT of the Signaling Server;
3. The server sends the Signal to the data Server on its external IP and PORT of the Signaling Server stores them;
4. The client sends a Signal to the data Server on its external IP and PORT and id_destination a search Server, which expects its external IP, PORT.
Signal to the Server, saves, searches the database using a id_destination and, in response, gives the information found in the form of a line: 'id_host, name_host, ip_host, port_host';
5. The client accepts the information found, splits on the separator and using (ip_host, port_host), sends a message to the Server.
Applications written in Python version 2.7, tested under Debian 7.7.
Create a file server.py with the contents:
server.py
# -*- coding: utf-8 -*-
#SERVER
from socket import *
import sys
import stun
def sigserver_exch():
# SERVER <-> SIGNAL the SERVER
# SERVER < CLIENT
# The SERVER sends a request to the SIGNALING SERVER with white static IP with the data about the current values of IP and PORT. Accepts the request from the CLIENT.
#External IP and PORT of the SIGNALING SERVER:
v_sig_host = 'XX.XX.XX.XX'
v_sig_port = XXXX
#the id of the CLIENT, the CLIENT's name, id of the desired SERVER
v_id_client = 'id_server_1002'
v_name_client = 'name_server_2'
v_id_server = 'none'
#IP and PORT of this CLIENT
v_ip_localhost = 'XX.XX.XX.XX'
v_port_localhost = XXXX
udp_socket = "
try:
#Get the current external IP and PORT using a STUN utility
nat_type, external_ip, external_port = stun.get_ip_info()
#Assign variable white IP and PORT of the signaling server to send the request
port_sigserver = v_sig_port
addr_sigserv = (host_sigserver,port_sigserver)
#Fill the dictionary with data to be sent to SIGNALING SERVER:
#the current id + name + current external IP and PORT
#id_dest - specify 'none'
#As the id, you can use the random number hash + salt
data_out = v_id_client + ',' + v_name_client + ',' + external_ip + ',' + str(external_port) + ',' + v_id_server
#Create the socket with the attributes:
#use an Internet address (AF_INET),
#transfer data in the form of separate messages
udp_socket = socket(AF_INET, SOCK_DGRAM)
#Assign a variable to your local IP and a free PORT for information
host = v_ip_localhost
port = v_port_localhost
addr = (host,port)
#Bind the socket to a local IP and PORT
udp_socket.bind(addr)
print('binding socket')
#Send message to ALARM SERVER
udp_socket.sendto(data_out,addr_sigserv)
while True:
#If the first element of the list is 'sigserv' (SIGNALING SERVER),
#print the message with the received data
#Otherwise print the message 'Message from CLIENT!'
data_in = udp_socket.recvfrom(1024)
if data_in[0] == 'sigserv':
print('signal server data: ', data_in)
else:
print('Message from CLIENT!')
#Close the socket
udp_socket.close()
except:
print('exit!')
sys.exit(1)
finally:
if udp_socket <> "
udp_socket.close()
sigserver_exch()
Fill in the appropriate fields sections: "External IP and PORT of the SIGNALING SERVER and the IP and PORT of this CLIENT".
Create a file client.py with the contents:
client.py
# -*- coding: utf-8 -*-
# CLIENT
from socket import *
import sys
import stun
def sigserver_exch():
# CLIENT <-> SIGNAL the SERVER
# CLIENT -> SERVER
# CLIENT sends a request to SIGNAL the SERVER with white IP
# to retrieve the current values IP and PORT of the SERVER behind a NAT to connect to it.
#External IP and PORT of the SIGNALING SERVER:
v_sig_host = 'XX.XX.XX.XX'
v_sig_port = XXXX
#the id of the CLIENT, the CLIENT's name, id of the desired SERVER
v_id_client = 'id_client_1001'
v_name_client = 'name_client_1'
v_id_server = 'id_server_1002'
#IP and PORT of this CLIENT
v_ip_localhost = 'XX.XX.XX.XX'
v_port_localhost = XXXX
udp_socket = "
try:
#Get the current external IP and PORT using a STUN utility
nat_type, external_ip, external_port = stun.get_ip_info()
#Assign variable white IP and PORT of the signaling server to send the request
host_sigserver = v_sig_host
port_sigserver = v_sig_port
addr_sigserv = (host_sigserver,port_sigserver)
#Fill the dictionary with data to be sent to SIGNALING SERVER:
#the current id + name + current external IP and PORT
#id_dest - id known to the server which we want to connect.
#As the id, you can use the random number hash + salt
data_out = v_id_client + ',' + v_name_client + ',' + external_ip + ',' + str(external_port) + ',' + v_id_server
#Create the socket with the attributes:
#use an Internet address (AF_INET),
#transfer data in the form of separate messages
udp_socket = socket(AF_INET, SOCK_DGRAM)
#Assign a variable to your local IP and a free PORT for information
host = v_ip_localhost
port = v_port_localhost
addr = (host,port)
#Bind the socket to a local IP and PORT
udp_socket.bind(addr)
#Send message to ALARM SERVER
udp_socket.sendto(data_out, addr_sigserv)
while True:
#If the first element of the list is 'sigserv' (SIGNALING SERVER),
#print the message with the received data and send a message
#'Hello SERVER!' to the server specified in the message address.
data_in = udp_socket.recvfrom(1024)
data_0 = data_in[0]
data_p = data_0.split(",")
if data_p[0] == 'sigserv':
print('signal server data: ', data_p)
udp_socket.sendto('Hello, SERVER!',(data_p[3],int(data_p[4])))
else:
print("No, it is not Rio de Janeiro!")
udp_socket.close()
except:
print ('Exit!')
sys.exit(1)
finally:
if udp_socket <> "
udp_socket.close()
sigserver_exch()
Fill in the appropriate fields sections: "External IP and PORT of the SIGNALING SERVER and the IP and PORT of this CLIENT".
Create a file signal_server.py with the contents:
signal_server.py
# -*- coding: utf-8 -*-
# SIGNAL SERVER
#Twisted - event-driven(event) structure
#Manage events function event handler
#The event loop monitors events and triggers the appropriate event handler
#Work cycle lies in the object reactor from twisted module.internet
from twisted.internet import reactor
from twisted.internet.protocol import DatagramProtocol
import sys, os
import sqlite3
class Query_processing_server(DatagramProtocol):
# SIGNAL the SERVER <-> CLIENT
# CLIENT -> SERVER
# either
# SIGNAL the SERVER <-> SERVER
# The SIGNAL SERVER receives requests from CLIENT and SERVER
# saves the current values of IP and PORT
# (if missing - creates a new + name and ID)
# and outputs the IP and PORT of the SERVER requested by the CLIENT.
def datagramReceived(self, data, addr_out):
conn = "
try:
#Split the data on the delimiter (,) [id_host,name_host,external_ip,external_port,id_dest]
#id_dest - the desired id of the server
data = data.split(",")
#Request the path to the database file sqlite3, in the absence of it will create a new database at the specified path:
path_to_db = raw_input('Enter db name. For example: "/home/user/new_db.db": ')
path_to_db = os.path.join(path_to_db)
#Create DB connection
conn = sqlite3.connect(path_to_db)
#Convert to Unicode bitstrike
conn.text_factory = str
#Create a cursor object
c = conn.cursor()
#Create a mapping table for hosts
c.execute("'CREATE TABLE IF NOT EXISTS compliance_table ("id_host" text UNIQUE, "name_host" text, "ip_host" text, \
"port_host" text)"')
#Add the new host, if not already created
#Update the ip, port to an existing host
c.execute('INSERT OR IGNORE INTO compliance_table VALUES (?, ?, ?, ?);', data[0:len(data)-1])
#Save the changes
conn.commit()
c.execute('SELECT * FROM compliance_table')
#Find the data on the server by its id
c.execute("'SELECT id_host, name_host, ip_host, port_host from compliance_table WHERE id_host=?"', (data[len(data)-1],))
cf = c.fetchone()
if cf == None:
print ('Server_id' not found!')
else:
#transport.write - sends data messages: id_host, name_host, ip_host, port_host and mark sigserver
lst = 'sigserv' + ',' + cf[0] + ',' + cf[1] + ',' + cf[2] + ',' + cf[3]
self.transport.write(str(lst), addr_out)
#Close the connection
conn.close()
except:
print ('Exit!')
sys.exit(1)
finally:
if conn <> "
conn.close()
reactor.listenUDP(9102, Query_processing_server())
print('reactor run!')
reactor.run()
Done!
The order of the applications is as follows:
— signal_server.py
— server.py
— client.py
Комментарии
Отправить комментарий