简体   繁体   中英

Python Socket Chat: Client recv() hanging

I have a small problem with a chat program I am trying to build: There are 2 clients and a server, a client sends out a message and the server receives it, and broadcasts it to everyone (usually both clients, but there can be more). The problem is, that when A client sends data to the other client (through the server), the receiving client has to send something himself in order to see the message.

This is my server.py:

import socket
from thread import start_new_thread
import threading


def thread(c,clients, c_lock,buf=1024,):
    c.send("You are connected! \n")
    while True:
        try:
            data = c.recv(buf)
            print data
            if not data:
                break
            broadcast(clients,c_lock,data)
        except socket.error as e:
            exit(0)
    c.close()

def broadcast(clients,c_lock,data):
    with c_lock:
        for c in clients:
            c.sendall("- " + data)

def main():
    host = ''
    port = 10000
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clients = set()
    clients_lock = threading.Lock()
    s.bind((host, port))
    s.listen(5)
    print "%s listening on %s" % (host, str(port))
    while True:
            c, addr = s.accept()
            print "got connection from ", addr
            with clients_lock:
                clients.add(c)
            start_new_thread(thread, (c, clients, clients_lock, ))
    s.close()

if __name__ == "__main__":
    main()

And client.py:

import socket
import time
from sys import exit

def main():
    host = socket.gethostname()
    port = 10000
    name = str(raw_input("Enter your name: "))


    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('192.168.1.12', port))
    except Exception as e:
        print 'Connection not working... %s' % (e)
        exit()

    print s.recv(1024)  # greeting msg
    print "Send q to stop the connection \n"
    out = ''
    while out != 'q':
        out = str(raw_input('->'))
        recv =  s.recv(1024)
        s.send(name + ": " + out)
        time.sleep(0.1)
        print recv
    s.close()
if __name__ == '__main__':
    main()

Thanks for your help :) (And please forgive me for the mess :/)

You have two issues.

recv() is a blocking call.
raw_input() / input() are also a blocking call.

Meaning the client will "hang" on both of these calls ( recv() being an exception if there is data in the pipe, then it won't block).

To get around this, as in your example. Either you can use threading as you've done in your server to have a class/instance that is souly responsible for retrieving data from the socket and one to read user input. Or on Linux you can use select.select or select.epoll to poll the socket if data is in the pipe and only then, call recv() . And for the input you're still better off using select/epoll on sys.stdin or use threading.

But recv() will always be a blocking call in Python (and many other languages) if there is no data available to read. And so will raw_input() / input() .

To use epoll , here's a short example:

from select import epoll, EPOLLIN
import sys

polly = epoll()
polly.register(s.fileno(), EPOLLIN)
polly.register(sys.stdin.fileno(), EPOLLIN)

for fid, eid in polly.poll(1):
    # s is your socket, s.fileno() on linux is the file number that socket has been assigned to.
    # And epoll returns a list of all filenumbers that has data waiting.
    # So we can check if the fid from epoll == the sockets filenumber.
    if fid == s.fileno(): 
        data = s.recv(8192)
    # of if it's sys.stdin's filenumber
    # (stdin is user input, this is where raw_input normally gets its data from)
    elif fid == sys.stdin.fileno():
        user_input = sys.stdin.readline()
        print('User wrote:', user_input)

Or if you want to use threading:

from threading import Thread, enumerate as t_enum
class userInput(Thread):
    def __init__(self, socket, username):
        Thread.__init__(self)
        self.socket = socket
        self.username = username
        self.start()

    def run(self):
        mainThread = None
        for thread in t_enum():
            if thread.name == 'MainThread':
                mainThread = thread
                break

        while mainThread and mainThread.isAlive():
            out = raw_input('->') # str() is redundant, it's type(str) already.
            self.socket.send(self.username + ": " + out)

And a little further down your code:

name = str(raw_input("Enter your name: "))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    s.connect(('192.168.1.12', port))
except Exception as e:
    print 'Connection not working... %s' % (e)
    exit()

userInput(s, name)

Note that this isn't proper thread handling. But it's a minimal example that will show you the general gist of how to go about the problem.

as a last tipe, fcntl is also a good option.

Further programming for the future tips

use print(...) instead of print ...
use 'Connection not working... {}'.format(e) instead of 'Connection not working... %s' % (e)
And overall, try to use Python3 instead of Python2. Python2 should really only be used if you have a old environment filled with legacy stuff (read: debian, old developed applications etc).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM