Chat server: Difference between revisions

m
m (Switch from draft to complete task)
m (→‎{{header|Wren}}: Minor tidy)
 
(104 intermediate revisions by 54 users not shown)
Line 1:
{{task|Networking and Web Interaction}}
 
;Task:
Write a server for a minimal text based chat. People should be able to connect
Write a server for a minimal text based chat.
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.
 
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.
<br><br>
=={{header|Ada}}==
{{libheader|AdaSockets}}
 
<syntaxhighlight lang="ada">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;</syntaxhighlight>
 
=={{header|BASIC}}==
==={{header|BaCon}}===
Requires BaCon 4.2 or higher. Clients have to login with an alias and can use the commands 'say' or 'quit'. Notifications are submitted when users enter the chat or leave the chat.
<syntaxhighlight lang="text">DECLARE user$ ASSOC STRING
DECLARE connect ASSOC long
OPEN "localhost:51000" FOR SERVER AS mynet
WHILE TRUE
IF WAIT(mynet, 30) THEN
fd = ACCEPT(mynet)
connect(GETPEER$(fd)) = fd
SEND "Enter your name: " TO fd
ELSE
FOR con$ IN OBTAIN$(connect)
IF WAIT(connect(con$), 10) THEN
RECEIVE in$ FROM connect(con$)
IF user$(GETPEER$(connect(con$))) = "" THEN
user$(GETPEER$(connect(con$))) = CHOP$(in$)
chat$ = chat$ & user$(GETPEER$(connect(con$))) & " joined the chat." & NL$
SEND "Welcome, " & CHOP$(in$) & "!" & NL$ TO connect(con$)
ELIF LEFT$(in$, 4) = "quit" THEN
SEND "You're disconnected!" & NL$ TO connect(con$)
chat$ = chat$ & user$(GETPEER$(connect(con$))) & " left the chat." & NL$
FREE user$(GETPEER$(connect(con$)))
FREE connect(con$)
CLOSE SERVER connect(con$)
ELIF LEFT$(in$, 4) = "say " THEN
chat$ = chat$ & user$(GETPEER$(connect(con$))) & " said: " & MID$(in$, 5)
ENDIF
ENDIF
NEXT
IF LEN(chat$) > 0 THEN
FOR con$ IN OBTAIN$(connect)
IF user$(GETPEER$(connect(con$))) <> "" THEN SEND chat$ TO connect(con$)
NEXT
chat$ = ""
ENDIF
ENDIF
WEND</syntaxhighlight>
 
