i'm trying build command line chat room server handling connections , repeating input 1 client other clients. server able take in input multiple clients, can send information clients individually. think problem each connection being handled on individual thread. how allow threads communicate each other or able send data each thread?
server code:
namespace consoleapplication { class tcphelper { private static object _lock = new object(); private static list<task> _connections = new list<task>(); private static tcplistener listener { get; set; } private static bool accept { get; set; } = false; private static task startlistener() { return task.run(async () => { ipaddress address = ipaddress.parse("127.0.0.1"); int port = 5678; listener = new tcplistener(address, port); listener.start(); console.writeline($"server started. listening tcp clients @ 127.0.0.1:{port}"); while (true) { var tcpclient = await listener.accepttcpclientasync(); console.writeline("client has connected"); var task = starthandleconnectionasync(tcpclient); if (task.isfaulted) task.wait(); } }); } // register , handle connection private static async task starthandleconnectionasync(tcpclient tcpclient) { // start new connection task var connectiontask = handleconnectionasync(tcpclient); // add list of pending task lock (_lock) _connections.add(connectiontask); // catch errors of handleconnectionasync try { await connectiontask; } catch (exception ex) { // log error console.writeline(ex.tostring()); } { // remove pending task lock (_lock) _connections.remove(connectiontask); } } private static async task handleconnectionasync(tcpclient client) { await task.yield(); { using (var networkstream = client.getstream()) { if (client != null) { console.writeline("client connected. waiting data."); streamreader streamreader = new streamreader(networkstream); streamwriter streamwriter = new streamwriter(networkstream); string clientmessage = ""; string servermessage = ""; while (clientmessage != null && clientmessage != "quit") { clientmessage = await streamreader.readlineasync(); console.writeline(clientmessage); servermessage = clientmessage; streamwriter.writeline(servermessage); streamwriter.flush(); } console.writeline("closing connection."); networkstream.dispose(); } } } } public static void main(string[] args) { // start server console.writeline("hit ctrl-c close chat server"); tcphelper.startlistener().wait(); } } }
client code:
namespace client2 { public class program { private static void clientconnect() { tcpclient socketforserver = new tcpclient(); bool status = true; string username; console.write("input username: "); username = console.readline(); try { ipaddress address = ipaddress.parse("127.0.0.1"); socketforserver.connectasync(address, 5678); console.writeline("connected server"); } catch { console.writeline("failed connect server{0}:999", "localhost"); return; } networkstream networkstream = socketforserver.getstream(); streamreader streamreader = new streamreader(networkstream); streamwriter streamwriter = new streamwriter(networkstream); try { string clientmessage = ""; string servermessage = ""; while (status) { console.write(username + ": "); clientmessage = console.readline(); if ((clientmessage == "quit") || (clientmessage == "quit")) { status = false; streamwriter.writeline("quit"); streamwriter.writeline(username + " has left conversation"); streamwriter.flush(); } if ((clientmessage != "quit") && (clientmessage != "quit")) { streamwriter.writeline(username + ": " + clientmessage); streamwriter.flush(); servermessage = streamreader.readline(); console.writeline("server:" + servermessage); } } } catch { console.writeline("exception reading server"); } streamreader.dispose(); networkstream.dispose(); streamwriter.dispose(); } public static void main(string[] args) { clientconnect(); } } }
the main thing wrong in code make no attempt send data received 1 client other connected clients. have _connections
list in server, thing stored in list task
objects connections, , don't those.
instead, should maintain list of connections themselves, when received message 1 client, can retransmit message other clients.
at minimum, should list<tcpclient>
, because using streamreader
, streamwriter
, you'll want initialize , store objects in list well. in addition, should include client identifier. 1 obvious choice name of client (i.e. user enters name), example doesn't provide mechanism in chat protocol transmit identification part of connection initialization, in example (below) use simple integer value.
there other irregularities in code posted, such as:
- starting task in brand new thread, execute few statements point of initiating asynchronous operation. in example, omit
task.run()
part of code, it's not needed. - checking connection-specific task when it's returned
isfaulted
. since it's unlikely i/o have occurred timetask
object returned, logic has little use. callwait()
throw exception, propagate main thread'swait()
call, terminating server. don't terminate server in event of other error, it's not clear why you'd want here. - there's spurious call
task.yield()
. have no idea you're trying accomplish there, whatever is, statement isn't useful. removed it. - in client code, attempt receive data server when you've sent data. wrong; want clients responsive , receive data it's sent them. in version, included simple little anonymous method called start separate message-receiving loop execute asynchronously , concurrently main user input loop.
- also in client code, sending "…has left…" message after "quit" message cause server close connection. means server never receive "…has left…" message. reversed order of messages "quit" last thing client ever sends.
my version looks this:
server:
class tcphelper { class clientdata : idisposable { private static int _nextid; public int id { get; private set; } public tcpclient client { get; private set; } public textreader reader { get; private set; } public textwriter writer { get; private set; } public clientdata(tcpclient client) { id = _nextid++; client = client; networkstream stream = client.getstream(); reader = new streamreader(stream); writer = new streamwriter(stream); } public void dispose() { writer.close(); reader.close(); client.close(); } } private static readonly object _lock = new object(); private static readonly list<clientdata> _connections = new list<clientdata>(); private static tcplistener listener { get; set; } private static bool accept { get; set; } public static async task startlistener() { ipaddress address = ipaddress.any; int port = 5678; listener = new tcplistener(address, port); listener.start(); console.writeline("server started. listening tcp clients on port {0}", port); while (true) { var tcpclient = await listener.accepttcpclientasync(); console.writeline("client has connected"); var task = starthandleconnectionasync(tcpclient); if (task.isfaulted) task.wait(); } } // register , handle connection private static async task starthandleconnectionasync(tcpclient tcpclient) { clientdata clientdata = new clientdata(tcpclient); lock (_lock) _connections.add(clientdata); // catch errors of handleconnectionasync try { await handleconnectionasync(clientdata); } catch (exception ex) { // log error console.writeline(ex.tostring()); } { lock (_lock) _connections.remove(clientdata); clientdata.dispose(); } } private static async task handleconnectionasync(clientdata clientdata) { console.writeline("client connected. waiting data."); string clientmessage; while ((clientmessage = await clientdata.reader.readlineasync()) != null && clientmessage != "quit") { string message = "from " + clientdata.id + ": " + clientmessage; console.writeline(message); lock (_lock) { // locking entire operation ensures a) none of client objects // disposed before can write them, , b) of chat messages // received in same order clients. foreach (clientdata recipient in _connections.where(r => r.id != clientdata.id)) { recipient.writer.writeline(message); recipient.writer.flush(); } } } console.writeline("closing connection."); } }
client:
class program { private const int _kport = 5678; private static async task clientconnect() { ipaddress address = ipaddress.loopback; tcpclient socketforserver = new tcpclient(); string username; console.write("input username: "); username = console.readline(); try { await socketforserver.connectasync(address, _kport); console.writeline("connected server"); } catch (exception e) { console.writeline("failed connect server {0}:{1}", address, _kport); return; } using (networkstream networkstream = socketforserver.getstream()) { var readtask = ((func<task>)(async () => { using (streamreader reader = new streamreader(networkstream)) { string receivedtext; while ((receivedtext = await reader.readlineasync()) != null) { console.writeline("server:" + receivedtext); } } }))(); using (streamwriter streamwriter = new streamwriter(networkstream)) { try { while (true) { console.write(username + ": "); string clientmessage = console.readline(); if ((clientmessage == "quit") || (clientmessage == "quit")) { streamwriter.writeline(username + " has left conversation"); streamwriter.writeline("quit"); streamwriter.flush(); break; } else { streamwriter.writeline(username + ": " + clientmessage); streamwriter.flush(); } } await readtask; } catch (exception e) { console.writeline("exception writing server: " + e); throw; } } } } public static void main(string[] args) { clientconnect().wait(); } }
there still lot you'll need work on. you'll want implement proper initialization of chat user names on server side. @ least, real-world code you'd want more error checking, , make sure client id generated reliably (if want positive id values, can't have more 2^31-1 connections before rolls on 0
).
i made other minor changes weren't strictly necessary, such using ipaddress.any
, ipaddress.loopback
values instead of parsing strings, , simplifying , cleaning code here , there. also, i'm not using c# 6 compiler @ moment, changed code using c# 6 features compile using c# 5 instead.
to full-blown chat server, still have work cut out you. hope above gets on right track.
Comments
Post a Comment