Описание окружающей среды
- Операционная система: Mac
- Язык программирования: Python3
- Зависит от библиотек Python: tkinter, pymysql, ssl, socket и т. д. (в основном встроено в Python)
- В mysql сначала должна быть создана база данных: filetransfer, новая таблица user, содержащая три поля: id, имя пользователя, пароль
создать сертификат
PROJECT_NAME="jiang zheng fool Project"
# Generate the openssl configuration files.
cat > ca_cert.conf << EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
O = $PROJECT_NAME Certificate Authority
EOF
cat > server_cert.conf << EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
O = $PROJECT_NAME
CN = SERVER
EOF
cat > client_cert.conf << EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
O = $PROJECT_NAME Device Certificate
CN = SERVER
EOF
mkdir ca
mkdir server
mkdir client
mkdir certDER
# private key generation
openssl genrsa -out ca.key 1024
openssl genrsa -out server.key 1024
openssl genrsa -out client.key 1024
# cert requests
openssl req -out ca.req -key ca.key -new -config ./ca_cert.conf
openssl req -out server.req -key server.key -new -config ./server_cert.conf
openssl req -out client.req -key client.key -new -config ./client_cert.conf
# generate the actual certs.
openssl x509 -req -in ca.req -out ca.crt -sha1 -days 5000 -signkey ca.key
openssl x509 -req -in server.req -out server.crt -sha1 -CAcreateserial -days 5000 -CA ca.crt -CAkey ca.key
openssl x509 -req -in client.req -out client.crt -sha1 -CAcreateserial -days 5000 -CA ca.crt -CAkey ca.key
mv ca.crt ca.key ca/
mv server.crt server.key server/
mv client.crt client.key client/
Услуги, предоставляемые протоколом SSL, в основном включают:
1) Аутентифицировать пользователей и серверы, чтобы гарантировать, что данные отправляются на правильный клиент и сервер;
2) Шифрование данных для предотвращения кражи данных на полпути;
3) Поддерживать целостность данных, чтобы гарантировать, что данные не изменятся во время передачи.
Сгенерированный файл сертификата выглядит следующим образом:
Откройте ca.crt, и вы увидите результат, как показано ниже:
Обзор
Проект в основном состоит из двух небольших проектов, включая функцию группового чата и систему передачи файлов. Функция группового чата в основном реализует такие функции, как многогрупповой чат и кешированные сообщения. Система передачи файлов включает загрузку и скачивание файлов.
Функция группового чата
запуск программы
- client.py 客户端1
- client2.py 客户端2
- server.py 服务端
визуализация
Введение в папку
- cer — в этой папке хранится корневой сертификат ЦС, а также сертификаты сервера и клиента (созданные с использованием OpenSSL).
- CA -- корневой сертификат и ключ
- server -- ключ сервера, подписанный сертификат и подписанный сертификат
- client -- секретный ключ клиента, подписанный сертификат и подписанный сертификат
Введение в файл
MultiPersonChat
- client.py клиент 1
- клиент2.py клиент2
- server.py сервер
полная функция
- Онлайн-напоминание, офлайн-напоминание
- SSL-ссылка
- шифрование пароля
- Пользователь не может повторно войти в систему
- На стороне сервера необходимо кэшировать некоторые последние исторические сообщения, чтобы передать их только что запущенному клиенту.
- Визуальный интерфейс
- многопользовательский чат
client.py Класс клиента
имя метода | конкретная функция |
---|---|
send_login_info | Отправить имя пользователя и пароль вошедшего в систему пользователя на сервер для проверки и вернуть результат проверки |
send_register_info | Отправить зарегистрированное имя пользователя и пароль на сервер и вернуть результат регистрации |
recv_data | Клиент получает данные от сервера |
close | Закройте сокет для соединения между клиентом и сервером |
client.py Класс LoginPanel
имя метода | конкретная функция |
---|---|
set_panel_position | Установите положение и размер интерфейса входа в систему на экране |
config_for_reg_panel | Установите другие конфигурации для интерфейса входа в систему |
set_title | Поместите заголовок интерфейса |
set_form | Разместите форму входа |
set_btn | Разместите кнопки регистрации и входа |
show | Вызовите метод экземпляра, чтобы сделать общий макет интерфейса входа в систему |
close | Реализовать закрытие интерфейса |
get_input | Получить имя пользователя и пароль, введенные пользователем |
login_func | Функция кнопки входа инкапсулирована в интерфейс входа |
reg_func | Он встроен в кнопку регистрации интерфейса входа в систему, чтобы реализовать переход от интерфейса входа в интерфейс регистрации. |
server.py
имя метода | Функции |
---|---|
encrypt_psw | Зашифровать пароль пользователя с помощью алгоритма MD5. |
check_user | Убедитесь, что имя пользователя и пароль, введенные пользователем при входе в систему, верны |
add_user | Проверьте имя пользователя, которое нужно зарегистрировать, чтобы определить, есть ли повторяющиеся имена пользователей. |
update_online_list | Обновить список онлайн-пользователей клиента |
online_notice | Отправить уведомление о новом клиенте онлайн всем онлайн-клиентам |
offline_notice | Отправлять уведомление пользователя в автономном режиме всем онлайн-пользователям |
handle_login | Обработка запросов на вход |
handle_reg | Обработка запросов на регистрацию клиентов |
handle_msg | Транслировать то, что клиент хочет отправить |
handle | Основной фрейм, на котором работает сервер |
Код ключа следующий, отвечающий за распределение запросов:
def handle(new_socket, addr):
"""
服务器运行的主框架
:param new_socket: 本次连接的客户端套接字
:param addr: 本次连接客户端的ip和port
"""
try:
while True:
req_type = new_socket.recv(1).decode("utf-8") # 获取请求类型
print(req_type)
if req_type: # 如果不为真,则说明客户端已断开
if req_type == "1": # 登录请求
print("开始处理登录请求")
handle_login(new_socket)
elif req_type == "2": # 注册请求
print("开始处理注册请求")
handle_reg(new_socket)
elif req_type == "3": # 发送消息
print("开始处理发送消息请求")
handle_msg(new_socket)
else:
break
except Exception as ret:
print(str(addr) + " 连接异常,准备断开: " + str(ret))
finally:
try:
# 客户端断开后执行的操作
new_socket.close()
online_socket.remove(new_socket)
offline_notice(new_socket)
socket2user.pop(new_socket)
time.sleep(4)
update_online_list()
except Exception as ret:
print(str(addr) + "连接关闭异常")
Введение в функцию кэширования
Содержимое каждого чата и пользователя хранится в массиве через структуру, а затем распространяется пользователю, когда пользователь входит в систему.
Функция передачи файлов безопасности
Метод запуска
- Запустите сервер:
python server_ssl.py
python server_no_ssl.py
- Запустите клиент:
python main.py
Папка Описание
- cer — в этой папке хранится корневой сертификат ЦС, а также сертификаты сервера и клиента (созданные с использованием OpenSSL).
- CA -- корневой сертификат и ключ
- server -- ключ сервера, подписанный сертификат и подписанный сертификат
- client -- секретный ключ клиента, подписанный сертификат и подписанный сертификат
- ClientCache — в этом каталоге хранятся данные списка загрузки, которые запрашивают обновления с сервера.
- ClientDownload -- путь загрузки клиента
- ServerRec -- путь загрузки сервера
описание файла
- файл запуска клиента main.py
- client_login.py интерфейс входа клиента
- client_mian.py основной интерфейс клиента
- вид основного интерфейса клиента view.py
- client_socket_no_ssl.py Клиент не шифрует объекты связи
- client_socket_ssl.py объект зашифрованной связи клиента
- Сервер server_no_ssl.py не шифрует код связи
- Шифрование сервера server_ssl.py не шифрует код связи
- result.txt используется для записи списка загрузки сервера
- Serverlog.txt Журнал сервера
блок-схема
Серверный процесс выглядит следующим образом:
Схема работы клиента выглядит следующим образом:
Пользовательский транспортный протокол
Пользовательский заголовок добавляется к каждому обмену данными между сервером и клиентом.Клиент активно запрашивает данные с сервера, а сервер возвращает пассивно, поэтому заголовки пакетов у них будут разными. Эта система использует структурную структуру Python для реализации передачи двоичного потока заголовка.
Содержимое заголовка клиента следующее: (1024 байта)
- Команда включает в себя: Скачать, Загрузить, Обновить, Войти и Зарегистрироваться.
- имя_файла — это имя файла, загружаемого командой загрузки, и путь к локально загруженному файлу в режиме загрузки.
- png это размер файла
- время - время запроса данных
- Пользователь и пароль — это имя пользователя и пароль, которые будут проверяться каждый раз при запросе данных, имитируя режим Cookie.
header = {
'Command': 'Download',
'fileName': filename,
'fileSize': '',
'time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
'user': self.username,
'password': self.password,
}
Использование структуры в python выглядит следующим образом
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('1024s', header_hex)
self.ssock.send(fhead)
Содержимое заголовка сервера выглядит следующим образом: (128 байт)
Поскольку сервер пассивно отвечает клиенту, содержимого заголовка не должно быть много, поэтому используется 128 байт.
- Обратная связь указывает на команду, на которую следует реагировать.
- Stat показывает статус ответа (например, зарегистрирован, вошел в систему и т. д.)
- Размер файла - это размер файла
- Пользователь является текущим пользователем
header = {
'Feedback': 'Login',
'stat': 'Success',
'fileSize': os.stat(listResult).st_size,
'user': username
}
ключевой код
Ответственный за распределение запросов:
def conn_thread(self,connection):
while True:
try:
connection.settimeout(60)
fileinfo_size = struct.calcsize('1024s')
buf = connection.recv(fileinfo_size)
if buf: # 如果不加这个if,第一个文件传输完成后会自动走到下一句
header_json = str(struct.unpack('1024s', buf)[0], encoding='utf-8').strip('\00')
#print(header_json)
header = json.loads(header_json)
Command = header['Command']
if Command == 'Upload':
fileName = header['fileName']
fileSize = header['fileSize']
time = header['time']
user = header['user']
filenewname = os.path.join('ServerRec/', fileName)
print('Upload: file new name is %s, filesize is %s' % (filenewname, fileSize))
recvd_size = 0 # 定义接收了的文件大小
file = open(filenewname, 'wb')
print('start receiving...')
while not recvd_size == fileSize:
if fileSize - recvd_size > 1024:
rdata = connection.recv(1024)
recvd_size += len(rdata)
else:
rdata = connection.recv(fileSize - recvd_size)
recvd_size = fileSize
file.write(rdata)
file.close()
print('receive done')
fileSize = float(fileSize)
if fileSize<1024.0:
fileSize = "%s bytes"%(int(fileSize))
elif fileSize/1024.0 <= 1024.0:
fileSize = "%.2f Kb"%(fileSize/1024.0)
elif fileSize/1024.0/1024.0 <= 1024.0:
fileSize = "%.2f Mb"%(fileSize/1024.0/1024.0)
else:
fileSize = "%.2f Gb"%(fileSize/1024.0/1024.0/1024.0)
uploadmsg = '{"文件名": "%s", "上传者": "%s", "上传时间": "%s", "大小": "%s"}\n'%\
(fileName,user,time,fileSize)
with open('result.txt', 'a', encoding='utf-8') as list:
list.write(uploadmsg)
uploadlog = '\n%s upload a file "%s" at %s' % \
(user, fileName, time)
with open('Serverlog.txt', 'a', encoding='utf-8') as list:
list.write(uploadlog)
#connection.close()
elif Command == 'Login':
# 查询数据表数据
username = header['user']
password = header['password']
time = header['time']
sql = "select * from user where username = '%s' and password = '%s'"%(username,password)
cursor.execute(sql)
data = cursor.fetchone()
if data:
listResult = './result.txt'
# 定义文件头信息,包含文件名和文件大小
header = {
'Feedback': 'Login',
'stat': 'Success',
'fileSize': os.stat(listResult).st_size,
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
fo = open(listResult, 'rb')
while True:
filedata = fo.read(1024)
if not filedata:
break
connection.send(filedata)
fo.close()
print('%s login successfully')
loginlog = '\n%s try to login at "%s" , Stat: Success ' % \
(username, time)
with open('Serverlog.txt', 'a', encoding='utf-8') as list:
list.write(loginlog)
#connection.close()
else:
header = {
'Feedback': 'Login',
'stat': 'Fail',
'fileSize': '',
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
loginlog = '\n%s try to login at "%s" , Stat: Fail ' % \
(username, time)
with open('Serverlog.txt', 'a', encoding='utf-8') as list:
list.write(loginlog)
elif Command == 'Download':
# 查询数据表数据
username = header['user']
password = header['password']
time = header['time']
sql = "select * from user where username = '%s' and password = '%s'" % (username, password)
cursor.execute(sql)
data = cursor.fetchone()
filename = header['fileName']
if data:
filepath = os.path.join('./ServerREc/', filename)
# 定义文件头信息,包含文件名和文件大小
header = {
'Feedback': 'Download',
'stat': 'Success',
'fileSize': os.stat(filepath).st_size,
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
fo = open(filepath, 'rb')
while True:
filedata = fo.read(1024)
if not filedata:
break
connection.send(filedata)
fo.close()
print('send file over...')
downloadlog = '\n%s download a file "%s" at %s' % \
(username, filename, time)
with open('Serverlog.txt', 'a', encoding='utf-8') as list:
list.write(downloadlog)
# connection.close()
else:
header = {
'Feedback': 'Download',
'stat': 'LoginFail',
'fileSize': '',
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
elif Command == 'Update':
# 查询数据表数据
username = header['user']
password = header['password']
sql = "select * from user where username = '%s' and password = '%s'" % (username, password)
cursor.execute(sql)
data = cursor.fetchone()
if data:
listResult = './result.txt'
# 定义文件头信息,包含文件名和文件大小
header = {
'Feedback': 'Update',
'stat': 'Success',
'fileSize': os.stat(listResult).st_size,
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
fo = open(listResult, 'rb')
while True:
filedata = fo.read(1024)
if not filedata:
break
connection.send(filedata)
fo.close()
#print('send list over...')
# connection.close()
else:
header = {
'Feedback': 'Login',
'stat': 'Fail',
'fileSize': '',
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
elif Command == 'Register':
# 查询数据表数据
username = header['user']
password = header['password']
time = header['time']
sql = "select * from user where username = '%s'" % (username)
cursor.execute(sql)
data = cursor.fetchone()
if data:
# 定义文件头信息,包含文件名和文件大小
header = {
'Feedback': 'Register',
'stat': 'Exist',
'fileSize': '',
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
loginlog = '\n%s try to register at "%s" , Stat: Fail ' % \
(username, time)
with open('Serverlog.txt', 'a', encoding='utf-8') as list:
list.write(loginlog)
else:
sql = "insert into user values ('','%s','%s')"%(username,password)
cursor.execute(sql)
db.commit()
# 定义文件头信息,包含文件名和文件大小
header = {
'Feedback': 'Register',
'stat': 'Success',
'fileSize': '',
'user': username
}
header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('128s', header_hex)
connection.send(fhead)
loginlog = '\n%s try to register at "%s" , Stat: Success ' % \
(username, time)
with open('Serverlog.txt', 'a', encoding='utf-8') as list:
list.write(loginlog)
except socket.timeout:
connection.close()
break
except ConnectionResetError:
connection.close()
break
визуализация
Отображение списка файлов выглядит следующим образом:
Рендеринг загрузки файла выглядит следующим образом:
столкнуться с проблемами
- нет графического интерфейса: использование набора инструментов для программирования Python с графическим интерфейсом (Tkinter).
- Сертификат не будет создан:команда openssl
-
hostname match error:
context.wrap_socket(self.sock, server_hostname='SERVER', server_side=False)
Имя_хоста_сервера в имени_хоста_сервера должно соответствовать CN в server.conf.
Экспериментальный урожай и опыт
Этот эксперимент многому меня научил и дал более глубокое понимание зашифрованной передачи SSL и протокола TCP. Раньше я читал знания о протоколе TCP в книге, но на этот раз я проверил трехстороннее рукопожатие TCP, номер ACK и т. д., перехватив пакеты самостоятельно, что сделало меня более заинтересованным в знаниях о компьютерных сетях. Кроме того, в этом эксперименте я сам разработал протокол, подал заявку на получение SSL-сертификата, реализовал графический интерфейс и т. д. Я никогда раньше не пробовал его. углубился. Проблемы, возникшие в этом эксперименте, в основном связаны с дизайном графического интерфейса и применением сертификата SSL. Так как я мало что знал об этом заранее, было сложно разобраться во время разработки.Позднее, после большого чтения материалов, просмотра блогов и изучения чужих кодов, я, наконец, решил трудности одну за другой и успешно реализовал все функции. Этот эксперимент развил мою способность мыслить и решать проблемы, а также расширил мой собственный опыт, что очень обогащало меня.