==={{header|Visual Basic .NET}}===
{{trans|C#}}
<syntaxhighlight lang="vbnet">Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
 
Module Module1
 
Class State
Private ReadOnly client As TcpClient
Private ReadOnly sb As New StringBuilder
 
Public Sub New(name As String, client As TcpClient)
Me.Name = name
Me.client = client
End Sub
 
Public ReadOnly Property Name As String
 
Public Sub Send(text As String)
Dim bytes = Encoding.ASCII.GetBytes(String.Format("{0}" & vbCrLf, text))
client.GetStream().Write(bytes, 0, bytes.Length)
End Sub
End Class
 
ReadOnly connections As New Dictionary(Of Integer, State)
Dim listen As TcpListener
Dim serverThread As Thread
 
Sub Main()
listen = New TcpListener(Net.IPAddress.Parse("127.0.0.1"), 4004)
serverThread = New Thread(New ThreadStart(AddressOf DoListen))
serverThread.Start()
End Sub
 
Private Sub DoListen()
listen.Start()
Console.WriteLine("Server: Started server")
 
Do
Console.Write("Server: Waiting...")
Dim client = listen.AcceptTcpClient()
Console.WriteLine(" Connected")
 
' New thread with client
Dim clientThread As New Thread(New ParameterizedThreadStart(AddressOf DoClient))
 
clientThread.Start(client)
Loop
End Sub
 
Private Sub DoClient(client As TcpClient)
Console.WriteLine("Client (Thread: {0}): Connected!", Thread.CurrentThread.ManagedThreadId)
Dim bytes = Encoding.ASCII.GetBytes("Enter name: ")
client.GetStream().Write(bytes, 0, bytes.Length)
 
Dim done As Boolean
Dim name As String
Do
If Not client.Connected Then
Console.WriteLine("Client (Thread: {0}): Terminated!", Thread.CurrentThread.ManagedThreadId)
client.Close()
Thread.CurrentThread.Abort() ' Kill thread
End If
 
name = Receive(client)
done = True
 
For Each cl In connections
Dim state = cl.Value
If state.Name = name Then
bytes = Encoding.ASCII.GetBytes("Name already registered. Please enter your name: ")
client.GetStream().Write(bytes, 0, bytes.Length)
done = False
End If
Next
Loop While Not done
 
connections.Add(Thread.CurrentThread.ManagedThreadId, New State(name, client))
Console.WriteLine(vbTab & "Total connections: {0}", connections.Count)
Broadcast(String.Format("+++ {0} arrived +++", name))
 
Do
Dim text = Receive(client)
If text = "/quit" Then
Broadcast(String.Format("Connection from {0} closed.", name))
connections.Remove(Thread.CurrentThread.ManagedThreadId)
Console.WriteLine(vbTab & "Total connections: {0}", connections.Count)
Exit Do
End If
 
If Not client.Connected Then
Exit Do
End If
 
Broadcast(String.Format("{0}> {1}", name, text))
Loop
 
Console.WriteLine("Client (Thread: {0}): Terminated!", Thread.CurrentThread.ManagedThreadId)
client.Close()
Thread.CurrentThread.Abort()
End Sub
 
Private Function Receive(client As TcpClient) As String
Dim sb As New StringBuilder
Do
If client.Available > 0 Then
While client.Available > 0
Dim ch = Chr(client.GetStream.ReadByte())
If ch = vbCr Then
' ignore
Continue While
End If
If ch = vbLf Then
Return sb.ToString()
End If
sb.Append(ch)
End While
 
' pause
Thread.Sleep(100)
End If
Loop
End Function
 
Private Sub Broadcast(text As String)
Console.WriteLine(text)
For Each client In connections
If client.Key <> Thread.CurrentThread.ManagedThreadId Then
Dim state = client.Value
state.Send(text)
End If
Next
End Sub
 
End Module</syntaxhighlight>
 
=={{header|C}}==
C has no built-in networking functions, but the POSIX library does provide some low-level networking functions. The functions of interest relating to sockets include ''bind'', ''listen'', ''select'', ''accept'', ''read'', ''write'' and ''close''.
 
The example below was compiled on Cygwin, and accepts PuTTY connections under the RAW protocol.
 
A glitch occurs if a connection is made using the Telnet protocol - user names are preceded by garbled text.
 
<syntaxhighlight lang="c">#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;
}</syntaxhighlight>
=={{header|C sharp|C#}}==
<syntaxhighlight lang="csharp">using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
 
namespace ChatServer {
class State {
private TcpClient client;
private StringBuilder sb = new StringBuilder();
 
public string Name { get; }
 
public State(string name, TcpClient client) {
Name = name;
this.client = client;
}
 
public void Add(byte b) {
sb.Append((char)b);
}
 
public void Send(string text) {
var bytes = Encoding.ASCII.GetBytes(string.Format("{0}\r\n", text));
client.GetStream().Write(bytes, 0, bytes.Length);
}
}
 
class Program {
static TcpListener listen;
static Thread serverthread;
static Dictionary<int, State> connections = new Dictionary<int, State>();
 
static void Main(string[] args) {
listen = new TcpListener(System.Net.IPAddress.Parse("127.0.0.1"), 4004);
serverthread = new Thread(new ThreadStart(DoListen));
serverthread.Start();
}
 
private static void DoListen() {
// Listen
listen.Start();
Console.WriteLine("Server: Started server");
 
while (true) {
Console.WriteLine("Server: Waiting...");
TcpClient client = listen.AcceptTcpClient();
Console.WriteLine("Server: Waited");
 
// New thread with client
Thread clientThread = new Thread(new ParameterizedThreadStart(DoClient));
clientThread.Start(client);
}
}
 
private static void DoClient(object client) {
// Read data
TcpClient tClient = (TcpClient)client;
 
Console.WriteLine("Client (Thread: {0}): Connected!", Thread.CurrentThread.ManagedThreadId);
byte[] bytes = Encoding.ASCII.GetBytes("Enter name: ");
tClient.GetStream().Write(bytes, 0, bytes.Length);
 
string name = string.Empty;
bool done = false;
do {
if (!tClient.Connected) {
Console.WriteLine("Client (Thread: {0}): Terminated!", Thread.CurrentThread.ManagedThreadId);
tClient.Close();
Thread.CurrentThread.Abort(); // Kill thread.
}
 
name = Receive(tClient);
done = true;
 
if (done) {
foreach (var cl in connections) {
var state = cl.Value;
if (state.Name == name) {
bytes = Encoding.ASCII.GetBytes("Name already registered. Please enter your name: ");
tClient.GetStream().Write(bytes, 0, bytes.Length);
done = false;
}
}
}
} while (!done);
 
connections.Add(Thread.CurrentThread.ManagedThreadId, new State(name, tClient));
Console.WriteLine("\tTotal connections: {0}", connections.Count);
Broadcast(string.Format("+++ {0} arrived +++", name));
 
do {
string text = Receive(tClient);
if (text == "/quit") {
Broadcast(string.Format("Connection from {0} closed.", name));
connections.Remove(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("\tTotal connections: {0}", connections.Count);
break;
}
 
if (!tClient.Connected) {
break;
}
Broadcast(string.Format("{0}> {1}", name, text));
} while (true);
 
Console.WriteLine("Client (Thread: {0}): Terminated!", Thread.CurrentThread.ManagedThreadId);
tClient.Close();
Thread.CurrentThread.Abort();
}
 
private static string Receive(TcpClient client) {
StringBuilder sb = new StringBuilder();
do {
if (client.Available > 0) {
while (client.Available > 0) {
char ch = (char)client.GetStream().ReadByte();
if (ch == '\r') {
//ignore
continue;
}
if (ch == '\n') {
return sb.ToString();
}
sb.Append(ch);
}
}
 
// Pause
Thread.Sleep(100);
} while (true);
}
 
private static void Broadcast(string text) {
Console.WriteLine(text);
foreach (var oClient in connections) {
if (oClient.Key != Thread.CurrentThread.ManagedThreadId) {
State state = oClient.Value;
state.Send(text);
}
}
}
}
}</syntaxhighlight>
=={{header|CoffeeScript}}==
This is ported from the JavaScript version. The tool js2coffee got me a mostly working version, and then I manually converted JS-style classes to CS "classic-style class" syntax.
 
<syntaxhighlight lang="coffeescript">
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()
</syntaxhighlight>
=={{header|Common Lisp}}==
{{libheader|usocket}}
{{libheader|simple-actors}}
{{libheader|bordeaux-threads}}
 
This chat server uses the SIMPLE-ACTORS library to avoid having any explicit
locks. Each resource is owned by an actor that receives messages pertaining
to that resource. The *USER-MANAGER* actor maintains a list of active users and the
actor objects that control their connections. It is responsible for sending users'
messages to everyone (by sending messages to the users' actors), announcing when
users join and leave (as a regular message from a user named "Server"), and making
sure that no two users have the same name (or the special name "Server").
 
The *USER-MANAGER* also accepts a :WHO message, which causes it to list the names
of all the connected users to the user who sent the message.
 
Each user connection is managed by a user actor, and is responsible for sending and
receiving messages over a single user's TCP connection, and for interpreting
/COMMANDS (supported: /WHO, /HELP, /QUIT, and also /NICK in the special case that the
initially-entered username was rejected by the *USER-MANAGER*). It is also responsible
for closing the user's network connection, either on receiving a :CLOSE message from the
*USER-MANAGER*, or upon an error occurring.
 
<syntaxhighlight lang="common-lisp">
(ql:quickload '(:usocket :simple-actors :bordeaux-threads))
 
(defpackage :chat-server
(:use :common-lisp :usocket :simple-actors :bordeaux-threads)
(:export :accept-connections))
 
(in-package :chat-server)
 
(defvar *whitespace* '(#\Space #\Tab #\Page #\Vt #\Newline #\Return))
 
(defun send-message (users from-user message)
(loop for (nil . actor) in users
do (send actor :message from-user message)))
 
(defun socket-format (socket format-control &rest format-arguments)
(apply #'format (socket-stream socket) format-control format-arguments)
(finish-output (socket-stream socket)))
 
(defvar *log* *standard-output*)
 
(defmacro log-errors (&body body)
`(handler-case
(progn ,@body)
(t (err)
(format *log* "Error: ~a" err))))
 
(defparameter *user-manager*
(let ((users nil))
(actor (action &rest args)
(format *log* "Handling message ~s~%" (cons action args))
(ecase action
(:newuser
(destructuring-bind (username session-actor)
args
(cond ((assoc username users :test #'equalp)
(send session-actor :rejected
(format nil "Username ~a is already taken. Send /NICK new-nick with a valid name to enter the chat~%" username)))
((equalp username "Server")
(send session-actor :rejected
(format nil "Server is not a valid username. Send /NICK new-nick with a valid name to enter the chat~%")))
(t (send-message users "Server" (format nil "~a has joined the chat." username))
(send session-actor :accepted
(format nil "Welcome to the Rosetta Code chat server in Common Lisp. ~a users connected.~%"
(length users)))
(pushnew (cons username session-actor)
users
:key #'car
:test #'equalp)))))
(:who
(destructuring-bind (username) args
(let ((actor (cdr (assoc username users :test #'equalp))))
(send actor :message "Server"
"Users connected right now:")
(loop for (user . nil) in users
do (send actor :message "Server" user)))))
(:message
(apply #'send-message users args))
(:dropuser
(destructuring-bind (username) args
(let ((user-actor (cdr (assoc username users :test #'equalp))))
(send user-actor :close)
(send user-actor 'stop))
(setf users (remove username users
:key #'car
:test #'equalp))
(send-message users "Server" (format nil "~a has left." username))))))))
 
(defmacro drop-connection-on-error (&body body)
`(handler-case (progn ,@body)
(t (err)
(format *log* "Error: ~a; Closing connection" err)
(send self :close)
(send self 'stop)
(send *user-manager* :dropuser username))))
 
(defun parse-command (message)
(let* ((space-at (position #\Space message))
(after-space (and space-at
(position-if (lambda (ch)
(not (char= ch #\Space)))
message :start (1+ space-at)))))
(values (subseq message 0 space-at)
(and after-space
(string-trim *whitespace*
(subseq message after-space))))))
 
(defun help (socket)
(socket-format socket "/QUIT to quit, /WHO to list users.~%"))
 
(defun make-user (username socket)
(let* ((state :unregistered)
(actor
(actor (message &rest args)
(drop-connection-on-error
(ecase message
(:register
(send *user-manager* :newuser username self))
(:accepted
(destructuring-bind (message) args
(write-string message (socket-stream socket))
(finish-output (socket-stream socket))
(setf state :registered)))
(:rejected
(destructuring-bind (message) args
(write-string message (socket-stream socket))
(finish-output (socket-stream socket))
(setf state :unregistered)))
(:user-typed
(destructuring-bind (message) args
(when (> (length message) 0)
(if (char= (aref message 0) #\/)
(multiple-value-bind (cmd arg)
(parse-command message)
(cond ((equalp cmd "/nick")
(ecase state
(:unregistered
(setf username arg)
(send *user-manager* :newuser username self))
(:registered
(socket-format socket
"Can't change your name after successfully registering~%"))))
((equalp cmd "/help")
(help socket))
((equalp cmd "/who")
(send *user-manager* :who username))
((equalp cmd "/quit")
(socket-format socket
"Goodbye.~%")
(send *user-manager* :dropuser username))
(t
(socket-format socket
"Unknown command~%"))))
(send *user-manager* :message username message)))))
(:message
(destructuring-bind (from-user message) args
(socket-format socket "<~a> ~a~%" from-user message)))
(:close
(log-errors
(close (socket-stream socket)))))))))
(bt:make-thread (lambda ()
(handler-case
(loop for line = (read-line (socket-stream socket) nil :eof)
do (if (eq line :eof)
(send *user-manager* :dropuser username)
(send actor :user-typed (remove #\Return line))))
(t () (send *user-manager* :dropuser username))))
:name "Reader thread")
actor))
 
(defun initialize-user (socket)
(bt:make-thread
(lambda ()
(format *log* "Handling new connection ~s" socket)
(log-errors
(loop do
(socket-format socket "Your name: ")
(let ((name (string-trim *whitespace* (read-line (socket-stream socket)))))
(format *log* "Registering user ~a" name)
(cond ((equalp name "Server")
(socket-format socket
"Server is not a valid username.~%"))
(t (send *user-manager*
:newuser name (make-user name socket))
(return)))))))
:name "INITIALIZE-USER"))
 
(defun accept-connections ()
(let ((accepting-socket (socket-listen "0.0.0.0" 7070)))
(loop for new-connection = (socket-accept accepting-socket)
do (initialize-user new-connection))))
 
(make-thread #'accept-connections)
</syntaxhighlight>
=={{header|D}}==
<syntaxhighlight lang="d">
import std.getopt;
import std.socket;
import std.stdio;
import std.string;
 
struct client {
int pos;
char[] name;
char[] buffer;
Socket socket;
}
 
void broadcast(client[] connections, size_t self, const char[] message) {
writeln(message);
for (size_t i = 0; i < connections.length; i++) {
if (i == self) continue;
 
connections[i].socket.send(message);
connections[i].socket.send("\r\n");
}
}
 
bool registerClient(client[] connections, size_t self) {
for (size_t i = 0; i < connections.length; i++) {
if (i == self) continue;
 
if (icmp(connections[i].name, connections[self].name) == 0) {
return false;
}
}
 
return true;
}
 
void main(string[] args) {
ushort port = 4004;
 
auto helpInformation = getopt
(
args,
"port|p", "The port to listen to chat clients on [default is 4004]", &port
);
 
if (helpInformation.helpWanted) {
defaultGetoptPrinter("A simple chat server based on a task in rosettacode.", helpInformation.options);
return;
}
 
auto listener = new TcpSocket();
assert(listener.isAlive);
listener.blocking = false;
listener.bind(new InternetAddress(port));
listener.listen(10);
writeln("Listening on port: ", port);
 
enum MAX_CONNECTIONS = 60;
auto socketSet = new SocketSet(MAX_CONNECTIONS + 1);
client[] connections;
 
while(true) {
socketSet.add(listener);
 
foreach (con; connections) {
socketSet.add(con.socket);
}
 
Socket.select(socketSet, null, null);
 
for (size_t i = 0; i < connections.length; i++) {
if (socketSet.isSet(connections[i].socket)) {
char[1024] buf;
auto datLength = connections[i].socket.receive(buf[]);
 
if (datLength == Socket.ERROR) {
writeln("Connection error.");
} else if (datLength != 0) {
if (buf[0] == '\n' || buf[0] == '\r') {
if (connections[i].buffer == "/quit") {
connections[i].socket.close();
if (connections[i].name.length > 0) {
writeln("Connection from ", connections[i].name, " closed.");
} else {
writeln("Connection from ", connections[i].socket.remoteAddress(), " closed.");
}
 
connections[i] = connections[$-1];
connections.length--;
i--;
 
writeln("\tTotal connections: ", connections.length);
continue;
} else if (connections[i].name.length == 0) {
connections[i].buffer = strip(connections[i].buffer);
if (connections[i].buffer.length > 0) {
connections[i].name = connections[i].buffer;
if (registerClient(connections, i)) {
connections.broadcast(i, "+++ " ~ connections[i].name ~ " arrived +++");
} else {
connections[i].socket.send("Name already registered. Please enter your name: ");
connections[i].name.length = 0;
}
} else {
connections[i].socket.send("A name is required. Please enter your name: ");
}
} else {
connections.broadcast(i, connections[i].name ~ "> " ~ connections[i].buffer);
}
connections[i].buffer.length = 0;
} else {
connections[i].buffer ~= buf[0..datLength];
}
} else {
try {
if (connections[i].name.length > 0) {
writeln("Connection from ", connections[i].name, " closed.");
} else {
writeln("Connection from ", connections[i].socket.remoteAddress(), " closed.");
}
} catch (SocketException) {
writeln("Connection closed.");
}
}
}
}
 
if (socketSet.isSet(listener)) {
Socket sn = null;
scope(failure) {
writeln("Error accepting");
 
if (sn) {
sn.close();
}
}
sn = listener.accept();
assert(sn.isAlive);
assert(listener.isAlive);
 
if (connections.length < MAX_CONNECTIONS) {
client newclient;
 
writeln("Connection from ", sn.remoteAddress(), " established.");
sn.send("Enter name: ");
 
newclient.socket = sn;
connections ~= newclient;
 
writeln("\tTotal connections: ", connections.length);
} else {
writeln("Rejected connection from ", sn.remoteAddress(), "; too many connections.");
sn.close();
assert(!sn.isAlive);
assert(listener.isAlive);
}
}
 
socketSet.reset();
}
}
</syntaxhighlight>
=={{header|Erlang}}==
<syntaxhighlight lang="erlang">
-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.
</syntaxhighlight>
=={{header|Go}}==
This example uses the Go idiom of [http://blog.golang.org/share-memory-by-communicating ''Do not communicate by sharing memory; instead, share memory by communicating'']; there are no explicit locks used, instead Go channels are used to safely synchronize where required.
 
A similar exercise of a chat roulette (different in that messages only have to be written to a single partner rather than broadcast, this simplifies the code greatly) was the topic of a [http://talks.golang.org/2012/chat.slide#1 2012 Go Talk].
 
This example handles the case of one specific client "falling behind" by relying on the underlying TCP stack to do a reasonable job of buffering. Once that buffer fills, a write to the that client's connection will time out and the connection will dropped. Other minor improvements would include enabling TCP keep alives, handling temporary errors from accept, and better logging. Not ideal, but it should be good enough for this example.
 
<syntaxhighlight lang="go">package main
 
import (
"bufio"
"flag"
"fmt"
"log"
"net"
"strings"
"time"
)
 
func main() {
log.SetPrefix("chat: ")
addr := flag.String("addr", "localhost:4000", "listen address")
flag.Parse()
log.Fatal(ListenAndServe(*addr))
}
 
// A Server represents a chat server that accepts incoming connections.
type Server struct {
add chan *conn // To add a connection
rem chan string // To remove a connection by name
msg chan string // To send a message to all connections
stop chan bool // To stop early
}
 
// ListenAndServe listens on the TCP network address addr for
// new chat client connections.
func ListenAndServe(addr string) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
log.Println("Listening for connections on", addr)
defer ln.Close()
s := &Server{
add: make(chan *conn),
rem: make(chan string),
msg: make(chan string),
stop: make(chan bool),
}
go s.handleConns()
for {
// TODO use AcceptTCP() so that we can get a TCPConn on which
// we can call SetKeepAlive() and SetKeepAlivePeriod()
rwc, err := ln.Accept()
if err != nil {
// TODO Could handle err.(net.Error).Temporary()
// here by adding a backoff delay.
close(s.stop)
return err
}
log.Println("New connection from", rwc.RemoteAddr())
go newConn(s, rwc).welcome()
}
}
 
// handleConns is run as a go routine to handle adding and removal of
// chat client connections as well as broadcasting messages to them.
func (s *Server) handleConns() {
// We define the `conns` map here rather than within Server,
// and we use local function literals rather than methods to be
// extra sure that the only place that touches this map is this
// method. In this way we forgo any explicit locking needed as
// we're the only go routine that can see or modify this.
conns := make(map[string]*conn)
 
var dropConn func(string)
writeAll := func(str string) {
log.Printf("Broadcast: %q", str)
// TODO handle blocked connections
for name, c := range conns {
c.SetWriteDeadline(time.Now().Add(500 * time.Millisecond))
_, err := c.Write([]byte(str))
if err != nil {
log.Printf("Error writing to %q: %v", name, err)
c.Close()
delete(conns, name)
// Defer all the disconnect messages until after
// we've closed all currently problematic conns.
defer dropConn(name)
}
}
}
 
dropConn = func(name string) {
if c, ok := conns[name]; ok {
log.Printf("Closing connection with %q from %v",
name, c.RemoteAddr())
c.Close()
delete(conns, name)
} else {
log.Printf("Dropped connection with %q", name)
}
str := fmt.Sprintf("--- %q disconnected ---\n", name)
writeAll(str)
}
 
defer func() {
writeAll("Server stopping!\n")
for _, c := range conns {
c.Close()
}
}()
 
for {
select {
case c := <-s.add:
if _, exists := conns[c.name]; exists {
fmt.Fprintf(c, "Name %q is not available\n", c.name)
go c.welcome()
continue
}
str := fmt.Sprintf("+++ %q connected +++\n", c.name)
writeAll(str)
conns[c.name] = c
go c.readloop()
case str := <-s.msg:
writeAll(str)
case name := <-s.rem:
dropConn(name)
case <-s.stop:
return
}
}
}
 
// A conn represents the server side of a single chat connection.
// Note we embed the bufio.Reader and net.Conn (and specifically in
// that order) so that a conn gets the appropriate methods from each
// to be a full io.ReadWriteCloser.
type conn struct {
*bufio.Reader // buffered input
net.Conn // raw connection
server *Server // the Server on which the connection arrived
name string
}
 
func newConn(s *Server, rwc net.Conn) *conn {
return &conn{
Reader: bufio.NewReader(rwc),
Conn: rwc,
server: s,
}
}
 
// welcome requests a name from the client before attempting to add the
// named connect to the set handled by the server.
func (c *conn) welcome() {
var err error
for c.name = ""; c.name == ""; {
fmt.Fprint(c, "Enter your name: ")
c.name, err = c.ReadString('\n')
if err != nil {
log.Printf("Reading name from %v: %v", c.RemoteAddr(), err)
c.Close()
return
}
c.name = strings.TrimSpace(c.name)
}
// The server will take this *conn and do a final check
// on the name, possibly starting c.welcome() again.
c.server.add <- c
}
 
// readloop is started as a go routine by the server once the initial
// welcome phase has completed successfully. It reads single lines from
// the client and passes them to the server for broadcast to all chat
// clients (including us).
// Once done, we ask the server to remove (and close) our connection.
func (c *conn) readloop() {
for {
msg, err := c.ReadString('\n')
if err != nil {
break
}
//msg = strings.TrimSpace(msg)
c.server.msg <- c.name + "> " + msg
}
c.server.rem <- c.name
}</syntaxhighlight>
=={{header|Groovy}}==
{{trans|Java}}
<syntaxhighlight lang="groovy">class ChatServer implements Runnable {
private int port = 0
private List<Client> clientList = new ArrayList<>()
 
ChatServer(int port) {
this.port = port
}
 
@SuppressWarnings("GroovyInfiniteLoopStatement")
@Override
void run() {
try {
ServerSocket serverSocket = new ServerSocket(port)
while (true) {
Socket socket = serverSocket.accept()
new Thread(new Client(socket)).start()
}
} catch (Exception e) {
e.printStackTrace()
}
}
 
private synchronized boolean registerClient(Client client) {
for (Client other : clientList) {
if (other.clientName.equalsIgnoreCase(client.clientName)) {
return false
}
}
clientList.add(client)
return true
}
 
private void deRegisterClient(Client client) {
boolean wasRegistered
synchronized (this) {
wasRegistered = clientList.remove(client)
}
if (wasRegistered) {
broadcast(client, "--- " + client.clientName + " left ---")
}
}
 
private synchronized String getOnlineListCSV() {
StringBuilder sb = new StringBuilder()
sb.append(clientList.size()).append(" user(s) online: ")
def it = clientList.iterator()
if (it.hasNext()) {
sb.append(it.next().clientName)
}
while (it.hasNext()) {
sb.append(", ")
sb.append(it.next().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
synchronized (this) {
clients = new ArrayList<>(this.clientList)
}
for (Client client : clients) {
if (client == fromClient) {
continue
}
try {
client.write(msg + "\r\n")
} catch (Exception ignored) {
// empty
}
}
}
 
class Client implements Runnable {
private Socket socket = null
private Writer output = null
private String clientName = null
 
Client(Socket socket) {
this.socket = socket
}
 
@Override
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
while (null != (line = input.readLine())) {
if (null == clientName) {
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 ignored) {
// empty
} finally {
deRegisterClient(this)
output = null
try {
socket.close()
} catch (Exception ignored) {
// empty
}
socket = null
}
}
 
void write(String msg) {
output.write(msg)
output.flush()
}
 
@Override
boolean equals(client) {
return (null != client) && (client instanceof Client) && (null != clientName) && clientName == client.clientName
}
 
@Override
int hashCode() {
int result
result = (socket != null ? socket.hashCode() : 0)
result = 31 * result + (output != null ? output.hashCode() : 0)
result = 31 * result + (clientName != null ? clientName.hashCode() : 0)
return result
}
}
 
static void main(String[] args) {
int port = 4004
if (args.length > 0) {
port = Integer.parseInt(args[0])
}
new ChatServer(port).run()
}
}</syntaxhighlight>
=={{header|Haskell}}==
<syntaxhighlight lang="haskell">{-# LANGUAGE OverloadedStrings #-}
import Network
import System.IO
import Control.Concurrent
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.Text.IO as T
import qualified Data.Map as M
import Data.Map (Map)
import Control.Monad.Reader
import Control.Monad.Error
import Control.Exception
import Data.Monoid
import Control.Applicative
 
type ServerApp = ReaderT ThreadData IO
data Speaker = Server | Client Text
data ThreadData = ThreadData { threadHandle :: Handle
, userTableMV :: MVar (Map Text Handle)}
 
echoLocal = liftIO . T.putStrLn
echoRemote = echoMessage . (">> "<>)
echoMessage msg = viewHandle >>= \h -> liftIO $ T.hPutStrLn h msg
getRemoteLine = viewHandle >>= liftIO . T.hGetLine
putMVarT = (liftIO.) . putMVar
takeMVarT = liftIO . takeMVar
readMVarT = liftIO . readMVar
modifyUserTable fn = viewUsers >>= \mv ->
liftIO $ modifyMVar_ mv (return . fn)
viewHandle = threadHandle <$> ask
viewUsers = userTableMV <$> ask
 
userChat :: ServerApp ()
userChat = do
name <- addUser
echoLocal name
h <- viewHandle
(flip catchError) (\_ -> removeUser name) $
do echoLocal $ "Accepted " <> name
forever $ getRemoteLine >>= broadcast (Client name)
 
removeUser :: Text -> ServerApp ()
removeUser name = do
echoLocal $ "Exception with " <> name <> ", removing from userTable"
broadcast Server $ name <> " has left the server"
modifyUserTable (M.delete name)
 
addUser :: ServerApp Text
addUser = do
h <- viewHandle
usersMV <- viewUsers
echoRemote "Enter username"
name <- T.filter (/='\r') <$> getRemoteLine
userTable <- takeMVarT usersMV
if name `M.member` userTable
then do echoRemote "Username already exists!"
putMVarT usersMV userTable
addUser
else do putMVarT usersMV (M.insert name h userTable)
broadcast Server $ name <> " has joined the server"
echoRemote "Welcome to the server!\n>> Other users:"
readMVarT usersMV >>=
mapM_ (echoRemote . ("*" <>) . fst)
. filter ((/=name). fst) . M.toList
return name
 
broadcast :: Speaker -> Text -> ServerApp ()
broadcast user msg =
viewUsers >>= readMVarT >>= mapM_ (f . snd) . fn . M.toList
where f h = liftIO $ T.hPutStrLn h $ nm <> msg
(fn, nm) = case user of
Server -> (id, ">> ")
Client t -> (filter ((/=t) . fst), t <> "> ")
 
clientLoop socket users = do
(h, _, _) <- accept socket
hSetBuffering h LineBuffering
forkIO $ runReaderT userChat (ThreadData h users)
clientLoop socket users
 
main = do
server <- listenOn $ PortNumber 5002
T.putStrLn "Server started"
newMVar (M.empty) >>= clientLoop server
</syntaxhighlight>
 
==Icon and {{header|Unicon}}==
 
This is Unicon-specific:
<syntaxhighlight lang="unicon">global mlck, nCons, cons
 
procedure main()
mlck := mutex()
nCons := 0
cons := mutex(set())
while f := open(":12321","na") do {
handle_client(f)
critical mlck: if nCons <= 0 then close(f)
}
end
 
procedure handle_client(f)
critical mlck: nCons +:= 1
thread {
select(f,1000) & {
writes(f, "Name? ")
nick := (read(f) ? tab(upto('\n\r')))
every write(!cons, nick," has joined.")
insert(cons, f)
while s := read(f) do every write(!cons, nick,": ",s)
}
delete(cons, f)
every write(!cons, nick," has left.")
critical mlck: nCons -:= 1
}
end</syntaxhighlight>
=={{header|Java}}==
 
Broadcasting of messages is done by the thread that received the message, so a bad client could potentially disrupt the server. The output buffer is set to 16K in an attempt to alleviate possible symptoms, but I'm not sure if it's effective. Server does not allow duplicate client names, and lists all users online after a successful connection. Client can type "/quit" to quit.
 
I think ideally, NIO would be used to select() sockets available/ready for I/O, to eliminate the possibility of a bad connection disrupting the server, but this increases the complexity.
 
<syntaxhighlight lang="java">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();
}
}
</syntaxhighlight>
=={{header|JavaScript}}==
{{works with|Node.js}}
<syntaxhighlight lang="javascript">const net = require("net");
const EventEmitter = require("events").EventEmitter;
/*******************************************************************************
* ChatServer
*
* Manages connections, users, and chat messages.
******************************************************************************/
class ChatServer {
constructor() {
this.chatters = {};
this.server = net.createServer(this.handleConnection.bind(this));
this.server.listen(1212, "localhost");
}
isNicknameLegal(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 (const used_nick in this.chatters) {
if (used_nick === nickname) {
return false;
}
}
return true;
}
handleConnection(connection) {
console.log(`Incoming connection from ${connection.remoteAddress}`);
connection.setEncoding("utf8");
 
let 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));
}
handleChat(chatter, message) {
this.sendToEveryChatterExcept(chatter, chatter.nickname + ": " + message);
}
handleJoin(chatter) {
console.log(`${chatter.nickname} has joined the chat.`);
this.sendToEveryChatter(`${chatter.nickname} has joined the chat.`);
this.addChatter(chatter);
}
handleLeave(chatter) {
console.log(`${chatter.nickname} has left the chat.`);
this.removeChatter(chatter);
this.sendToEveryChatter(`${chatter.nickname} has left the chat.`);
}
addChatter(chatter) {
this.chatters[chatter.nickname] = chatter;
}
removeChatter(chatter) {
delete this.chatters[chatter.nickname];
}
sendToEveryChatter(data) {
for (const nickname in this.chatters) {
this.chatters[nickname].send(data);
}
}
sendToEveryChatterExcept(chatter, data) {
for (const nickname in this.chatters) {
if (nickname !== chatter.nickname) {
this.chatters[nickname].send(data);
}
}
}
}
/*******************************************************************************
* Chatter
*
* Represents a single user/connection in the chat server.
******************************************************************************/
class Chatter extends EventEmitter {
constructor(socket, server) {
super();
 
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?");
}
handleNickname(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?");
}
}
handleChat(line) {
this.emit("chat", this, line);
}
handleDisconnect() {
this.emit("leave", this);
}
send(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.
******************************************************************************/
class SocketLineBuffer extends EventEmitter {
constructor(socket) {
super();
 
this.socket = socket;
this.buffer = "";
 
this.socket.on("data", this.handleData.bind(this));
}
handleData(data) {
for (let i = 0; i < data.length; i++) {
const 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();</syntaxhighlight>
=={{header|Julia}}==
Modified to fit the Rosetta Code task from example code for the WebSockets module written by Leah Hanson.
To test, start the code and use a browser to connect to localhost:8000.
<syntaxhighlight lang="julia">
using HttpServer
using WebSockets
 
const connections = Dict{Int,WebSocket}()
const usernames = Dict{Int,String}()
 
function decodeMessage( msg )
String(copy(msg))
end
 
 
wsh = WebSocketHandler() do req, client
global connections
@show connections[client.id] = client
println("req is $req")
notifyonline = "Connection from user number $(client.id) is now online."
for (k,v) in connections
if k != client.id
try
write(v, notifyonline)
catch
continue
end
end
end
while true
try
msg = read(client)
catch
telloffline = "User $(usernames[client.id]) disconnected."
println(telloffline, "(The client id was $(client.id).)")
delete!(connections, client.id)
if haskey(usernames, client.id)
delete!(usernames, client.id)
end
for (k,v) in connections
try
write(v, telloffline)
catch
continue
end
end
return
end
msg = decodeMessage(msg)
if startswith(msg, "setusername:")
println("SETTING USERNAME: $msg")
usernames[client.id] = msg[13:end]
notifyusername = "User number $(client.id) chose $(usernames[client.id]) as name handle."
for (k,v) in connections
try
write(v, notifyusername)
catch
println("Caught exception writing to user $k")
continue
end
end
end
if startswith(msg, "say:")
println("EMITTING MESSAGE: $msg")
for (k,v) in connections
if k != client.id
try
write(v, (usernames[client.id] * ": " * msg[5:end]))
catch
println("Caught exception writing to user $k")
continue
end
end
end
end
end
end
 
onepage = readstring(Pkg.dir("WebSockets","examples","chat-client.html"))
httph = HttpHandler() do req::Request, res::Response
Response(onepage)
end
 
server = Server(httph, wsh)
println("Chat server listening on 8000...")
run(server,8000)
</syntaxhighlight>
=={{header|Kotlin}}==
{{trans|Java}}
<syntaxhighlight lang="scala">import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.io.Writer
import java.net.ServerSocket
import java.net.Socket
import java.util.ArrayList
import java.util.Collections
 
class ChatServer private constructor(private val port: Int) : Runnable {
private val clients = ArrayList<Client>()
 
private val onlineListCSV: String
@Synchronized get() {
val sb = StringBuilder()
sb.append(clients.size).append(" user(s) online: ")
for (i in clients.indices) {
sb.append(if (i > 0) ", " else "").append(clients[i].clientName)
}
return sb.toString()
}
 
override fun run() {
try {
val ss = ServerSocket(port)
while (true) {
val s = ss.accept()
Thread(Client(s)).start()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
 
@Synchronized
private fun registerClient(client: Client): Boolean {
for (otherClient in clients) {
if (otherClient.clientName!!.equals(client.clientName!!, ignoreCase = true)) {
return false
}
}
clients.add(client)
return true
}
 
private fun deRegisterClient(client: Client) {
var wasRegistered = false
synchronized(this) {
wasRegistered = clients.remove(client)
}
if (wasRegistered) {
broadcast(client, "--- " + client.clientName + " left ---")
}
}
 
private fun broadcast(fromClient: Client, msg: String) {
// Copy client list (don't want to hold lock while doing IO)
var clients: List<Client> = Collections.emptyList()
synchronized(this) {
clients = ArrayList(this.clients)
}
for (client in clients) {
if (client.equals(fromClient)) {
continue
}
try {
client.write(msg + "\r\n")
} catch (e: Exception) {
e.printStackTrace()
}
 
}
}
 
inner class Client internal constructor(private var socket: Socket?) : Runnable {
private var output: Writer? = null
var clientName: String? = null
 
override fun run() {
try {
socket!!.sendBufferSize = 16384
socket!!.tcpNoDelay = true
val input = BufferedReader(InputStreamReader(socket!!.getInputStream()))
output = OutputStreamWriter(socket!!.getOutputStream())
write("Please enter your name: ")
var line: String
while (true) {
line = input.readLine()
if (null == line) {
break
}
if (clientName == null) {
line = line.trim { it <= ' ' }
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(onlineListCSV + "\r\n")
broadcast(this, "+++ $clientName arrived +++")
continue
}
if (line.equals("/quit", ignoreCase = true)) {
return
}
broadcast(this, "$clientName> $line")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
deRegisterClient(this)
output = null
try {
socket!!.close()
} catch (e: Exception) {
e.printStackTrace()
}
 
socket = null
}
}
 
@Throws(IOException::class)
internal fun write(msg: String) {
output!!.write(msg)
output!!.flush()
}
 
internal fun equals(client: Client?): Boolean {
return (client != null
&& clientName != null
&& client.clientName != null
&& clientName == client.clientName)
}
}
 
companion object {
@JvmStatic
fun main(args: Array<String>) {
var port = 4004
if (args.isNotEmpty()) {
port = Integer.parseInt(args[0])
}
ChatServer(port).run()
}
}
}</syntaxhighlight>
=={{header|Nim}}==
<syntaxhighlight lang="nim">import asyncnet, asyncdispatch
 
type
Client = tuple
socket: AsyncSocket
name: string
connected: bool
 
var clients {.threadvar.}: seq[Client]
 
proc sendOthers(client: Client, line: string) {.async.} =
for c in clients:
if c != client and c.connected:
await c.socket.send(line & "\c\L")
 
proc processClient(socket: AsyncSocket) {.async.} =
await socket.send("Please enter your name: ")
var client: Client = (socket, await socket.recvLine(), true)
 
clients.add(client)
asyncCheck client.sendOthers("+++ " & client.name & " arrived +++")
 
while true:
let line = await client.socket.recvLine()
if line == "":
asyncCheck client.sendOthers("--- " & client.name & " leaves ---")
client.connected = false
return
asyncCheck client.sendOthers(client.name & "> " & line)
 
proc serve() {.async.} =
clients = @[]
var server = newAsyncSocket()
server.bindAddr(Port(4004))
server.listen()
 
while true:
let socket = await server.accept()
asyncCheck processClient(socket)
 
asyncCheck serve()
runForever()</syntaxhighlight>
=={{header|Objeck}}==
<syntaxhighlight lang="objeck">
use System.IO.Net;
use System.Concurrency;
use Collection;
 
bundle Default {
class ChatServer {
@clients : StringMap;
@clients_mutex : ThreadMutex;
New() {
@clients := StringMap->New();
@clients_mutex := ThreadMutex->New("clients_mutex");
}
 
method : ValidLogin(login_name : String, clients : StringMap) ~ Bool {
if(clients->Has(login_name)) {
return false;
};
return true;
}
 
function : Main(args : String[]) ~ Nil {
chat_server := ChatServer->New();
chat_server->Run();
}
method : public : Broadcast(message : String, sender : Client) ~ Nil {
client_array : Vector;
critical(@clients_mutex) {
client_array := @clients->GetValues();
};
each(i : client_array) {
client := client_array->Get(i)->As(Client);
if(client <> sender) {
client->Send(message);
};
};
}
 
method : public : Disconnect(sender : Client) ~ Nil {
send_name := sender->GetName();
Broadcast("+++ {$send_name} has left +++", sender);
critical(@clients_mutex) {
@clients->Remove(sender->GetName());
};
sender->Close();
}
 
method : public : Run() ~ Nil {
server := TCPSocketServer->New(4661);
if(server->Listen(5)) {
while(true) {
client_sock := server->Accept();
critical(@clients_mutex) {
client_sock->WriteString("login: ");
login_name := client_sock->ReadString();
if(ValidLogin(login_name, @clients)) {
client := Client->New(login_name, client_sock, @self);
@clients->Insert(client->GetName(), client);
client->Execute(Nil);
}
else {
client_sock->WriteString("+++ login in use +++\r\n");
client_sock->Close();
};
};
};
};
server->Close();
}
}
 
class Client from Thread {
@client_sock : TCPSocket;
@server : ChatServer;
New(login_name : String, client_sock : TCPSocket, server : ChatServer) {
Parent(login_name);
@client_sock := client_sock;
@server := server;
}
method : public : Close() ~ Nil {
@client_sock->Close();
}
method : public : Send(message : String) ~ Nil {
if(@client_sock->IsOpen() & message->Size() > 0) {
@client_sock->WriteString("{$message}\r\n");
}
else {
@server->Disconnect(@self);
};
}
 
method : public : Run(param : Base) ~ Nil {
client_name := GetName();
@server->Broadcast("+++ {$client_name} has arrived +++", @self);
message := @client_sock->ReadString();
while(message->Size() > 0 & message->Equals("/quit") = false) {
@server->Broadcast("{$client_name}> {$message}", @self);
message := @client_sock->ReadString();
};
@server->Disconnect(@self);
}
}
}
</syntaxhighlight>
=={{header|Ol}}==
<syntaxhighlight lang="scheme">
(define (timestamp) (syscall 201 "%c"))
 
(fork-server 'chat-room (lambda ()
(let this ((visitors #empty))
(let* ((envelope (wait-mail))
(sender msg envelope))
(case msg
(['join who name]
(let ((visitors (put visitors who name)))
(for-each (lambda (who)
(print-to (car who) name " joined to as"))
(ff->alist visitors))
(this visitors)))
(['talk message]
(for-each (lambda (who)
(print-to (car who) (cdr who) ": " message))
(ff->alist visitors))
(this visitors))
(['part who]
(for-each (lambda (who)
(print-to (car who) (visitors (car who) "unknown") " leaved"))
(ff->alist visitors))
(let ((visitors (del visitors who)))
(this visitors))))))))
 
 
(define (on-accept name fd)
(lambda ()
(print "# " (timestamp) "> we got new visitor: " name)
(mail 'chat-room ['join fd name])
 
(let*((ss1 ms1 (clock)))
(let loop ((str #null) (stream (force (port->bytestream fd))))
(cond
((null? stream)
#false)
((function? stream)
(mail 'chat-room ['talk (list->string (reverse str))])
(loop #null (force stream)))
(else
(loop (cons (car stream) str) (cdr stream)))))
(syscall 3 fd)
(let*((ss2 ms2 (clock)))
(print "# " (timestamp) "> visitor leave us. It takes " (+ (* (- ss2 ss1) 1000) (- ms2 ms1)) "ms.")))
(mail 'chat-room ['part fd])
))
 
(define (run port)
(let ((socket (syscall 41)))
; bind
(let loop ((port port))
(if (not (syscall 49 socket port)) ; bind
(loop (+ port 2))
(print "Server binded to " port)))
; listen
(if (not (syscall 50 socket)) ; listen
(shutdown (print "Can't listen")))
 
; accept
(let loop ()
(if (syscall 23 socket) ; select
(let ((fd (syscall 43 socket))) ; accept
;(print "\n# " (timestamp) ": new request from " (syscall 51 fd))
(fork (on-accept (syscall 51 fd) fd))))
(sleep 0)
(loop))))
 
(run 8080)
</syntaxhighlight>
{{Out}}
 
Usecase:
# First client connected.
# Second client connected.
# First client say "I'm the first. hello!".
# Second client say "I'm the second :)".
# Second client say "see you..".
# Second client disconnected.
# First client disconnected.
 
Server:
<pre>
$ ol chat_server.scm
Server binded to 8080
# Fri Jul 24 00:29:49 2020> we got new visitor: (127.0.0.1 . 55316)
# Fri Jul 24 00:29:55 2020> we got new visitor: (127.0.0.1 . 55320)
# Fri Jul 24 00:30:34 2020> visitor leave us. It takes 38977ms.
# Fri Jul 24 00:30:51 2020> visitor leave us. It takes 61962ms.
</pre>
 
First client:
<pre>
$ telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
(127.0.0.1 . 55316) joined to as
(127.0.0.1 . 55320) joined to as
I'm the first. hello!
(127.0.0.1 . 55316): I'm the first. hello!
 
(127.0.0.1 . 55316): I'm the second :)
 
(127.0.0.1 . 55316): see you..
 
(127.0.0.1 . 55316) leaved
</pre>
 
Second client:
<pre>
$ telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
(127.0.0.1 . 55320) joined to as
(127.0.0.1 . 55320): I'm the first. hello!
 
I'm the second :)
(127.0.0.1 . 55320): I'm the second :)
 
see you..
(127.0.0.1 . 55320): see you..
</pre>
=={{header|Perl}}==
{{trans|Python}}
<syntaxhighlight lang="perl">use 5.010;
use strict;
use warnings;
 
use threads;
use threads::shared;
 
use IO::Socket::INET;
use Time::HiRes qw(sleep ualarm);
 
my $HOST = "localhost";
my $PORT = 4004;
 
my @open;
my %users : shared;
 
sub broadcast {
my ($id, $message) = @_;
print "$message\n";
foreach my $i (keys %users) {
if ($i != $id) {
$open[$i]->send("$message\n");
}
}
}
 
sub sign_in {
my ($conn) = @_;
 
state $id = 0;
 
threads->new(
sub {
while (1) {
$conn->send("Please enter your name: ");
$conn->recv(my $name, 1024, 0);
 
if (defined $name) {
$name = unpack('A*', $name);
 
if (exists $users{$name}) {
$conn->send("Name entered is already in use.\n");
}
elsif ($name ne '') {
$users{$id} = $name;
broadcast($id, "+++ $name arrived +++");
last;
}
}
}
}
);
 
++$id;
push @open, $conn;
}
 
my $server = IO::Socket::INET->new(
Timeout => 0,
LocalPort => $PORT,
Proto => "tcp",
LocalAddr => $HOST,
Blocking => 0,
Listen => 1,
Reuse => 1,
);
 
local $| = 1;
print "Listening on $HOST:$PORT\n";
 
while (1) {
my ($conn) = $server->accept;
 
if (defined($conn)) {
sign_in($conn);
}
 
foreach my $i (keys %users) {
 
my $conn = $open[$i];
my $message;
 
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
ualarm(500);
$conn->recv($message, 1024, 0);
ualarm(0);
};
 
if ($@ eq "alarm\n") {
next;
}
 
if (defined($message)) {
if ($message ne '') {
$message = unpack('A*', $message);
broadcast($i, "$users{$i}> $message");
}
else {
broadcast($i, "--- $users{$i} leaves ---");
delete $users{$i};
undef $open[$i];
}
}
}
 
sleep(0.1);
}</syntaxhighlight>
===Alternate with both read and write queuing===
<syntaxhighlight lang="perl">#!/usr/bin/perl
 
use strict; # http://www.rosettacode.org/wiki/Chat_server
use warnings;
use IO::Socket;
use IO::Select; # with write queueing
 
my $port = shift // 6666;
my (%nicks, @users, %data);
 
my $listen = IO::Socket::INET->new(LocalPort => $port, Listen => 9,
Reuse => 1) or die "$@ opening socket on port $port";
my $rsel = IO::Select->new($listen);
my $wsel = IO::Select->new();
print "ready on $port...\n";
 
sub to
{
my $text = pop;
for ( @_ )
{
length $data{$_}{out} or $wsel->add( $_ );
length( $data{$_}{out} .= $text ) > 1e4 and left( $_ );
}
return $text;
}
 
sub left
{
my $h = shift;
@users = grep $h != $_, @users;
if( defined( my $nick = delete $nicks{$h} ) )
{
print to @users, "$nick has left\n";
}
delete $data{$h};
$rsel->remove($h);
}
 
while( 1 )
{
my ($reads, $writes) = IO::Select->select($rsel, $wsel, undef, 5);
for my $h ( @{ $writes // [] } )
{
my $len = syswrite $h, $data{$h}{out};
$len and substr $data{$h}{out}, 0, $len, '';
length $data{$h}{out} or $wsel->remove( $h );
}
for my $h ( @{ $reads // [] } )
{
if( $h == $listen ) # new connection
{
$rsel->add( my $client = $h->accept );
$data{$client} = { h => $client, out => "enter nick: ", in => '' };
$wsel->add( $client );
}
elsif( not sysread $h, $data{$h}{in}, 4096, length $data{$h}{in} ) # closed
{
left $h;
}
elsif( exists $nicks{$h} ) # user is signed in
{
my @others = grep $h != $_, @users;
to @others, "$nicks{$h}> $&" while $data{$h}{in} =~ s/.*\n//;
}
elsif( $data{$h}{in} =~ s/^(\w+)\r?\n.*//s and
not grep lc $1 eq lc, values %nicks )
{ # user has joined
my $all = join ' ', sort values %nicks;
$nicks{$h} = $1;
push @users, $h;
print to @users, "$nicks{$h} has joined $all\n";
}
else # bad nick
{
to $h, "nick invalid or in use, enter nick: ";
$data{$h}{in} = '';
}
}
}</syntaxhighlight>
=={{header|Phix}}==
===server===
<!--<syntaxhighlight lang="phix">(notonline)-->
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\ChatServer.exw
-- ========================
--
-- translation of (qchat) chatServ.exw
--
-- Run this first, then ChatClient.exw, see also IRC_Gateway.exw
--
-- Note that I've only got a 32-bit windows eulibnet.dll, but it should not
-- be dificult to use a "real" libnet.dll/so, or something a little newer.
--</span>
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span>
<span style="color: #008080;">include</span> <span style="color: #000000;">pGUI</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">dl</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">`Download rosetta\eulibnet\ from http://phix.x10.mx/pmwiki/pmwiki.php?n=Main.Eulibnet`</span>
<span style="color: #7060A8;">assert</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">get_file_type</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"eulibnet"</span><span style="color: #0000FF;">)=</span><span style="color: #004600;">FILETYPE_DIRECTORY</span><span style="color: #0000FF;">,</span><span style="color: #000000;">dl</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">include</span> <span style="color: #000000;">eulibnet</span><span style="color: #0000FF;">/</span><span style="color: #000000;">eulibnet</span><span style="color: #0000FF;">.</span><span style="color: #000000;">ew</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">log_window</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">statusbar</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">timer</span>
<span style="color: #004080;">atom</span> <span style="color: #000000;">listconn</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">IP</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"127.0.0.1"</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">port</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"29029"</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">IPaddress</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">IP</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">":"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">port</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">MAX_MSG</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">550</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">connections</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{},</span>
<span style="color: #000000;">nicknames</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">max_log_len</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">300</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">log_to_window</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">txt</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">count</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">to_number</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">IupGetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"COUNT"</span><span style="color: #0000FF;">))</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">count</span> <span style="color: #0000FF;">></span> <span style="color: #000000;">max_log_len</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">t</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #000000;">count</span><span style="color: #0000FF;">-</span><span style="color: #000000;">max_log_len</span> <span style="color: #008080;">do</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"REMOVEITEM"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"1"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"APPENDITEM"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">txt</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"TOPITEM"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">IupGetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"COUNT"</span><span style="color: #0000FF;">))</span>
<span style="color: #7060A8;">IupUpdate</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">sequence</span> <span style="color: #000000;">args</span><span style="color: #0000FF;">={})</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">args</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span> <span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">,</span><span style="color: #000000;">args</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #7060A8;">IupSetStrAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">statusbar</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"TITLE"</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">log_to_window</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">shutDown</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Shutting down euLibnet..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">connections</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_closeconn</span><span style="color: #0000FF;">(</span><span style="color: #000000;">connections</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">])</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error closing connection!"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_closeconn</span><span style="color: #0000FF;">(</span><span style="color: #000000;">listconn</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error closing listconn!"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_shutdown</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error shutting down euLibnet!"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">sendToAll</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #000080;font-style:italic;">-- Send msg to all clients</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">connections</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">atom</span> <span style="color: #000000;">ci</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">connections</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Sending to connection %d (%s)"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">,</span><span style="color: #000000;">nicknames</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]})</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_send_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error sending to connection %d"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">})</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">mainWindow_onOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Initializing euLibnet..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_init</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error initializing euLibnet!"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Initializing driver..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_initdriver</span><span style="color: #0000FF;">(</span><span style="color: #000000;">NET_DRIVER_WSOCK_WIN</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">!=</span> <span style="color: #000000;">1</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error initializing WinSock driver!"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Opening port "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">IPaddress</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">"..."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">listconn</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_openconn</span><span style="color: #0000FF;">(</span><span style="color: #000000;">NET_DRIVER_WSOCK_WIN</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">IPaddress</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">listconn</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">NULL</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Couldn't open connection (server already running?)"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_listen</span><span style="color: #0000FF;">(</span><span style="color: #000000;">listconn</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error trying to listen to port"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Listening on port "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">IPaddress</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">timer_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*timer*/</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">conn</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_poll_listen</span><span style="color: #0000FF;">(</span><span style="color: #000000;">listconn</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">conn</span> <span style="color: #0000FF;">!=</span> <span style="color: #004600;">NULL</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">connections</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">connections</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">conn</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">nicknames</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nicknames</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">""</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"New connection open from "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">net_getpeer</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">))</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000080;font-style:italic;">-- Check for messages from clients</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">connections</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">ci</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">connections</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_query_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">></span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">ni</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">nicknames</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #000080;font-style:italic;">--Get the message</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_receive_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">MAX_MSG</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"received msg \"%s\" of length %d from %d aka %s"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">2</span><span style="color: #0000FF;">],</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">],</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">,</span><span style="color: #000000;">ni</span><span style="color: #0000FF;">})</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;"><</span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span> <span style="color: #000080;font-style:italic;">--Exit on error</span>
<span style="color: #0000FF;">{}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_ignore_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">sendToAll</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Server error: some data may be lost"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">exit</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">2</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">></span> <span style="color: #000000;">4</span> <span style="color: #008080;">and</span> <span style="color: #7060A8;">equal</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">..</span><span style="color: #000000;">3</span><span style="color: #0000FF;">],</span> <span style="color: #008000;">"/n:"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">find</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)],</span> <span style="color: #000000;">nicknames</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_send_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"/nt"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error sending to %d"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">})</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">else</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">prevname</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">ni</span>
<span style="color: #000000;">ni</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)]</span>
<span style="color: #000000;">nicknames</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">ni</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">prevname</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">sendToAll</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"/j:"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">ni</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">" has joined"</span><span style="color: #0000FF;">)</span>
<span style="color: #000080;font-style:italic;">-- send fake "has joined"s to the new joiner,
-- so that it can build a full members list (see allj)</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">n</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nicknames</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">n</span><span style="color: #0000FF;">!=</span><span style="color: #000000;">i</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"/j:"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">nicknames</span><span style="color: #0000FF;">[</span><span style="color: #000000;">n</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">" has joined"</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_send_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error sending to connection %d"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">ci</span><span style="color: #0000FF;">})</span>
<span style="color: #008080;">exit</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">else</span>
<span style="color: #000000;">sendToAll</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"/c:"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">prevname</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">" has changed name to "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">ni</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">elsif</span> <span style="color: #7060A8;">equal</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"/d"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"/l:"</span><span style="color: #0000FF;">&</span> <span style="color: #000000;">ni</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">" has left"</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">nicknames</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">..</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span>
<span style="color: #000000;">connections</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">..</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span>
<span style="color: #000000;">sendToAll</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">exit</span>
<span style="color: #000080;font-style:italic;">-- Aside: bunch of popup and file transfer stuff was here in qchat.exw,
-- all ripped out in the name of keeping this short and sweet.</span>
<span style="color: #008080;">else</span>
<span style="color: #000000;">sendToAll</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ni</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">": "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- (Add nickname to message)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_IGNORE</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">close_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">shutDown</span><span style="color: #0000FF;">()</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #7060A8;">IupOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">log_list</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupList</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"EXPAND=YES, CANFOCUS=NO, MULTIPLE=YES"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">statusbar</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Loading..."</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"EXPAND=HORIZONTAL"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">log_window</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupDialog</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">IupVbox</span><span style="color: #0000FF;">({</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #000000;">statusbar</span><span style="color: #0000FF;">}),</span>
<span style="color: #008000;">`TITLE="Chat Server", RASTERSIZE=400x400`</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetCallback</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_window</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"CLOSE_CB"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"close_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #7060A8;">IupShow</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_window</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">mainWindow_onOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">timer</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupTimer</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"timer_cb"</span><span style="color: #0000FF;">),</span> <span style="color: #000000;">500</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupMainLoop</span><span style="color: #0000FF;">()</span>
<span style="color: #7060A8;">IupClose</span><span style="color: #0000FF;">()</span>
<!--</syntaxhighlight>-->
===client===
<!--<syntaxhighlight lang="phix">(notonline)-->
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\ChatClient.exw
-- ===========================
--
-- translation of (qchat) QChat.exw
--
-- Probably best to run ChatServer.exw before this.
--</span>
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span>
<span style="color: #008080;">constant</span> <span style="color: #004080;">bool</span> <span style="color: #000000;">bViaGateway</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">false</span>
<span style="color: #000080;font-style:italic;">--constant bool bViaGateway = true -- see IRC_Gateway.exw</span>
<span style="color: #008080;">include</span> <span style="color: #000000;">pGUI</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">dl</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">`Download rosetta\eulibnet\ from http://phix.x10.mx/pmwiki/pmwiki.php?n=Main.Eulibnet`</span>
<span style="color: #7060A8;">assert</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">get_file_type</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"eulibnet"</span><span style="color: #0000FF;">)=</span><span style="color: #004600;">FILETYPE_DIRECTORY</span><span style="color: #0000FF;">,</span><span style="color: #000000;">dl</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">include</span> <span style="color: #000000;">eulibnet</span><span style="color: #0000FF;">/</span><span style="color: #000000;">eulibnet</span><span style="color: #0000FF;">.</span><span style="color: #000000;">ew</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">about</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">help</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">quit</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">nickle</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">nickname</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">login</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">memble</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">members</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">logle</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">messle</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">input</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">statusbar</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">chat_window</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">timer</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">conn</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">conFlag</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span>
<span style="color: #004080;">bool</span> <span style="color: #000000;">allj</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">true</span> <span style="color: #000080;font-style:italic;">-- (suppress initial flurry of fake "has joined" messages)</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">nick</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">""</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">conTextBack</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">""</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">IP</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"127.0.0.1"</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">port</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #000000;">bViaGateway</span><span style="color: #0000FF;">?</span><span style="color: #008000;">"29030"</span><span style="color: #0000FF;">:</span><span style="color: #008000;">"29029"</span><span style="color: #0000FF;">),</span> <span style="color: #000080;font-style:italic;">-- See IRC_Gateway.exw</span>
<span style="color: #000000;">timeout</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">20</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">IPaddress</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">IP</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">":"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">port</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">cr</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"\r\n"</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">MAX_MSG</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">80</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">about_text</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"""
Translated by Pete Lomax from qchat by Andrew/Norman (and win32lib ==&gt; pGUI).
Uses Libnet by George Foot and Chad Catlet as wrapped by Ray Smith."
"""</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">about_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupMessage</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"About"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">about_text</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">help_text</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"""
Make sure ChatServer.exw is running first...
Enter a nickname and press the Connect Button (or Return), then
enter your messages in the message area.
"""</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">help_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandln</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupMessage</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Chat client"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">help_text</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">message</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetStrAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">statusbar</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"TITLE"</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">message</span><span style="color: #0000FF;">)</span>
<span style="color: #000080;font-style:italic;">-- This should probably be cropped once it gets too long...</span>
<span style="color: #000000;">conTextBack</span> <span style="color: #0000FF;">&=</span> <span style="color: #000000;">message</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">cr</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">conTextBack</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"SCROLLTOPOS"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conTextBack</span><span style="color: #0000FF;">))</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">sendMsg</span><span style="color: #0000FF;">(</span><span style="color: #004080;">sequence</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">conFlag</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"You must be connected to do this"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_send_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error sending message \'"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">msg</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">"\'"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">shutDown</span><span style="color: #0000FF;">()</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nick</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">></span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Disconnecting from server..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_send_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"/d"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error disconnecting from server!"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Shutting down euLibnet..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">conn</span> <span style="color: #0000FF;">></span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_closeconn</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error closing connection!"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_shutdown</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error shutting down euLibnet!"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">conFlag</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">quit_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">shutDown</span><span style="color: #0000FF;">()</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_CLOSE</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">connect_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*ih*/</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">nick</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupGetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nickname</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nick</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Opening connection..."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">conn</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_openconn</span><span style="color: #0000FF;">(</span><span style="color: #000000;">NET_DRIVER_WSOCK_WIN</span><span style="color: #0000FF;">,</span> <span style="color: #004600;">NULL</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">conn</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">NULL</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Couldn't open connection."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Attempting to connect to chat server..."</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">ret</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_connect_wait_time</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">IPaddress</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">timeout</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ret</span> <span style="color: #0000FF;"><</span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error trying to establish connection."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">ret</span> <span style="color: #0000FF;">></span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Timeout trying to establish connection."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">conFlag</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">1</span>
<span style="color: #7060A8;">IupSetStrAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nickname</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"Chat Client - "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">nick</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetInt</span><span style="color: #0000FF;">({</span><span style="color: #000000;">nickname</span><span style="color: #0000FF;">,</span><span style="color: #000000;">login</span><span style="color: #0000FF;">},</span><span style="color: #008000;">"ACTIVE"</span><span style="color: #0000FF;">,</span><span style="color: #004600;">false</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">sendMsg</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"/n:"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">nick</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">timer</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"RUN"</span><span style="color: #0000FF;">,</span><span style="color: #004600;">true</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">members</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">nick</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">""</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"ACTIVE"</span><span style="color: #0000FF;">,</span><span style="color: #004600;">true</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetFocus</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">mainWindow_onOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Initializing euLibnet..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_init</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">then</span> <span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error initializing euLibnet!"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Initializing driver..."</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_initdriver</span><span style="color: #0000FF;">(</span><span style="color: #000000;">NET_DRIVER_WSOCK_WIN</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">!=</span> <span style="color: #000000;">1</span> <span style="color: #008080;">then</span>
<span style="color: #7060A8;">crash</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error initializing WinSock driver!"</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"done."</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetFocus</span><span style="color: #0000FF;">(</span><span style="color: #000000;">nickname</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">member_has_joined</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">joiner</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">mt</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupGetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">members</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">ml</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">split</span><span style="color: #0000FF;">(</span><span style="color: #000000;">mt</span><span style="color: #0000FF;">,</span><span style="color: #008000;">'\n'</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">mt</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">join</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">unique</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ml</span><span style="color: #0000FF;">,</span><span style="color: #000000;">joiner</span><span style="color: #0000FF;">)),</span><span style="color: #008000;">'\n'</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetStrAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">members</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">mt</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">procedure</span> <span style="color: #000000;">member_has_left</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">leaver</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">mt</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupGetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">members</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">ml</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">split</span><span style="color: #0000FF;">(</span><span style="color: #000000;">mt</span><span style="color: #0000FF;">,</span><span style="color: #008000;">'\n'</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">k</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">find</span><span style="color: #0000FF;">(</span><span style="color: #000000;">leaver</span><span style="color: #0000FF;">,</span><span style="color: #000000;">ml</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">ml</span><span style="color: #0000FF;">[</span><span style="color: #000000;">k</span><span style="color: #0000FF;">..</span><span style="color: #000000;">k</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span>
<span style="color: #7060A8;">IupSetStrAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">members</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">join</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ml</span><span style="color: #0000FF;">,</span><span style="color: #008000;">'\n'</span><span style="color: #0000FF;">))</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">timer_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000080;font-style:italic;">/*timer*/</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">net_query_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">)</span> <span style="color: #0000FF;">></span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span> <span style="color: #000080;font-style:italic;">--Check for message</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_receive_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">MAX_MSG</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;"><</span> <span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Error receiving message!"</span><span style="color: #0000FF;">)</span>
<span style="color: #0000FF;">{}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">net_ignore_rdm</span><span style="color: #0000FF;">(</span><span style="color: #000000;">conn</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">2</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">equal</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"/nt"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Nickname "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">nick</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">" already taken"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">nick</span> <span style="color: #0000FF;">&=</span> <span style="color: #008000;">"b"</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Trying "</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">nick</span> <span style="color: #0000FF;">&</span> <span style="color: #008000;">" instead"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Type /n:nickname to try a different one"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">sendMsg</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"/n:"</span> <span style="color: #0000FF;">&</span> <span style="color: #000000;">nick</span><span style="color: #0000FF;">)</span>
<span style="color: #000080;font-style:italic;">-- As per ChatServer, bunch of popup and file transfer stuff was
-- all ripped out in the name of keeping this short and sweet.</span>
<span style="color: #008080;">else</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)></span><span style="color: #000000;">3</span> <span style="color: #008080;">and</span> <span style="color: #7060A8;">equal</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">..</span><span style="color: #000000;">3</span><span style="color: #0000FF;">],</span> <span style="color: #008000;">"/j:"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">member_has_joined</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..</span><span style="color: #7060A8;">match</span><span style="color: #0000FF;">(</span><span style="color: #008000;">" has joined"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">])</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">allj</span> <span style="color: #008080;">then</span> <span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..$]</span>
<span style="color: #008080;">elsif</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)></span><span style="color: #000000;">3</span> <span style="color: #008080;">and</span> <span style="color: #7060A8;">equal</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">..</span><span style="color: #000000;">3</span><span style="color: #0000FF;">],</span> <span style="color: #008000;">"/l:"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">member_has_left</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..</span><span style="color: #7060A8;">match</span><span style="color: #0000FF;">(</span><span style="color: #008000;">" has left"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">])</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..$]</span>
<span style="color: #008080;">elsif</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)></span><span style="color: #000000;">3</span> <span style="color: #008080;">and</span> <span style="color: #7060A8;">equal</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">..</span><span style="color: #000000;">3</span><span style="color: #0000FF;">],</span> <span style="color: #008000;">"/c:"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span>
<span style="color: #004080;">string</span> <span style="color: #000000;">shcnts</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">" has changed name to "</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">k</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">match</span><span style="color: #0000FF;">(</span><span style="color: #000000;">shcnts</span><span style="color: #0000FF;">,</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">member_has_left</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..</span><span style="color: #000000;">k</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">])</span>
<span style="color: #000000;">member_has_joined</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">k</span><span style="color: #0000FF;">+</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">shcnts</span><span style="color: #0000FF;">)-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">..$])</span>
<span style="color: #000000;">msg</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">msg</span><span style="color: #0000FF;">[</span><span style="color: #000000;">4</span><span style="color: #0000FF;">..$]</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">message</span><span style="color: #0000FF;">(</span><span style="color: #000000;">msg</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">allj</span> <span style="color: #0000FF;">=</span> <span style="color: #004600;">false</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">key_cb</span><span style="color: #0000FF;">(</span><span style="color: #004080;">Ihandle</span> <span style="color: #000000;">ih</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">atom</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_ESC</span> <span style="color: #008080;">then</span> <span style="color: #000000;">shutDown</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">return</span> <span style="color: #004600;">IUP_CLOSE</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #004600;">K_F1</span> <span style="color: #008080;">then</span> <span style="color: #008080;">return</span> <span style="color: #000000;">help_cb</span><span style="color: #0000FF;">(</span><span style="color: #004600;">NULL</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">c</span><span style="color: #0000FF;">=</span><span style="color: #008000;">'\r'</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ih</span><span style="color: #0000FF;">=</span><span style="color: #000000;">input</span> <span style="color: #008080;">then</span>
<span style="color: #000000;">sendMsg</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">IupGetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">))</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"VALUE"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">""</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">ih</span><span style="color: #0000FF;">=</span><span style="color: #000000;">nickname</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">connect_cb</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ih</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">return</span> <span style="color: #004600;">IUP_DEFAULT</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #7060A8;">IupOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">about</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupButton</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"About"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"about_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #000000;">help</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupButton</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Help"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"help_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #000000;">quit</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupButton</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Exit"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"quit_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #000000;">nickle</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Nickname"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">nickname</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupText</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"EXPAND=HORIZONTAL"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">login</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupButton</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Connect"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"connect_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #000000;">memble</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Members online"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">members</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupText</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"EXPAND=VERTICAL, CANFOCUS=NO, MULTILINE=YES, SIZE=70x"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">logle</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Incoming text"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">log_list</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupText</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"EXPAND=YES, CANFOCUS=NO, MULTILINE=YES"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">messle</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Message"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">input</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupText</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"EXPAND=HORIZONTAL"</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetCallback</span><span style="color: #0000FF;">({</span><span style="color: #000000;">nickname</span><span style="color: #0000FF;">,</span><span style="color: #000000;">input</span><span style="color: #0000FF;">},</span><span style="color: #008000;">"KEY_CB"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"key_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #7060A8;">IupSetAttribute</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"CUEBANNER"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"This Is Where You Type"</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"NC"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">72</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- Max 72 characters allowed</span>
<span style="color: #7060A8;">IupSetInt</span><span style="color: #0000FF;">(</span><span style="color: #000000;">input</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"ACTIVE"</span><span style="color: #0000FF;">,</span><span style="color: #004600;">false</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">statusbar</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"Loading..."</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"EXPAND=HORIZONTAL"</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">chat_window</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupDialog</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">IupVbox</span><span style="color: #0000FF;">({</span><span style="color: #7060A8;">IupHbox</span><span style="color: #0000FF;">({</span><span style="color: #000000;">about</span><span style="color: #0000FF;">,</span><span style="color: #000000;">help</span><span style="color: #0000FF;">,</span><span style="color: #000000;">quit</span><span style="color: #0000FF;">,</span><span style="color: #000000;">nickle</span><span style="color: #0000FF;">,</span><span style="color: #000000;">nickname</span><span style="color: #0000FF;">,</span><span style="color: #000000;">login</span><span style="color: #0000FF;">},</span>
<span style="color: #008000;">"NORMALIZESIZE=VERTICAL,GAP=10,MARGIN=5x5"</span><span style="color: #0000FF;">),</span>
<span style="color: #7060A8;">IupHbox</span><span style="color: #0000FF;">({</span><span style="color: #7060A8;">IupVbox</span><span style="color: #0000FF;">({</span><span style="color: #000000;">memble</span><span style="color: #0000FF;">,</span><span style="color: #000000;">members</span><span style="color: #0000FF;">}),</span>
<span style="color: #7060A8;">IupVbox</span><span style="color: #0000FF;">({</span><span style="color: #000000;">logle</span><span style="color: #0000FF;">,</span><span style="color: #000000;">log_list</span><span style="color: #0000FF;">})},</span>
<span style="color: #008000;">"NGAP=10,NMARGIN=5x5"</span><span style="color: #0000FF;">),</span>
<span style="color: #7060A8;">IupHbox</span><span style="color: #0000FF;">({</span><span style="color: #000000;">messle</span><span style="color: #0000FF;">,</span><span style="color: #000000;">input</span><span style="color: #0000FF;">},</span><span style="color: #008000;">"GAP=10,MARGIN=5x5"</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">statusbar</span><span style="color: #0000FF;">}),</span>
<span style="color: #008000;">`TITLE="Chat Client", RASTERSIZE=400x400`</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupSetCallback</span><span style="color: #0000FF;">(</span><span style="color: #000000;">chat_window</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"CLOSE_CB"</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"quit_cb"</span><span style="color: #0000FF;">))</span>
<span style="color: #7060A8;">IupSetAttributeHandle</span><span style="color: #0000FF;">(</span><span style="color: #004600;">NULL</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"PARENTDIALOG"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">chat_window</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupShow</span><span style="color: #0000FF;">(</span><span style="color: #000000;">chat_window</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">mainWindow_onOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">timer</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupTimer</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">Icallback</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"timer_cb"</span><span style="color: #0000FF;">),</span> <span style="color: #000000;">500</span><span style="color: #0000FF;">,</span> <span style="color: #004600;">false</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupMainLoop</span><span style="color: #0000FF;">()</span>
<span style="color: #7060A8;">IupClose</span><span style="color: #0000FF;">()</span>
<!--</syntaxhighlight>-->
=={{header|PicoLisp}}==
<langsyntaxhighlight PicoLisplang="picolisp">#!/usr/bin/picolisp /usr/lib/picolisp/lib.l
 
(de chat Lst
Line 34 ⟶ 3,142:
(tell 'chat "--- " *Name " left ---")
(bye) ) ) )
(wait)</langsyntaxhighlight>
After starting the above script, connect to the chat server from two terminals:
<pre> Terminal 1 | Terminal 2
Line 66 ⟶ 3,174:
| Connection closed.
| $</pre>
=={{header|Prolog}}==
Works with Swi-Prolog as of Jan 2019.
 
This version will load the server automatically on port 5000, adapt to your needs.
<syntaxhighlight lang="prolog">:- initialization chat_server(5000).
 
chat_server(Port) :-
tcp_socket(Socket),
tcp_bind(Socket, Port),
tcp_listen(Socket, 5),
tcp_open_socket(Socket, AcceptFd, _),
dispatch(AcceptFd).
 
dispatch(AcceptFd) :-
tcp_accept(AcceptFd, Socket, _),
thread_create(process_client(Socket, _), _, [detached(true)]),
dispatch(AcceptFd).
 
process_client(Socket, _) :-
setup_call_cleanup(
tcp_open_socket(Socket, Str),
handle_connection(Str),
close(Str)).
 
% a connection was made, get the username and add the streams so the
% client can be broadcast to.
handle_connection(Str) :-
send_msg(Str, msg_welcome, []),
repeat,
send_msg(Str, msg_username, []),
read_line_to_string(Str, Name),
connect_user(Name, Str), !.
 
% connections are stored here
:- dynamic(connected/2).
 
connect_user(Name, Str) :-
connected(Name, _),
send_msg(Str, msg_username_taken, []),
fail.
connect_user(Name, Str) :-
\+ connected(Name, _),
send_msg(Str, msg_welcome_name, Name),
 
% make sure that the connection is removed when the client leaves.
setup_call_cleanup(
assert(connected(Name, Str)),
(
broadcast(Name, msg_joined, Name),
chat_loop(Name, Str), !,
broadcast(Name, msg_left, Name)
),
retractall(connected(Name, _))
).
 
% wait for a line to be sent then broadcast to the rest of the clients
% finish this goal when the client disconnects (end of stream)
chat_loop(Name, Str) :-
read_line_to_string(Str, S),
dif(S, end_of_file),
broadcast(Name, msg_by_user, [Name, S]),
chat_loop(Name, Str).
chat_loop(_, Str) :- at_end_of_stream(Str).
 
% send a message to all connected clients except Name (the sender)
broadcast(Name, Msg, Params) :-
forall(
(connected(N, Str), dif(N, Name)),
(send_msg(Str, Msg, Params), send_msg(Str, msg_new_line, []))
).
 
send_msg(St, MsgConst, Params) :-
call(MsgConst, Msg),
format(St, Msg, Params),
flush_output(St).
 
% constants for the various message types that are sent
msg_welcome('Welcome to Chatalot\n\r').
msg_username('Please enter your nickname: ').
msg_welcome_name('Welcome ~p\n\r').
msg_joined(' -- "~w" has joined the chat --').
msg_left(' -- "~w" has left the chat. --').
msg_username_taken('That username is already taken, choose another\n\r').
msg_new_line('\n\r').
msg_by_user('~w> ~w').</syntaxhighlight>
=={{header|Python}}==
<syntaxhighlight lang="python">#!/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</syntaxhighlight>
=={{header|R}}==
This implementation relies on the new server socket connection type introduced in R 4.0.0.
<syntaxhighlight lang="r">
chat_loop <- function(server, sockets, delay = 0.5) {
repeat {
Sys.sleep(delay) # Saves CPU resources
 
## Exhausts queue each iteration
while (in_queue(server))
sockets <- new_socket_entry(server, sockets)
 
## Update which sockets have sent messages
sockets <- check_messages(sockets)
 
## No sockets, nothing to do
if (nrow(sockets) == 0)
next
 
## No new messages, nothing to do
if (all(!sockets$message_ready))
next
 
 
sockets <- read_messages(sockets) # Messages are stored until sent
sockets <- drop_dead(sockets) # Dead = ready to read, but no data
 
## In case all sockets were dropped
if (nrow(sockets) == 0)
next
 
sockets <- update_nicknames(sockets)
sockets <- send_messages(sockets) # Only to users with nicknames
}
}
 
check_messages <- function(sockets) {
if (nrow(sockets) != 0)
sockets$message_ready <- socketSelect(sockets$conn, timeout = 0)
 
sockets
}
 
drop_dead <- function(sockets) {
lapply(with(sockets, conn[!alive]), close)
dropped <- with(sockets, nickname[nickname_exists(sockets) & !alive])
sockets <- sockets[sockets$alive, ]
 
if (length(dropped) != 0) {
send_named(sockets, paste0(dropped, " has disconnected."))
}
 
sockets
}
 
in_queue <- function(server) socketSelect(list(server), timeout = 0)
is_valid_name <- function(nicks) gsub("[A-Za-z0-9]*", "", nicks) == ""
message_exists <- function(sockets) !is.na(sockets$message)
 
new_row <- function(df) {
df[nrow(df) + 1, ] <- NA
df
}
 
new_socket_entry <- function(server, sockets) {
sockets <- new_row(sockets)
n <- nrow(sockets)
within(sockets, {
conn[[n]] <- new_user(server)
alive[n] <- TRUE
message_ready[n] <- FALSE
})
}
 
new_user <- function(server) {
conn <- socketAccept(server)
writeLines("Hello! Please enter a nickname.", conn)
conn
}
 
nickname_exists <- function(sockets) !is.na(sockets$nickname)
 
read_messages <- function(sockets) {
if (all(!sockets$message_ready))
return(sockets)
 
msgs <- lapply(with(sockets, conn[message_ready]), readLines, n = 1)
empty_msgs <- sapply(msgs, identical, character(0))
sockets <- within(sockets, alive[message_ready & empty_msgs] <- FALSE)
msgs <- unlist(ifelse(empty_msgs, NA, msgs))
within(sockets, message[message_ready] <- msgs)
}
 
send_messages <- function(sockets) {
named_message <- message_exists(sockets) & nickname_exists(sockets)
 
if (all(!named_message))
return(sockets)
 
rows <- which(named_message)
socksub <- sockets[rows, ]
time <- format(Sys.time(), "[%H:%M:%S] ")
with(socksub, send_named(sockets, paste0(time, nickname, ": ", message)))
within(sockets, message[rows] <- NA)
}
 
send_named <- function(sockets, msg) {
has_nickname <- nickname_exists(sockets)
invisible(lapply(sockets$conn[has_nickname], writeLines, text = msg))
}
 
start_chat_server <- function(port = 50525) {
server <- serverSocket(port) # Start listening
on.exit(closeAllConnections()) # Cleanup connections
 
## All socket data is stored and passed using this object
sockets <- data.frame(conn = I(list()), nickname = character(),
message = character(), alive = logical(),
message_ready = logical())
 
## Main event loop
chat_loop(server, sockets)
}
 
update_nicknames <- function(sockets) {
sent_nickname <- message_exists(sockets) & !nickname_exists(sockets)
nickname_valid <- is_valid_name(sockets$message)
 
if (all(!sent_nickname))
return(sockets)
 
is_taken <- with(sockets, (tolower(message) %in% tolower(sockets$nickname)) &
!is.na(message))
sent_ok <- sent_nickname & nickname_valid & !is_taken
sockets <- within(sockets, {
nickname[sent_ok] <- message[sent_ok]
message[sent_nickname] <- NA
lapply(conn[sent_nickname & !nickname_valid], writeLines,
text = "Alphanumeric characters only. Try again.")
lapply(conn[is_taken], writeLines,
text = "Name already taken. Try again.")
})
 
if (any(sent_ok))
send_named(sockets, paste0(sockets$nickname[sent_ok], " has connected."))
 
sockets
}
 
start_chat_server()</syntaxhighlight>
=={{header|Racket}}==
 
This is a very basic chat server, but it does everything that is needed for this task.
 
<syntaxhighlight lang="racket">
#lang racket
 
(define outs (list (current-output-port)))
(define ((tell-all who o) line)
(for ([c outs] #:unless (eq? o c)) (displayln (~a who ": " line) c)))
 
(define ((client i o))
(define nick (begin (display "Nick: " o) (read-line i)))
(define tell (tell-all nick o))
(let loop ([line "(joined)"])
(if (eof-object? line)
(begin (tell "(left)") (set! outs (remq o outs)) (close-output-port o))
(begin (tell line) (loop (read-line i))))))
 
(define (chat-server listener)
(define-values [i o] (tcp-accept listener))
(for ([p (list i o)]) (file-stream-buffer-mode p 'none))
(thread (client i o)) (set! outs (cons o outs)) (chat-server listener))
 
(void (thread (λ() (chat-server (tcp-listen 12321)))))
((client (current-input-port) (current-output-port)))
</syntaxhighlight>
=={{header|Raku}}==
(formerly Perl 6)
 
<div style="display:inline-block">{{trans|Python}}</div> (or at least started out that way)
{{works with|Rakudo|2016.07}}
<syntaxhighlight lang="raku" line>react {
my %connections;
whenever IO::Socket::Async.listen('localhost', 4004) -> $conn {
my $name;
$conn.print: "Please enter your name: ";
whenever $conn.Supply.lines -> $message {
if !$name {
if %connections{$message} {
$conn.print: "Name already taken, choose another one: ";
}
else {
$name = $message;
%connections{$name} = $conn;
broadcast "+++ %s arrived +++", $name;
}
}
else {
broadcast "%s> %s", $name, $message;
}
LAST {
broadcast "--- %s left ---", $name;
%connections{$name}:delete;
$conn.close ;
}
QUIT {
default {
say "oh no, $_";
}
}
}
}
sub broadcast ($format, $from, *@message) {
my $text = sprintf $format, $from, |@message;
say $text;
for %connections.kv -> $name, $conn {
$conn.print: "$text\n" if $name ne $from;
}
}
}</syntaxhighlight>
 
Notes:
* It operates asynchronously (using <tt>IO::Socket::Async</tt>), so a slow connection to one client won't affect other clients.
* It accepts messages encoded in UTF-8.
* It tokenizes the message streams at newline boundaries (using the <tt>Supply.lines</tt> method), which I think makes the most sense for a chat application.
=={{header|Ruby}}==
<syntaxhighlight lang="ruby">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
</syntaxhighlight>
=={{header|Rust}}==
<syntaxhighlight lang="rust">
use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::io::BufReader;
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, RwLock};
use std::thread;
 
type Username = String;
 
/// Sends a message to all clients except the sending client.
fn broadcast_message(
user: &str,
clients: &mut HashMap<String, TcpStream>,
message: &str,
) -> io::Result<()> {
for (client, stream) in clients.iter_mut() {
if client != user {
writeln!(stream, "{}", message)?;
}
}
 
Ok(())
}
 
fn chat_loop(listener: &TcpListener) -> io::Result<()> {
let local_clients: Arc<RwLock<HashMap<Username, TcpStream>>> =
Arc::new(RwLock::new(HashMap::new()));
 
println!("Accepting connections on {}", listener.local_addr()?.port());
 
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let client_clients = Arc::clone(&local_clients);
thread::spawn(move || -> io::Result<()> {
let mut reader = BufReader::new(stream.try_clone()?);
let mut writer = stream;
 
let mut name = String::new();
loop {
write!(writer, "Please enter a username: ")?;
reader.read_line(&mut name)?;
name = name.trim().to_owned();
 
let clients = client_clients.read().unwrap();
if !clients.contains_key(&name) {
writeln!(writer, "Welcome, {}!", &name)?;
break;
}
 
writeln!(writer, "That username is taken.")?;
name.clear();
}
 
{
let mut clients = client_clients.write().unwrap();
clients.insert(name.clone(), writer);
broadcast_message(
&name,
&mut *clients,
&format!("{} has joined the chat room.", &name),
)?;
}
 
for line in reader.lines() {
let mut clients = client_clients.write().unwrap();
broadcast_message(&name, &mut *clients, &format!("{}: {}", &name, line?))?;
}
 
{
let mut clients = client_clients.write().unwrap();
clients.remove(&name);
broadcast_message(
&name,
&mut *clients,
&format!("{} has left the chat room.", &name),
)?;
}
 
Ok(())
});
}
Err(e) => {
println!("Connection failed: {}", e);
}
}
}
 
Ok(())
}
 
fn main() {
let listener = TcpListener::bind(("localhost", 7000)).unwrap();
chat_loop(&listener).unwrap();
}
</syntaxhighlight>
=={{header|Tcl}}==
{{works with|Tcl|8.6}}
<langsyntaxhighlight lang="tcl">package require Tcl 8.6
 
# Write a message to everyone except the sender of the message
Line 129 ⟶ 3,798:
socket -server {coroutine c[incr count] chat} 4004
set ::cmap {}; # Dictionary mapping nicks to channels
vwait forever; # Run event loop</langsyntaxhighlight>
 
=={{header|Wren}}==
An embedded solution using a C host since Wren has no built in support for either networking or multi-threading.
 
The following is based on the C code [https://github.com/yorickdewid/Chat-Server/blob/master/chat_server.c here] and runs fine on my Ubuntu 20.04 box.
 
As Wren's VM is single threaded we create separate VMs to service each potential client connection (limited to 10) which run in their own thread. As the only way for the VMs to share mutable state is to use global variables within the host, synchronization is needed when accessing such variables.
<syntaxhighlight lang="wren">/* Chat_server.wren */
 
class Clients {
foreign static max
foreign static count
foreign static isActive(vmi)
foreign static connfd(vmi)
foreign static uid(vmi)
foreign static name(vmi)
foreign static setName(vmi, s)
foreign static printAddr(vmi)
foreign static delete(vmi)
}
 
class Mutex {
foreign static clientsLock()
foreign static clientsUnlock()
 
foreign static topicLock()
foreign static topicUnlock()
}
 
class Chat {
// send message to all clients but the sender
static sendMessage(s, uid) {
Mutex.clientsLock()
for (i in 0...Clients.max) {
if (Clients.isActive(i) && Clients.uid(i) != uid) {
if (write(Clients.connfd(i), s, s.bytes.count) < 0) {
System.print("Write to descriptor %(Clients.connfd(i)) failed.")
break
}
}
}
Mutex.clientsUnlock()
}
 
// send message to all clients
static sendMessageAll(s) {
Mutex.clientsLock()
for (i in 0...Clients.max) {
if (Clients.isActive(i)) {
if (write(Clients.connfd(i), s, s.bytes.count) < 0) {
System.print("Write to descriptor %(Clients.connfd(i)) failed.")
break
}
}
}
Mutex.clientsUnlock()
}
 
// send message to sender
static sendMessageSelf(s, connfd) {
if (write(connfd, s, s.bytes.count) < 0) {
Fiber.abort("Write to descriptor %(connfd) failed.")
}
}
 
// send message to client
static sendMessageClient(s, uid) {
Mutex.clientsLock()
for (i in 0...Clients.max) {
if (Clients.isActive(i) && Clients.uid(i) == uid) {
if (write(Clients.connfd(i), s, s.bytes.count) < 0) {
System.print("Write to descriptor %(Clients.connfd(i)) failed.")
break
}
}
}
Mutex.clientsUnlock()
}
 
// send list of active clients
static sendActiveClients(connfd) {
Mutex.clientsLock()
for (i in 0...Clients.max) {
if (Clients.isActive(i)) {
var s = "<< [%(Clients.uid(i))] %(Clients.name(i))\r\n"
sendMessageSelf(s, connfd)
}
}
Mutex.clientsUnlock()
}
 
// handle all communication with the client
static handleClient(vmi) {
if (!Clients.isActive(vmi)) {
Fiber.abort("The client handled by VM[%(vmi)] is inactive.")
}
var connfd = Clients.connfd(vmi)
var uid = Clients.uid(vmi)
var name = Clients.name(vmi)
System.write("<< accept ")
Clients.printAddr(vmi)
System.print(" referenced by %(uid)")
var buffOut = "<< %(name) has joined\r\n"
sendMessageAll(buffOut)
Mutex.topicLock()
if (topic != "") {
buffOut = "<< topic: %(topic)\r\n"
sendMessageSelf(buffOut, connfd)
}
Mutex.topicUnlock()
sendMessageSelf("<< see /help for assistance\r\n", connfd)
 
/* receive input from client */
var buffIn = ""
while ((buffIn = read(connfd, bufferSize/2 - 1)) && buffIn.bytes.count > 0) {
buffOut = ""
buffIn = buffIn.trimEnd("\r\n")
/* ignore empty buffer */
if (buffIn == "") continue
 
/* special options */
if (buffIn[0] == "/") {
var split = buffIn.split(" ")
var command = split[0]
if (command == "/quit") {
break
} else if (command == "/ping") {
sendMessageSelf("<< pong\r\n", connfd)
} else if (command == "/topic") {
if (split.count > 0) {
Mutex.topicLock()
topic = split[1..-1].join(" ")
Mutex.topicUnlock()
buffOut = "<< topic changed to: %(topic)\r\n"
sendMessageAll(buffOut)
} else {
sendMessageSelf("<< message cannot be null\r\n", connfd)
}
} else if (command == "/nick") {
if (split.count > 0) {
var newName = split[1..-1].join(" ")
buffOut = "<< %(name) is now known as %(newName)\r\n"
Clients.setName(vmi, newName)
name = newName
sendMessageAll(buffOut)
} else {
sendMessageSelf("<< name cannot be null\r\n", connfd)
}
} else if (command == "/msg") {
if (split.count > 0) {
var toUid = Num.fromString(split[1])
if (split.count > 1) {
buffOut = "[PM][%(name)] "
buffOut = buffOut + split[2..-1].join(" ") + "\r\n"
sendMessageClient(buffOut, toUid)
} else {
sendMessageSelf("<< message cannot be null\r\n", connfd)
}
} else {
sendMessageSelf("<< reference cannot be null\r\n", connfd)
}
} else if (command == "/list") {
buffOut = "<< clients %(Clients.count)\r\n"
sendMessageSelf(buffOut, connfd)
sendActiveClients(connfd)
} else if (command == "/help") {
buffOut = ""
buffOut = buffOut + "<< /quit Quit chatroom\r\n"
buffOut = buffOut + "<< /ping Server test\r\n"
buffOut = buffOut + "<< /topic <message> Set chat topic\r\n"
buffOut = buffOut + "<< /nick <name> Change nickname\r\n"
buffOut = buffOut + "<< /msg <reference> <message> Send private message\r\n"
buffOut = buffOut + "<< /list Show active clients\r\n"
buffOut = buffOut + "<< /help Show help\r\n"
sendMessageSelf(buffOut, connfd)
} else {
sendMessageSelf("<< unknown command\r\n", connfd)
}
} else {
/* send message */
buffOut = "[%(name)] %(buffIn)\r\n"
sendMessage(buffOut, uid)
}
}
 
/* close connection */
buffOut = "<< [%(name)] has left\r\n"
sendMessageAll(buffOut)
close(connfd)
 
/* delete client from queue and yield thread (from C side) */
System.write("<< quit ")
Clients.printAddr(vmi)
System.print(" referenced by %(uid)")
Clients.delete(vmi)
}
 
foreign static topic
foreign static topic=(s)
 
foreign static bufferSize
foreign static write(connfd, buf, count)
foreign static read(connfd, count)
foreign static close(connfd)
}</syntaxhighlight>
<br>
We now embed this in the following C program, build and run it to start the server. To end the server, just press control-C. For testing purposes, clients can use telnet from separate terminals to connect to the server on port 5000.
<syntaxhighlight lang="c">/* gcc Chat_server.c -o Chat_server -lpthread -lwren -lm */
 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <signal.h>
#include <wren.h>
 
#define MAX_CLIENTS 10
#define BUFFER_SZ 2048
 
static _Atomic unsigned int cli_count = 0;
static int listenfd = 0, uid = 10;
char *script = NULL;
 
/* Client structure */
typedef struct {
struct sockaddr_in addr; /* Client remote address */
int connfd; /* Connection file descriptor */
int uid; /* Client unique identifier */
int vmi; /* The index of the VM which handles this client */
char name[32]; /* Client name */
} client_t;
 
client_t *clients[MAX_CLIENTS];
 
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;
 
static char topic[BUFFER_SZ/2];
 
pthread_mutex_t topic_mutex = PTHREAD_MUTEX_INITIALIZER;
 
WrenVM* vms[MAX_CLIENTS]; // array of VMs
 
/* add client to queue */
void queue_add(client_t *cl){
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; ++i) {
if (!clients[i]) {
cl->vmi = i;
clients[i] = cl;
break;
}
}
pthread_mutex_unlock(&clients_mutex);
}
 
/* Delete client from queue */
void queue_delete(int uid){
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; ++i) {
if (clients[i]) {
if (clients[i]->uid == uid) {
clients[i] = NULL;
break;
}
}
}
pthread_mutex_unlock(&clients_mutex);
}
 
/* print ip address */
void print_client_addr(struct sockaddr_in addr){
printf("%d.%d.%d.%d",
addr.sin_addr.s_addr & 0xff,
(addr.sin_addr.s_addr & 0xff00) >> 8,
(addr.sin_addr.s_addr & 0xff0000) >> 16,
(addr.sin_addr.s_addr & 0xff000000) >> 24);
}
 
/* enable Wren to handle all client communication */
void *handle_client(void *arg) {
client_t *cli = (client_t *)arg;
cli_count++;
int vmi = cli->vmi;
WrenHandle *callHandle = wrenMakeCallHandle(vms[vmi], "handleClient(_)");
wrenEnsureSlots(vms[vmi], 2);
wrenGetVariable(vms[vmi], "main", "Chat", 0);
wrenSetSlotDouble(vms[vmi], 1, (double)vmi);
wrenCall(vms[vmi], callHandle);
}
 
/* C <= Wren interface functions */
 
void C_max(WrenVM* vm) {
wrenSetSlotDouble(vm, 0, (double)MAX_CLIENTS);
}
 
void C_count(WrenVM* vm) {
wrenSetSlotDouble(vm, 0, (double)cli_count);
}
 
void C_isActive(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
bool res = clients[vmi] != NULL;
wrenSetSlotBool(vm, 0, res);
}
 
void C_connfd(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, (double)clients[vmi]->connfd);
}
 
void C_uid(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
wrenSetSlotDouble(vm, 0, (double)clients[vmi]->uid);
}
 
void C_name(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
wrenSetSlotString(vm, 0, (const char *)clients[vmi]->name);
}
 
void C_setName(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
const char *name = wrenGetSlotString(vm, 2);
size_t size = sizeof(clients[vmi]->name);
strncpy(clients[vmi]->name, name, size);
clients[vmi]->name[size-1] = '\0';
}
 
void C_clientsLock(WrenVM* vm) {
pthread_mutex_lock(&clients_mutex);
}
 
void C_clientsUnlock(WrenVM* vm) {
pthread_mutex_unlock(&clients_mutex);
}
 
void C_topicLock(WrenVM* vm) {
pthread_mutex_lock(&topic_mutex);
}
 
void C_topicUnlock(WrenVM* vm) {
pthread_mutex_unlock(&topic_mutex);
}
 
void C_printAddr(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
print_client_addr(clients[vmi]->addr);
}
 
void C_topic(WrenVM* vm) {
wrenSetSlotString(vm, 0, (const char *)topic);
}
 
void C_setTopic(WrenVM* vm) {
const char *t = wrenGetSlotString(vm, 1);
strncpy(topic, t, sizeof(topic));
topic[sizeof(topic)-1] = '\0';
}
 
void C_bufferSize(WrenVM* vm) {
wrenSetSlotDouble(vm, 0, (double)BUFFER_SZ);
}
 
void C_write(WrenVM* vm) {
int fd = (int)wrenGetSlotDouble(vm, 1);
const void *buf = (const void *)wrenGetSlotString(vm, 2);
size_t count = (size_t)wrenGetSlotDouble(vm, 3);
ssize_t res = write(fd, buf, count);
wrenSetSlotDouble(vm, 0, (double)res);
}
 
void C_read(WrenVM* vm) {
char buf[BUFFER_SZ / 2];
int fd = (int)wrenGetSlotDouble(vm, 1);
size_t count = (size_t)wrenGetSlotDouble(vm, 2);
ssize_t rlen = read(fd, buf, count);
buf[rlen] = '\0';
wrenSetSlotString(vm, 0, (const char *)buf);
}
 
void C_close(WrenVM* vm) {
int connfd = (int)wrenGetSlotDouble(vm, 1);
close(connfd);
}
 
void C_delete(WrenVM* vm) {
int vmi = (int)wrenGetSlotDouble(vm, 1);
client_t *cli = clients[vmi];
queue_delete(cli->uid);
free(cli);
cli_count--;
pthread_detach(pthread_self());
}
 
WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature) {
if (strcmp(module, "main") == 0) {
if (strcmp(className, "Clients") == 0) {
if (isStatic && strcmp(signature, "max") == 0) return C_max;
if (isStatic && strcmp(signature, "count") == 0) return C_count;
if (isStatic && strcmp(signature, "isActive(_)") == 0) return C_isActive;
if (isStatic && strcmp(signature, "connfd(_)") == 0) return C_connfd;
if (isStatic && strcmp(signature, "uid(_)") == 0) return C_uid;
if (isStatic && strcmp(signature, "name(_)") == 0) return C_name;
if (isStatic && strcmp(signature, "setName(_,_)") == 0) return C_setName;
if (isStatic && strcmp(signature, "printAddr(_)") == 0) return C_printAddr;
if (isStatic && strcmp(signature, "delete(_)") == 0) return C_delete;
} else if (strcmp(className, "Mutex") == 0) {
if (isStatic && strcmp(signature, "clientsLock()") == 0) return C_clientsLock;
if (isStatic && strcmp(signature, "clientsUnlock()") == 0) return C_clientsUnlock;
if (isStatic && strcmp(signature, "topicLock()") == 0) return C_topicLock;
if (isStatic && strcmp(signature, "topicUnlock()") == 0) return C_topicUnlock;
} else if (strcmp(className, "Chat") == 0) {
if (isStatic && strcmp(signature, "topic") == 0) return C_topic;
if (isStatic && strcmp(signature, "topic=(_)") == 0) return C_setTopic;
if (isStatic && strcmp(signature, "bufferSize") == 0) return C_bufferSize;
if (isStatic && strcmp(signature, "write(_,_,_)") == 0) return C_write;
if (isStatic && strcmp(signature, "read(_,_)") == 0) return C_read;
if (isStatic && strcmp(signature, "close(_)") == 0) return C_close;
}
}
return NULL;
}
 
static void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}
 
void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
switch (errorType) {
case WREN_ERROR_COMPILE:
printf("[%s line %d] [Error] %s\n", module, line, msg);
break;
case WREN_ERROR_STACK_TRACE:
printf("[%s line %d] in %s\n", module, line, msg);
break;
case WREN_ERROR_RUNTIME:
printf("[Runtime Error] %s\n", msg);
break;
}
}
 
char *readFile(const char *fileName) {
FILE *f = fopen(fileName, "r");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
rewind(f);
char *script = malloc(fsize + 1);
fread(script, 1, fsize, f);
fclose(f);
script[fsize] = 0;
return script;
}
 
void catch_ctrl_c(int sig) {
/* clean up and exit */
for (int i = 0; i < MAX_CLIENTS; ++i) {
wrenFreeVM(vms[i]);
if (clients[i]) {
close(clients[i]->connfd);
free(clients[i]);
}
}
close(listenfd);
free(script);
printf("\n<[ SERVER ENDED ]>\n");
exit(EXIT_SUCCESS);
}
 
int main(int argc, char **argv) {
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
config.bindForeignMethodFn = &bindForeignMethod;
const char* module = "main";
const char* fileName = "Chat_server.wren";
script = readFile(fileName);
 
/* config the VMs and interpret the script */
for (int i = 0; i < MAX_CLIENTS; ++i) {
vms[i] = wrenNewVM(&config);
wrenInterpret(vms[i], module, script);
}
 
/* prepare to start the server */
int connfd = 0;
struct sockaddr_in serv_addr;
struct sockaddr_in cli_addr;
pthread_t tid;
 
/* socket settings */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(5000);
 
/* ignore pipe signals */
signal(SIGPIPE, SIG_IGN);
 
/* catch ctrl-c being pressed */
signal(SIGINT, catch_ctrl_c);
 
/* bind */
if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Socket binding failed");
return EXIT_FAILURE;
}
 
/* listen */
if (listen(listenfd, 10) < 0) {
perror("Socket listening failed");
return EXIT_FAILURE;
}
 
printf("<[ SERVER STARTED ]>\n");
 
/* accept clients */
while (1) {
socklen_t clilen = sizeof(cli_addr);
connfd = accept(listenfd, (struct sockaddr*)&cli_addr, &clilen);
 
/* check if max clients is reached */
if ((cli_count + 1) == MAX_CLIENTS) {
printf("<< max clients reached\n");
printf("<< reject ");
print_client_addr(cli_addr);
printf("\n");
close(connfd);
continue;
}
 
/* client settings */
client_t *cli = (client_t *)malloc(sizeof(client_t));
cli->addr = cli_addr;
cli->connfd = connfd;
cli->uid = uid++;
sprintf(cli->name, "%d", cli->uid);
 
/* add client to the queue and fork thread */
queue_add(cli);
pthread_create(&tid, NULL, &handle_client, (void*)cli);
 
/* reduce CPU usage */
sleep(1);
}
 
return 0;
}</syntaxhighlight>
 
=={{header|zkl}}==
{{trans|Python}}
On my Linux box, telnet seems to only want to connect to port 23.
<syntaxhighlight lang="zkl">const PORT=23;
var users=Dictionary(); // ( handle:socket, ...)
pipe:=Thread.Pipe(); // how server tells thread to connect to user
 
fcn accept(pipe){ // a thread waiting for the server to send a socket
while(socket:=pipe.read()){
println("Somebody is connecting ...");
socket.read(); // telnet stuff
while(True){ // get credentials
reg name;
socket.write("Your handle: "); // bottle neck
try{ name = socket.read().text.strip() } catch(IOError){ continue }
if(users.holds(name)) socket.write("Handle is already in use.\n");
else if(name){
users[name] = socket;
chat.launch(name,socket); // thread
broadcast(name, "+++ %s arrived +++".fmt(name));
break; // wait for next connection
}
}//while
}//while
}.launch(pipe); // thread
fcn chat(name,socket){ // a thread, one per user
try{
socket.write("^D to disconnect\n");
while(True){
message:=socket.read().text.strip();
if(message=="\xff\xec") break; // ^D to disconnect.
broadcast(name, "%s> %s".fmt(name,message));
}
}catch{} // eg socket pukes
users.del(name); socket.close();
broadcast(name, "--- %s leaves ---".fmt(name));
}
 
// Send a message to all users from the given name.
fcn broadcast(name, message){ // called from user thread
println(message); // log message to server console
users.pump(Void,'wrap([(toName,socket)]){
if(toName != name) try{ socket.write(message + "\n") } catch(IOError){}
});
}
 
// Set up the server socket.
server:=Network.TCPServerSocket.open(PORT);
println("Listening on %s:%s".fmt(server.hostname,server.port));
server.listen(pipe); // Main event loop </syntaxhighlight>
{{out}}
Start the server:
<pre>
$ sudo ../../../Bin/zkl chatServer.zkl
Listening on Octavius:23
Somebody is connecting ...
+++ sam arrived +++
--- sam leaves ---
Somebody is connecting ...
+++ sam arrived +++
Somebody is connecting ...
+++ craig arrived +++
craig> this is a test
sam> sam i am
--- sam leaves ---
^\
</pre>
Another terminal:
<pre>
$ telnet localhost
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Your handle: sam
^D to disconnect
+++ craig arrived +++
craig> this is a test
sam i am
^D
Connection closed by foreign host.
</pre>
And yet another terminal:
<pre>
$ telnet localhost
...
Your handle: craig
^D to disconnect
this is a test
sam> sam i am
--- sam leaves ---
Connection closed by foreign host.
</pre>
{{omit from|AutoHotkey}}
{{omit from|Lilypond}}
{{omit from|Mathematica}}
{{omit from|Maxima}}
{{omit from|ML/I}}
{{omit from|PARI/GP|No good way to access network}}
{{omit from|Retro}}
{{omit from|SmileBASIC}}
{{omit from|Stata}}
9,485

edits