This commit is contained in:
Ingy döt Net 2013-04-10 16:57:12 -07:00
parent 518da4a923
commit 764da6cbbb
6144 changed files with 83610 additions and 11 deletions

View file

@ -0,0 +1 @@
Write a server for a minimal text based chat. People should be able to connect via telnet, sign on with a nickname, and type messages which will then be seen by all other connected users. Arrivals and departures of chat members should generate appropriate notification messages.

View file

@ -0,0 +1,2 @@
---
note: Networking and Web Interaction

View file

@ -0,0 +1,85 @@
with Ada.Containers.Vectors;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Sockets; use Sockets;
procedure Chat_Server is
package Client_Vectors is new Ada.Containers.Vectors
(Element_Type => Socket_FD, Index_Type => Positive);
All_Clients : Client_Vectors.Vector;
procedure Write (S : String) is
procedure Output (Position : Client_Vectors.Cursor) is
Sock : Socket_FD := Client_Vectors.Element (Position);
begin
Put_Line (Sock, S);
end Output;
begin
All_Clients.Iterate (Output'Access);
end Write;
task type Client_Task is
entry Start (FD : Socket_FD);
end Client_Task;
task body Client_Task is
Sock : Socket_FD;
Sock_ID : Positive;
Name : Unbounded_String;
begin
select
accept Start (FD : Socket_FD) do
Sock := FD;
end Start;
or
terminate;
end select;
while Name = Null_Unbounded_String loop
Put (Sock, "Enter Name:");
Name := To_Unbounded_String (Get_Line (Sock));
end loop;
Write (To_String (Name) & " joined.");
All_Clients.Append (Sock);
Sock_ID := All_Clients.Find_Index (Sock);
loop
declare
Input : String := Get_Line (Sock);
begin
Write (To_String (Name) & ": " & Input);
end;
end loop;
exception
when Connection_Closed =>
Put_Line ("Connection closed");
Shutdown (Sock, Both);
All_Clients.Delete (Sock_ID);
Write (To_String (Name) & " left.");
end Client_Task;
Accepting_Socket : Socket_FD;
Incoming_Socket : Socket_FD;
type Client_Access is access Client_Task;
Dummy : Client_Access;
begin
if Argument_Count /= 1 then
Raise_Exception (Constraint_Error'Identity,
"Usage: " & Command_Name & " port");
end if;
Socket (Accepting_Socket, PF_INET, SOCK_STREAM);
Setsockopt (Accepting_Socket, SOL_SOCKET, SO_REUSEADDR, 1);
Bind (Accepting_Socket, Positive'Value (Argument (1)));
Listen (Accepting_Socket);
loop
Put_Line ("Waiting for new connection");
Accept_Socket (Accepting_Socket, Incoming_Socket);
Put_Line ("New connection acknowledged");
Dummy := new Client_Task;
Dummy.Start (Incoming_Socket);
end loop;
end Chat_Server;

View file

@ -0,0 +1,248 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/ip.h>
int tsocket;
struct sockaddr_in tsockinfo;
fd_set status, current;
void ClientText(int handle, char *buf, int buf_len);
struct client
{
char buffer[4096];
int pos;
char name[32];
} *connections[FD_SETSIZE];
void AddConnection(int handle)
{
connections[handle] = malloc(sizeof(struct client));
connections[handle]->buffer[0] = '\0';
connections[handle]->pos = 0;
connections[handle]->name[0] = '\0';
}
void CloseConnection(int handle)
{
char buf[512];
int j;
FD_CLR(handle, &status);
if (connections[handle]->name[0])
{
sprintf(buf, "* Disconnected: %s\r\n", connections[handle]->name);
for (j = 0; j < FD_SETSIZE; j++)
{
if (handle != j && j != tsocket && FD_ISSET(j, &status))
{
if (write(j, buf, strlen(buf)) < 0)
{
CloseConnection(j);
}
}
}
} else
{
printf ("-- Connection %d disconnected\n", handle);
}
if (connections[handle])
{
free(connections[handle]);
}
close(handle);
}
void strip(char *buf)
{
char *x;
x = strchr(buf, '\n');
if (x) { *x='\0'; }
x = strchr(buf, '\r');
if (x) { *x='\0'; }
}
int RelayText(int handle)
{
char *begin, *end;
int ret = 0;
begin = connections[handle]->buffer;
if (connections[handle]->pos == 4000)
{
if (begin[3999] != '\n')
begin[4000] = '\0';
else {
begin[4000] = '\n';
begin[4001] = '\0';
}
} else {
begin[connections[handle]->pos] = '\0';
}
end = strchr(begin, '\n');
while (end != NULL)
{
char output[8000];
output[0] = '\0';
if (!connections[handle]->name[0])
{
strncpy(connections[handle]->name, begin, 31);
connections[handle]->name[31] = '\0';
strip(connections[handle]->name);
sprintf(output, "* Connected: %s\r\n", connections[handle]->name);
ret = 1;
} else
{
sprintf(output, "%s: %.*s\r\n", connections[handle]->name,
end-begin, begin);
ret = 1;
}
if (output[0])
{
int j;
for (j = 0; j < FD_SETSIZE; j++)
{
if (handle != j && j != tsocket && FD_ISSET(j, &status))
{
if (write(j, output, strlen(output)) < 0)
{
CloseConnection(j);
}
}
}
}
begin = end+1;
end = strchr(begin, '\n');
}
strcpy(connections[handle]->buffer, begin);
connections[handle]->pos -= begin - connections[handle]->buffer;
return ret;
}
void ClientText(int handle, char *buf, int buf_len)
{
int i, j;
if (!connections[handle])
return;
j = connections[handle]->pos;
for (i = 0; i < buf_len; ++i, ++j)
{
connections[handle]->buffer[j] = buf[i];
if (j == 4000)
{
while (RelayText(handle));
j = connections[handle]->pos;
}
}
connections[handle]->pos = j;
while (RelayText(handle));
}
int ChatLoop()
{
int i, j;
FD_ZERO(&status);
FD_SET(tsocket, &status);
FD_SET(0, &status);
while(1)
{
current = status;
if (select(FD_SETSIZE, &current, NULL, NULL, NULL)==-1)
{
perror("Select");
return 0;
}
for (i = 0; i < FD_SETSIZE; ++i)
{
if (FD_ISSET(i, &current))
{
if (i == tsocket)
{
struct sockaddr_in cliinfo;
socklen_t addrlen = sizeof(cliinfo);
int handle;
handle = accept(tsocket, &cliinfo, &addrlen);
if (handle == -1)
{
perror ("Couldn't accept connection");
} else if (handle > FD_SETSIZE)
{
printf ("Unable to accept new connection.\n");
close(handle);
}
else
{
if (write(handle, "Enter name: ", 12) >= 0)
{
printf("-- New connection %d from %s:%hu\n",
handle,
inet_ntoa (cliinfo.sin_addr),
ntohs(cliinfo.sin_port));
FD_SET(handle, &status);
AddConnection(handle);
}
}
} /* Read data, relay to others. */
else
{
char buf[512];
int b;
b = read(i, buf, 500);
if (b <= 0)
{
CloseConnection(i);
}
else
{
ClientText(i, buf, b);
}
}
}
}
} /* While 1 */
}
int main (int argc, char*argv[])
{
tsocket = socket(PF_INET, SOCK_STREAM, 0);
tsockinfo.sin_family = AF_INET;
tsockinfo.sin_port = htons(7070);
if (argc > 1)
{
tsockinfo.sin_port = htons(atoi(argv[1]));
}
tsockinfo.sin_addr.s_addr = htonl(INADDR_ANY);
printf ("Socket %d on port %hu\n", tsocket, ntohs(tsockinfo.sin_port));
if (bind(tsocket, &tsockinfo, sizeof(tsockinfo)) == -1)
{
perror("Couldn't bind socket");
return -1;
}
if (listen(tsocket, 10) == -1)
{
perror("Couldn't listen to port");
}
ChatLoop();
return 0;
}

View file

@ -0,0 +1,107 @@
net = require("net")
sys = require("sys")
EventEmitter = require("events").EventEmitter
isNicknameLegal = (nickname) ->
return false unless nickname.replace(/[A-Za-z0-9]*/, "") is ""
for used_nick of @chatters
return false if used_nick is nickname
true
class ChatServer
constructor: ->
@chatters = {}
@server = net.createServer @handleConnection
@server.listen 1212, "localhost"
handleConnection: (connection) =>
console.log "Incoming connection from " + connection.remoteAddress
connection.setEncoding "utf8"
chatter = new Chatter(connection, this)
chatter.on "chat", @handleChat
chatter.on "join", @handleJoin
chatter.on "leave", @handleLeave
handleChat: (chatter, message) =>
@sendToEveryChatterExcept chatter, chatter.nickname + ": " + message
handleJoin: (chatter) =>
console.log chatter.nickname + " has joined the chat."
@sendToEveryChatter chatter.nickname + " has joined the chat."
@addChatter chatter
handleLeave: (chatter) =>
console.log chatter.nickname + " has left the chat."
@removeChatter chatter
@sendToEveryChatter chatter.nickname + " has left the chat."
addChatter: (chatter) =>
@chatters[chatter.nickname] = chatter
removeChatter: (chatter) =>
delete @chatters[chatter.nickname]
sendToEveryChatter: (data) =>
for nickname of @chatters
@chatters[nickname].send data
sendToEveryChatterExcept: (chatter, data) =>
for nickname of @chatters
@chatters[nickname].send data unless nickname is chatter.nickname
class Chatter extends EventEmitter
constructor: (socket, server) ->
EventEmitter.call this
@socket = socket
@server = server
@nickname = ""
@lineBuffer = new SocketLineBuffer(socket)
@lineBuffer.on "line", @handleNickname
@socket.on "close", @handleDisconnect
@send "Welcome! What is your nickname?"
handleNickname: (nickname) =>
if isNicknameLegal(nickname)
@nickname = nickname
@lineBuffer.removeAllListeners "line"
@lineBuffer.on "line", @handleChat
@send "Welcome to the chat, " + nickname + "!"
@emit "join", this
else
@send "Sorry, but that nickname is not legal or is already in use!"
@send "What is your nickname?"
handleChat: (line) =>
@emit "chat", this, line
handleDisconnect: =>
@emit "leave", this
send: (data) =>
@socket.write data + "\r\n"
class SocketLineBuffer extends EventEmitter
constructor: (socket) ->
EventEmitter.call this
@socket = socket
@buffer = ""
@socket.on "data", @handleData
handleData: (data) =>
console.log "Handling data", data
i = 0
while i < data.length
char = data.charAt(i)
@buffer += char
if char is "\n"
@buffer = @buffer.replace("\r\n", "")
@buffer = @buffer.replace("\n", "")
@emit "line", @buffer
console.log "incoming line: #{@buffer}"
@buffer = ""
i++
server = new ChatServer()

View file

@ -0,0 +1,63 @@
-module(chat).
-export([start/0, start/1]).
-record(client, {name=none, socket=none}).
start() -> start(8080).
start(Port) ->
register(server, spawn(fun() -> server() end)),
{ok, LSocket} = gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]),
accept(LSocket).
% main loop for message dispatcher
server() -> server([]).
server(Clients) ->
receive
{join, Client=#client{name = Name, socket = Socket}} ->
self() ! {say, Socket, "has joined." ++ [10, 13]},
server(Clients ++ [Client]);
{leave, Socket} ->
{value, #client{name = Name}, List} = lists:keytake(Socket, 3, Clients),
self() ! {say, none, Message = "has left."},
server(List);
{say, Socket, Data} ->
{value, #client{name = From}, List} = lists:keytake(Socket, 3, Clients),
Message = From ++ " : " ++ Data,
lists:map(fun(#client{socket = S}) ->
gen_tcp:send(S, Message)
end, List)
end,
server(Clients).
% accepts connections then spawns the client handler
accept(LSocket) ->
{ok, Socket} = gen_tcp:accept(LSocket),
spawn(fun() -> connecting(Socket) end),
accept(LSocket).
% when client is first connect send prompt for user name
connecting(Socket) ->
gen_tcp:send(Socket, "What is your name? "),
case listen(Socket) of
{ok, N} ->
Name = binary_to_list(N),
server ! {join, #client{name = lists:sublist(Name, 1, length(Name) - 2), socket = Socket} },
client(Socket);
_ -> ok
end.
% main client loop that listens for data
client(Socket) ->
case listen(Socket) of
{ok, Data} ->
server ! {say, Socket, binary_to_list(Data)},
client(Socket);
_ -> server ! {leave, Socket}
end.
% utility function that listens for data on a socket
listen(Socket) ->
case gen_tcp:recv(Socket, 0) of
Response -> Response
end.

View file

@ -0,0 +1,148 @@
package main
import (
"os"
"fmt"
"net"
"flag"
"bufio"
"bytes"
)
// Quick and dirty error handling.
func error_(err error, r int) {
fmt.Printf("Error: %v\n", err)
if r >= 0 {
os.Exit(r)
}
}
// A type for storing the connections.
type clientMap map[string]net.Conn
// A method that makes clientMap compatible with io.Writer, allowing it to be
// used with fmt.Fprintf().
func (cm clientMap) Write(buf []byte) (n int, err error) {
for _, c := range cm {
// Write to each client in a seperate goroutine.
go c.Write(buf)
}
n = len(buf)
return
}
// Check if a name exists; if it doesn't, add it.
func (cm clientMap) Add(name string, c net.Conn) bool {
for k := range cm {
if name == k {
return false
}
}
cm[name] = c
return true
}
// A clientMap variable.
var clients clientMap
func init() {
// Initialize the map.
clients = make(clientMap)
}
func client(c net.Conn) {
// Close the connection when this function returns.
defer c.Close()
br := bufio.NewReader(c)
fmt.Fprintf(c, "Please enter your name: ")
buf, err := br.ReadBytes('\n')
if err != nil {
error_(err, -1)
return
}
name := string(bytes.Trim(buf, " \t\n\r\x00"))
if name == "" {
fmt.Fprintf(c, "!!! %v is invalid !!!\n", name)
}
// Try to add the connection to the map.
if !clients.Add(name, c) {
fmt.Fprintf(c, "!!! %v is not available !!!\n", name)
return
}
// Send a message telling the clients who connected.
fmt.Fprintf(clients, "+++ %v connected +++\n", name)
// Send a disconnected message when the function returns.
defer fmt.Fprintf(clients, "--- %v disconnected ---\n", name)
// Remove the client from the list.
defer delete(clients, name)
for {
buf, err = br.ReadBytes('\n')
if err != nil {
break
}
buf = bytes.Trim(buf, " \t\n\r\x00")
// Ignore empty messages.
if len(buf) == 0 {
continue
}
switch {
// Support for '/me' type messages.
case string(buf[0:3]) == "/me":
buf = append([]byte(name), buf[3:]...)
default:
// Prepend the user-name and '> '.
buf = append([]byte(name+"> "), buf...)
}
// Send the message to all the clients.
fmt.Fprintf(clients, "%v\n", string(buf))
}
}
func main() {
// Flags. Use -help for usage info.
var (
port int
help bool
)
flag.IntVar(&port, "port", 23, "Port to listen on")
flag.BoolVar(&help, "help", false, "Display this")
flag.Parse()
if help {
flag.Usage()
return
}
// Initialize a new listener.
lis, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
error_(err, 1)
}
// Begin the main loop.
for {
c, err := lis.Accept()
if err != nil {
error_(err, -1)
continue
}
// Launch a new goroutine to handle the connection.
go client(c)
}
}

View file

@ -0,0 +1,152 @@
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer implements Runnable
{
private int port = 0;
private List<Client> clients = new ArrayList<Client>();
public ChatServer(int port)
{ this.port = port; }
public void run()
{
try
{
ServerSocket ss = new ServerSocket(port);
while (true)
{
Socket s = ss.accept();
new Thread(new Client(s)).start();
}
}
catch (Exception e)
{ e.printStackTrace(); }
}
private synchronized boolean registerClient(Client client)
{
for (Client otherClient : clients)
if (otherClient.clientName.equalsIgnoreCase(client.clientName))
return false;
clients.add(client);
return true;
}
private void deregisterClient(Client client)
{
boolean wasRegistered = false;
synchronized (this)
{ wasRegistered = clients.remove(client); }
if (wasRegistered)
broadcast(client, "--- " + client.clientName + " left ---");
}
private synchronized String getOnlineListCSV()
{
StringBuilder sb = new StringBuilder();
sb.append(clients.size()).append(" user(s) online: ");
for (int i = 0; i < clients.size(); i++)
sb.append((i > 0) ? ", " : "").append(clients.get(i).clientName);
return sb.toString();
}
private void broadcast(Client fromClient, String msg)
{
// Copy client list (don't want to hold lock while doing IO)
List<Client> clients = null;
synchronized (this)
{ clients = new ArrayList<Client>(this.clients); }
for (Client client : clients)
{
if (client.equals(fromClient))
continue;
try
{ client.write(msg + "\r\n"); }
catch (Exception e)
{ }
}
}
public class Client implements Runnable
{
private Socket socket = null;
private Writer output = null;
private String clientName = null;
public Client(Socket socket)
{
this.socket = socket;
}
public void run()
{
try
{
socket.setSendBufferSize(16384);
socket.setTcpNoDelay(true);
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output = new OutputStreamWriter(socket.getOutputStream());
write("Please enter your name: ");
String line = null;
while ((line = input.readLine()) != null)
{
if (clientName == null)
{
line = line.trim();
if (line.isEmpty())
{
write("A name is required. Please enter your name: ");
continue;
}
clientName = line;
if (!registerClient(this))
{
clientName = null;
write("Name already registered. Please enter your name: ");
continue;
}
write(getOnlineListCSV() + "\r\n");
broadcast(this, "+++ " + clientName + " arrived +++");
continue;
}
if (line.equalsIgnoreCase("/quit"))
return;
broadcast(this, clientName + "> " + line);
}
}
catch (Exception e)
{ }
finally
{
deregisterClient(this);
output = null;
try
{ socket.close(); }
catch (Exception e)
{ }
socket = null;
}
}
public void write(String msg) throws IOException
{
output.write(msg);
output.flush();
}
public boolean equals(Client client)
{
return (client != null) && (client instanceof Client) && (clientName != null) && (client.clientName != null) && clientName.equals(client.clientName);
}
}
public static void main(String[] args)
{
int port = 4004;
if (args.length > 0)
port = Integer.parseInt(args[0]);
new ChatServer(port).run();
}
}

View file

@ -0,0 +1,158 @@
var net = require("net");
var sys = require("sys");
var EventEmitter = require("events").EventEmitter;
/*******************************************************************************
* ChatServer
*
* Manages connections, users, and chat messages.
******************************************************************************/
function ChatServer() {
this.chatters = {};
this.server = net.createServer(this.handleConnection.bind(this));
this.server.listen(1212, "localhost");
}
ChatServer.prototype.isNicknameLegal = function(nickname) {
// A nickname may contain letters or numbers only,
// and may only be used once.
if(nickname.replace(/[A-Za-z0-9]*/, '') != "") {
return false
}
for(used_nick in this.chatters) {
if(used_nick == nickname) {
return false;
}
}
return true;
};
ChatServer.prototype.handleConnection = function(connection) {
console.log("Incoming connection from " + connection.remoteAddress);
connection.setEncoding("utf8");
var chatter = new Chatter(connection, this);
chatter.on("chat", this.handleChat.bind(this));
chatter.on("join", this.handleJoin.bind(this));
chatter.on("leave", this.handleLeave.bind(this));
};
ChatServer.prototype.handleChat = function(chatter, message) {
this.sendToEveryChatterExcept(chatter, chatter.nickname + ": " + message);
};
ChatServer.prototype.handleJoin = function(chatter) {
console.log(chatter.nickname + " has joined the chat.");
this.sendToEveryChatter(chatter.nickname + " has joined the chat.");
this.addChatter(chatter);
};
ChatServer.prototype.handleLeave = function(chatter) {
console.log(chatter.nickname + " has left the chat.");
this.removeChatter(chatter);
this.sendToEveryChatter(chatter.nickname + " has left the chat.");
};
ChatServer.prototype.addChatter = function(chatter) {
this.chatters[chatter.nickname] = chatter;
};
ChatServer.prototype.removeChatter = function(chatter) {
delete this.chatters[chatter.nickname];
};
ChatServer.prototype.sendToEveryChatter = function(data) {
for(nickname in this.chatters) {
this.chatters[nickname].send(data);
}
};
ChatServer.prototype.sendToEveryChatterExcept = function(chatter, data) {
for(nickname in this.chatters) {
if(nickname != chatter.nickname) {
this.chatters[nickname].send(data);
}
}
};
/*******************************************************************************
* Chatter
*
* Represents a single user/connection in the chat server.
******************************************************************************/
function Chatter(socket, server) {
EventEmitter.call(this);
this.socket = socket;
this.server = server;
this.nickname = "";
this.lineBuffer = new SocketLineBuffer(socket);
this.lineBuffer.on("line", this.handleNickname.bind(this));
this.socket.on("close", this.handleDisconnect.bind(this));
this.send("Welcome! What is your nickname?");
};
sys.inherits(Chatter, EventEmitter);
Chatter.prototype.handleNickname = function(nickname) {
if(server.isNicknameLegal(nickname)) {
this.nickname = nickname;
this.lineBuffer.removeAllListeners("line");
this.lineBuffer.on("line", this.handleChat.bind(this));
this.send("Welcome to the chat, " + nickname + "!");
this.emit("join", this);
} else {
this.send("Sorry, but that nickname is not legal or is already in use!");
this.send("What is your nickname?");
}
};
Chatter.prototype.handleChat = function(line) {
this.emit("chat", this, line);
};
Chatter.prototype.handleDisconnect = function() {
this.emit("leave", this);
};
Chatter.prototype.send = function(data) {
this.socket.write(data + "\r\n");
};
/*******************************************************************************
* SocketLineBuffer
*
* Listens for and buffers incoming data on a socket and emits a 'line' event
* whenever a complete line is detected.
******************************************************************************/
function SocketLineBuffer(socket) {
EventEmitter.call(this);
this.socket = socket;
this.buffer = "";
this.socket.on("data", this.handleData.bind(this));
};
sys.inherits(SocketLineBuffer, EventEmitter);
SocketLineBuffer.prototype.handleData = function(data) {
for(var i = 0; i < data.length; i++) {
var char = data.charAt(i);
this.buffer += char;
if(char == "\n") {
this.buffer = this.buffer.replace("\r\n", "");
this.buffer = this.buffer.replace("\n", "");
this.emit("line", this.buffer);
this.buffer = "";
}
}
};
// Start the server!
server = new ChatServer();

View file

@ -0,0 +1,28 @@
#!/usr/bin/picolisp /usr/lib/picolisp/lib.l
(de chat Lst
(out *Sock
(mapc prin Lst)
(prinl) ) )
(setq *Port (port 4004))
(loop
(setq *Sock (listen *Port))
(NIL (fork) (close *Port))
(close *Sock) )
(out *Sock
(prin "Please enter your name: ")
(flush) )
(in *Sock (setq *Name (line T)))
(tell 'chat "+++ " *Name " arrived +++")
(task *Sock
(in @
(ifn (eof)
(tell 'chat *Name "> " (line T))
(tell 'chat "--- " *Name " left ---")
(bye) ) ) )
(wait)

View file

@ -0,0 +1,78 @@
#!/usr/bin/env python
import socket
import thread
import time
HOST = ""
PORT = 4004
def accept(conn):
"""
Call the inner func in a thread so as not to block. Wait for a
name to be entered from the given connection. Once a name is
entered, set the connection to non-blocking and add the user to
the users dict.
"""
def threaded():
while True:
conn.send("Please enter your name: ")
try:
name = conn.recv(1024).strip()
except socket.error:
continue
if name in users:
conn.send("Name entered is already in use.\n")
elif name:
conn.setblocking(False)
users[name] = conn
broadcast(name, "+++ %s arrived +++" % name)
break
thread.start_new_thread(threaded, ())
def broadcast(name, message):
"""
Send a message to all users from the given name.
"""
print message
for to_name, conn in users.items():
if to_name != name:
try:
conn.send(message + "\n")
except socket.error:
pass
# Set up the server socket.
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.setblocking(False)
server.bind((HOST, PORT))
server.listen(1)
print "Listening on %s" % ("%s:%s" % server.getsockname())
# Main event loop.
users = {}
while True:
try:
# Accept new connections.
while True:
try:
conn, addr = server.accept()
except socket.error:
break
accept(conn)
# Read from connections.
for name, conn in users.items():
try:
message = conn.recv(1024)
except socket.error:
continue
if not message:
# Empty string is given on disconnect.
del users[name]
broadcast(name, "--- %s leaves ---" % name)
else:
broadcast(name, "%s> %s" % (name, message.strip()))
time.sleep(.1)
except (SystemExit, KeyboardInterrupt):
break

View file

@ -0,0 +1,68 @@
require 'gserver'
class ChatServer < GServer
def initialize *args
super
#Keep a list for broadcasting messages
@chatters = []
#We'll need this for thread safety
@mutex = Mutex.new
end
#Send message out to everyone but sender
def broadcast message, sender = nil
#Need to use \r\n for our Windows friends
message = message.strip << "\r\n"
#Mutex for safety - GServer uses threads
@mutex.synchronize do
@chatters.each do |chatter|
begin
chatter.print message unless chatter == sender
rescue
@chatters.delete chatter
end
end
end
end
#Handle each connection
def serve io
io.print 'Name: '
name = io.gets
#They might disconnect
return if name.nil?
name.strip!
broadcast "--+ #{name} has joined +--"
#Add to our list of connections
@mutex.synchronize do
@chatters << io
end
#Get and broadcast input until connection returns nil
loop do
message = io.gets
if message
broadcast "#{name}> #{message}", io
else
break
end
end
broadcast "--+ #{name} has left +--"
end
end
#Start up the server on port 7000
#Accept connections for any IP address
#Allow up to 100 connections
#Send information to stderr
#Turn on informational messages
ChatServer.new(7000, '0.0.0.0', 100, $stderr, true).start.join

View file

@ -0,0 +1,61 @@
package require Tcl 8.6
# Write a message to everyone except the sender of the message
proc writeEveryoneElse {sender message} {
dict for {who ch} $::cmap {
if {$who ne $sender} {
puts $ch $message
}
}
}
# How to read a line (up to 256 chars long) in a coroutine
proc cgets {ch var} {
upvar 1 $var v
while {[gets $ch v] < 0} {
if {[eof $ch] || [chan pending input $ch] > 256} {
return false
}
yield
}
return true
}
# The chatting, as seen by one user
proc chat {ch addr port} {
### CONNECTION CODE ###
#Log "connection from ${addr}:${port} on channel $ch"
fconfigure $ch -buffering none -blocking 0 -encoding utf-8
fileevent $ch readable [info coroutine]
global cmap
try {
### GET THE NICKNAME OF THE USER ###
puts -nonewline $ch "Please enter your name: "
if {![cgets $ch name]} {
return
}
#Log "Mapping ${addr}:${port} to ${name} on channel $ch"
dict set cmap $name $ch
writeEveryoneElse $name "+++ $name arrived +++"
### MAIN CHAT LOOP ###
while {[cgets $ch line]} {
writeEveryoneElse $name "$name> $line"
}
} finally {
### DISCONNECTION CODE ###
if {[info exists name]} {
writeEveryoneElse $name "--- $name left ---"
dict unset cmap $name
}
close $ch
#Log "disconnection from ${addr}:${port} on channel $ch"
}
}
# Service the socket by making corouines running [chat]
socket -server {coroutine c[incr count] chat} 4004
set ::cmap {}; # Dictionary mapping nicks to channels
vwait forever; # Run event loop