1

Here is the server:

import socket
from threading import Thread

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 4444))

def get_data(s, conn): 
    data = conn.recv(1024) 
    print conn, data, "\n" 
    return data; 

def send_data(s, conn, data):
    conn.sendall(data) 
    print "Data sent to clients \n" 

def listen():

    s.listen(5)
    conn, addr = s.accept()
    print addr, " connected \n"
    while True:
        data = get_data(s, conn)
        send_data(s, conn, data)


for i in range(5):
    Thread(target = listen).start()

and the client:

import socket
import time

HOST = '127.0.0.1'
PORT = 4444
NICKNAME = 'NICKNAME_HERE' + ' >'


def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        message = raw_input("> ")
        nickandmessage = NICKNAME + message
        s.send(nickandmessage)
        data = s.recv(1024)
        if NICKNAME not in data:
            print data

connect()

Everything works, multiple clients can connect to the server, but if I write a message in a client, the server gets it but it doesn't seem to make it to the other clients... I think I missed something VERY simple and I think that I'm just too stupid to figure it out.

amsterdamn
  • 33
  • 4

1 Answers1

0

Several problems here, I would advise reading through the answers here as well. I'll show you how to make your solution work below. But this isn't the ideal solution.

Using asyncore or something else as suggested in the answer above is probably a much better solution. There are things that I haven't handled below - like gracefully killing the server threads, filling up a socket buffer on write, etc... It also still has a limit of handling 5 connections at a time.

Server

Firstly, you should handle the closure of the socket. On close the read call will then return an empty string. If you disconnect a client from your server it will loop infinitely on the recv() call.

The socket object you receive as conn from s.accept() is a socket that allows you to talk on the connection you've just accepted from the client. If you want to send the messages to the other clients, you need to call send on their connections.

To achieve this you can store and remove the connection objects from a shared list. You can then iterate through these connections in send_data to send the received message to other connections. You can also avoid sending the message back to the sender.

A server updated to do this is below, you can test this with nc / netcat

nc localhost 4444

instead of using your client python program, because that too has an issue.

#!/usr/bin/python
import socket
from threading import Thread, Lock

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 4444))

# CHANGED:
# threading.Lock() to lock access to the connections list
lock = Lock()
# hold open connections in a list
connections = []

def get_data(s, conn):
    data = conn.recv(1024)
    print conn, data, "\n"
    return data;

def send_data(sender, data):
    with lock:
        count = 0
        for conn in connections:
            if conn == sender:
                # don't echo back to the originator
                continue
            try:
                print(data)
                conn.send(data)
                count += 1
            except socket.error:
                # TODO: add better error handling
                pass

    # CHANGED: show num clients sent to
    print "Data sent to %d clients \n" % count

def listen():
    while True:
        conn, addr = s.accept()

        # CHANGED: add connection to connections list
        with lock:
            connections.append(conn)

        print addr, " connected \n"
        while True:
            data = get_data(s, conn)
            # CHANGED: handle close
            if not data:
                break
            send_data(conn, data)

        # CHANGED: remove connection from connections list and close it
        with lock:
            connections.remove(conn)

        conn.close()

s.listen(5)
for i in range(5):
    Thread(target = listen).start()

Client

If the server sends data asynchronously to your client sockets, your clients won't read the data because they'll be blocking at the raw_input() call waiting for user input.


Update: Based on comments below, using select.select() to wait on stdin will not work on Windows as the select() system call on Win only supports sockets.

from the docs for select.select():

Note File objects on Windows are not acceptable, but sockets are. On Windows, the underlying select() function is provided by the WinSock library, and does not handle file descriptors that don’t originate from WinSock.

Thus another solution that involves threads is probably more appropriate for Windows.


You could solve this by having the socket talk on its own thread and communicate with the main thread via Queues or something. I've opted to use select.select() to do this within a single thread.

select.select() allows you to wait on multiple file-like objects for I/O. It accepts three lists, and returns three lists. The first list argument contains file-like objects you are interested in reading from. The first returned list will contained a subset of these that have data to read. The second list argument/return list behaves similarly for writable files and the third is for error conditions. The fourth argument to select.select() is a timeout, if this timeout is reached and there are no files ready for read/write/error then the call will return with three empty lists.

I've used this to handle user input from stdin and data arriving on the socket.

The insertion of the '>' is slightly broken in this answer though.

import socket
import time
import sys
import select

HOST = '127.0.0.1'
PORT = 4444
NICKNAME = 'NICKNAME_HERE' + ' >'

def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))


    while True:
        sys.stdout.write(">")
        sys.stdout.flush()
        readable, writable, errors = select.select([sys.stdin, s], [], [])
        if sys.stdin in readable:
            message = sys.stdin.readline()
            nickandmessage = NICKNAME + message
            s.send(nickandmessage)

        if s in readable:
            data = s.recv(1024)
            # TODO: check if socket was closed and exit (data == "")
            print(data)
            # CHANGED: this was to remove echos? the server fixes that
            # if NICKNAME not in data:
            #     print data

connect()
Community
  • 1
  • 1
Charith A.
  • 106
  • 1
  • 3
  • The client doesn't work for some reason, I get an error in line 18 when I try to start it with the message: "An operation was attempted on something that is not a socket." Any ideas? – amsterdamn Mar 22 '16 at 16:05
  • what platform are you trying this on? Windows, Linux or OSX? – Charith A. Mar 22 '16 at 16:22
  • My main machine is a linux laptop, and it works on linux, but not on Windows. Could this work on Windows somehow? – amsterdamn Mar 22 '16 at 16:30
  • Apparently the `select.select()` call does not support file-like objects on Windows only sockets. Updated answer to include that. Didn't know that before sorry. Unfortunately don't know how to solve this on Windows short of a thread waiting on user input or using the win32 api (msvcrt.kbhit() looks promising). – Charith A. Mar 22 '16 at 16:39
  • It worked on linux, so I'll accept the answer. Thanks for the help, I'll try to figure it out somehow. – amsterdamn Mar 22 '16 at 16:50