Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit df8e811

Browse files
Create Chat Room With Python Socket
Create Chat Room With Python Socket
1 parent eac29b8 commit df8e811

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

‎client.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import sys
2+
import socket
3+
import select
4+
import errno
5+
6+
7+
HEADER_LENGTH = 10
8+
9+
IP = "192.168.204.1"
10+
PORT = 5052
11+
my_username = input("Username: ")
12+
13+
# Create a socket
14+
# socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX
15+
# socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets
16+
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
17+
18+
# Connect to a given ip and port
19+
client_socket.connect((IP, PORT))
20+
21+
# Set connection to non-blocking state, so .recv() call won;t block, just return some exception we'll handle
22+
client_socket.setblocking(False)
23+
24+
# Prepare username and header and send them
25+
# We need to encode username to bytes, then count number of bytes and prepare header of fixed size, that we encode to bytes as well
26+
username = my_username.encode('utf-8')
27+
username_header = f"{len(username):<{HEADER_LENGTH}}".encode('utf-8')
28+
client_socket.send(username_header + username)
29+
30+
while True:
31+
32+
# Wait for user to input a message
33+
message = input(f'{my_username} > ')
34+
35+
# If message is not empty - send it
36+
if message:
37+
38+
# Encode message to bytes, prepare header and convert to bytes, like for username above, then send
39+
message = message.encode('utf-8')
40+
message_header = f"{len(message):<{HEADER_LENGTH}}".encode('utf-8')
41+
client_socket.send(message_header + message)
42+
43+
try:
44+
# Now we want to loop over received messages (there might be more than one) and print them
45+
while True:
46+
47+
# Receive our "header" containing username length, it's size is defined and constant
48+
username_header = client_socket.recv(HEADER_LENGTH)
49+
50+
# If we received no data, server gracefully closed a connection, for example using socket.close() or socket.shutdown(socket.SHUT_RDWR)
51+
if not len(username_header):
52+
print('Connection closed by the server')
53+
sys.exit()
54+
55+
# Convert header to int value
56+
username_length = int(username_header.decode('utf-8').strip())
57+
58+
# Receive and decode username
59+
username = client_socket.recv(username_length).decode('utf-8')
60+
61+
# Now do the same for message (as we received username, we received whole message, there's no need to check if it has any length)
62+
message_header = client_socket.recv(HEADER_LENGTH)
63+
message_length = int(message_header.decode('utf-8').strip())
64+
message = client_socket.recv(message_length).decode('utf-8')
65+
66+
# Print message
67+
print(f'{username} > {message}')
68+
69+
except IOError as e:
70+
# This is normal on non blocking connections - when there are no incoming data error is going to be raised
71+
# Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
72+
# We are going to check for both - if one of them - that's expected, means no incoming data, continue as normal
73+
# If we got different error code - something happened
74+
if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
75+
print('Reading error: {}'.format(str(e)))
76+
sys.exit()
77+
78+
# We just did not receive anything
79+
continue
80+
81+
except Exception as e:
82+
# Any other exception - something happened, exit
83+
print('Reading error: '.format(str(e)))
84+
sys.exit()

