I am trying to build a console chat app on Python socket. I currently running into some issues regarding the server sending message to the client. I managed to successfully send the request from the client to the server and printed it out. However, when I try to sent the confirmation message back to the client, there were always problems that I couldn't quite understand.
Here is my client side:
import socket
import select
import errno
import sys, struct
HEADER_LENGTH = 1024
IP = "127.0.0.1"
PORT = 9669
ProtoHeader = struct.Struct("!HI")
LoginRequest = 1
MessageRequest = 2
ConversationRequest = 3
def format_login_request(username):
username_bytes = username.encode()
proto_block = ProtoHeader.pack(LoginRequest, len(username_bytes)) + username_bytes
return proto_block
def format_send_message(recv_id, message):
message_bytes = message.encode()
recv_id = recv_id.encode()
proto_block = ProtoHeader.pack(MessageRequest, len(message_bytes)+len(recv_id)) + recv_id+message_bytes
return proto_block
def format_con_request(conv_id):
recv_id_bytes = recv_id.encode()
proto_block = ProtoHeader.pack(ConversationRequest, len(recv_id_bytes)) + recv_id_bytes
return proto_block
# Create a socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to a given ip and port
client_socket.connect((IP, PORT))
client_socket.setblocking(False)
my_username = input("Username: ")
print(format_login_request(my_username))
client_socket.sendall(format_login_request(my_username))
username_conf = client_socket.recv(HEADER_LENGTH).decode()
print(username_conf)
if username_conf == "Welcome to the server":
con_id = input("Please enter conversation's id, if don't have one, please enter no ")
if con_id == 'no':
client_socket.send(format_con_request(con_id))
else:
client_socket.send(format_con_request(con_id))
conversation = client_socket.recv(HEADER_LENGTH).decode()
recv_id = input("Please enter receiver's id")
while True:
# Wait for user to input a message
message = input(f'{my_username} > ')
# If message is not empty - send it
if message:
# send_message = send_message(recv_id,message)
client_socket.sendall(format_send_message(recv_id,message))
try:
while True:
message_receiver = client_socket.recv(HEADER_LENGTH).decode()
x = message_receiver.split('|')
print(x)
username = x[0]
message = x[1]
# Print message
print(f'{username} > {message}')
except IOError as e:
if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
print('Reading error: {}'.format(str(e)))
sys.exit()
# We just did not receive anything
continue
except Exception as e:
# Any other exception - something happened, exit
print('Reading error: {}'.format(str(e)))
sys.exit()
Here is the console terminal for client:
Username: user1
b'\x00\x01\x00\x00\x00\x05user1'
Traceback (most recent call last):
File "C:\Users\Duong Dang\Desktop\bai 2.3\client.py", line 57, in <module>
username_conf = client_socket.recv(HEADER_LENGTH).decode()
BlockingIOError: [WinError 10035] A non-blocking socket operation could not be completed immediately
Here is my server side:
import socket
import select
import struct
import sys
import pickle
ProtoHeader = struct.Struct("!HI")
LoginRequest = 1
MessageRequest = 2
ConversationRequest = 3
HEADER_LENGTH = 1024
conversation ={}
users = [
{
'username': 'user1',
'user_id': 1
},
{
'username': 'user2',
'user_id': 2
},
{
'username': 'user3',
'user_id': 3
},
{
'username': 'user4',
'user_id': 4
},
{
'username': 'user5',
'user_id': 5
}
]
def login(username):
for user in users:
if user['username'] == username:
return user
else:
return False
IP = "127.0.0.1"
PORT = 9669
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((IP, PORT))
server_socket.listen()
# List of sockets for select.select()
sockets_list = [server_socket]
# List of connected clients - socket as a key, user header and name as data
clients_socket = {}
sessions = {}
conversation = {
(1,2) : '1.txt',
(3,4) : '2.txt'
}
def getRecvSocket(user_id):
try:
return sessions[user_id]
except:
return None
# def sendErrorMes(socketid, mes):
# package = [9]
# length = len(mes)
# if length > 1019:
# length = 1019
# package += struct.pack("I", length)
# package += mes
# package = pad(package)
print(f'Listening for connections on {IP}:{PORT}...')
def receive_bytes(conn, count):
""" General purpose receiveer:
Receive exactly @count bytes from @conn"""
buf = b''
remaining = count
while remaining > 0:
#Receive part of all of data
tbuf = conn.recv(remaining)
tbuf_len = len(tbuf)
if tbuf_len == 0:
#return 0 if buf is empty and allow the higher-level routine to determine
#if the EOF is at a proper message boundary in which case, you silently close
#the connection. You would normally only raise an exception if you EOF in the
#middle of the message.
raise RuntimeError("end of file")
buf += tbuf
remaining -= tbuf_len
return buf
def receive_proto_block(conn):
"""Receive the next protocol block from @conn. Return a tuple of request_type(interger)
and payload (byte string) """
proto_header = receive_bytes(conn, ProtoHeader.size)
request_type, payload_length = ProtoHeader.unpack(proto_header)
payload = receive_bytes(conn, payload_length)
return request_type, payload
while True:
read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list)
# Iterate over notified sockets
for notified_socket in read_sockets:
# If notified socket is a server socket - new connection, accept it
if notified_socket == server_socket:
client_socket, client_address = server_socket.accept()
sockets_list.append(client_socket)
else:
# Receive message
request_type, payload = receive_proto_block(notified_socket)
print(request_type)
print(payload.decode())
if request_type == LoginRequest:
username = payload.decode()
print(username)
user = login(username)
if user == False:
notified_socket.send("no user found".encode())
else:
sessions[user["user_id"]] = notified_socket
print(sessions)
notified_socket.send(("Welcome to the server").encode())
elif request_type == 2:
recv_package = payload.decode()
recv_id = recv_package[0]
print(message)
# if getRecvSocket(recv_id) == None:
# sendErrorMes(notified_socket, "User is offline")
message = recv_package[1]
for socket in sessions.values():
if socket == notified_socket:
user = sessions[notified_socket]
# fIterate over connected clients and broadcast message
for client_socket in clients_socket:
# if clients[client_socket] == receive_user and client_socket != notified_socket:
# But don't sent it to sender
if client_socket != notified_socket and clients_socket[client_socket] == recv_id:
# Send user and message (both with their headers)
# We are reusing here message header sent by sender, and saved username header send by user when he connected
a = sessions[notified_socket]
b = recv_id
with open(f"{conversation[a,b]}.txt", "w"):
f.write(user + message)
client_socket.send((user + "|" + message).encode())
if message is False:
# print('Closed connection from: {}'.format(user))
# Remove from list for socket.socket()
sockets_list.remove(notified_socket)
# Remove from our list of users
del clients_socket[notified_socket]
continue
elif request_type == 3:
convo_id = payload.decode()
if convo_id in conversation:
with open(conversation[convo_id], 'rb') as file_to_send:
for data in file_to_send:
notified_socket.sendall(data)
print('send successful')
else:
f = open(f"{len(conversation)+1}.txt", "w+")
and here is the console terminal for the server:
Listening for connections on 127.0.0.1:9669...
1
user1
user1
{1: <socket.socket fd=432, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9669), raddr=('127.0.0.1', 52919)>}
Traceback (most recent call last):
File "c:/Users/Duong Dang/Desktop/bai 2.3/server.py", line 137, in <module>
request_type, payload = receive_proto_block(notified_socket)
File "c:/Users/Duong Dang/Desktop/bai 2.3/server.py", line 115, in receive_proto_block
proto_header = receive_bytes(conn, ProtoHeader.size)
File "c:/Users/Duong Dang/Desktop/bai 2.3/server.py", line 97, in receive_bytes
tbuf = conn.recv(remaining)
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
Thanks for the help.