‎server.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import socket
2+
import select
3+
4+
HEADER_LENGTH = 10
5+
6+
IP = "192.168.204.1"
7+
PORT = 5052
8+
9+
# Create a socket
10+
# socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX
11+
# socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets
12+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
13+
14+
# SO_ - socket option
15+
# SOL_ - socket option level
16+
# Sets REUSEADDR (as a socket option) to 1 on socket
17+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
18+
19+
# Bind, so server informs operating system that it's going to use given IP and port
20+
# For a server using 0.0.0.0 means to listen on all available interfaces, useful to connect locally to 127.0.0.1 and remotely to LAN interface IP
21+
server_socket.bind((IP, PORT))
22+
23+
# This makes server listen to new connections
24+
server_socket.listen()
25+
26+
# List of sockets for select.select()
27+
sockets_list = [server_socket]
28+
29+
# List of connected clients - socket as a key, user header and name as data
30+
clients = {}
31+
32+
print(f'Listening for connections on {IP}:{PORT}...')
33+
34+
# Handles message receiving
35+
def receive_message(client_socket):
36+
37+
try:
38+
39+
# Receive our "header" containing message length, it's size is defined and constant
40+
message_header = client_socket.recv(HEADER_LENGTH)
41+
42+
# If we received no data, client gracefully closed a connection, for example using socket.close() or socket.shutdown(socket.SHUT_RDWR)
43+
if not len(message_header):
44+
return False
45+
46+
# Convert header to int value
47+
message_length = int(message_header.decode('utf-8').strip())
48+
49+
# Return an object of message header and message data
50+
return {'header': message_header, 'data': client_socket.recv(message_length)}
51+
52+
except:
53+
54+
# If we are here, client closed connection violently, for example by pressing ctrl+c on his script
55+
# or just lost his connection
56+
# socket.close() also invokes socket.shutdown(socket.SHUT_RDWR) what sends information about closing the socket (shutdown read/write)
57+
# and that's also a cause when we receive an empty message
58+
return False
59+
60+
while True:
61+
62+
# Calls Unix select() system call or Windows select() WinSock call with three parameters:
63+
# - rlist - sockets to be monitored for incoming data
64+
# - wlist - sockets for data to be send to (checks if for example buffers are not full and socket is ready to send some data)
65+
# - xlist - sockets to be monitored for exceptions (we want to monitor all sockets for errors, so we can use rlist)
66+
# Returns lists:
67+
# - reading - sockets we received some data on (that way we don't have to check sockets manually)
68+
# - writing - sockets ready for data to be send thru them
69+
# - errors - sockets with some exceptions
70+
# This is a blocking call, code execution will "wait" here and "get" notified in case any action should be taken
71+
read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list)
72+
73+
74+
# Iterate over notified sockets
75+
for notified_socket in read_sockets:
76+
77+
# If notified socket is a server socket - new connection, accept it
78+
if notified_socket == server_socket:
79+
80+
# Accept new connection
81+
# That gives us new socket - client socket, connected to this given client only, it's unique for that client
82+
# The other returned object is ip/port set
83+
client_socket, client_address = server_socket.accept()
84+
85+
# Client should send his name right away, receive it
86+
user = receive_message(client_socket)
87+
88+
# If False - client disconnected before he sent his name
89+
if user is False:
90+
continue
91+
92+
# Add accepted socket to select.select() list
93+
sockets_list.append(client_socket)
94+
95+
# Also save username and username header
96+
clients[client_socket] = user
97+
98+
print('Accepted new connection from {}:{}, username: {}'.format(*client_address, user['data'].decode('utf-8')))
99+
100+
# Else existing socket is sending a message
101+
else:
102+
103+
# Receive message
104+
message = receive_message(notified_socket)
105+
106+
# If False, client disconnected, cleanup
107+
if message is False:
108+
print('Closed connection from: {}'.format(clients[notified_socket]['data'].decode('utf-8')))
109+
110+
# Remove from list for socket.socket()
111+
sockets_list.remove(notified_socket)
112+
113+
# Remove from our list of users
114+
del clients[notified_socket]
115+
116+
continue
117+
118+
# Get user by notified socket, so we will know who sent the message
119+
user = clients[notified_socket]
120+
121+
print(f'Received message from {user["data"].decode("utf-8")}: {message["data"].decode("utf-8")}')
122+
123+
# Iterate over connected clients and broadcast message
124+
for client_socket in clients:
125+
126+
# But don't sent it to sender
127+
if client_socket != notified_socket:
128+
129+
# Send user and message (both with their headers)
130+
# We are reusing here message header sent by sender, and saved username header send by user when he connected
131+
client_socket.send(user['header'] + user['data'] + message['header'] + message['data'])
132+
133+
# It's not really necessary to have this, but will handle some socket exceptions just in case
134+
for notified_socket in exception_sockets:
135+
136+
# Remove from list for socket.socket()
137+
sockets_list.remove(notified_socket)
138+
139+
# Remove from our list of users
140+
del clients[notified_socket]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /