This tutorial attempts to bring you up to speed with the basics of using sockets. It is assumed that you have good scripting knowledge. This tutorial uses a small "dcc chat" simulator called SockChat. It functions the same as /dcc chat. This is NOT a tutorial on DCC Chats.
NOTE: At the bottom of this tutorial, the entire source code is provided.
Sockets in mIRC
You can use sockets in mIRC to connect to other machines, allow other machines to connect to you, or both. Using sockets, you can create a myriad of applications within mIRC such as an FTP client, a partyline, or even another irc client.
In this tutorial, we are going to simulate a DCC Chat with sockets and custom @windows. Before you can understand how to do this, you must first know how DCC Chat works. Luckily, it's quite simple.
-
The person initiating the DCC Chat sets up what is known as a "listen server." A listen server "listens" for a connection on a specified port.
-
The person receiving the request gets the information from the initiator and attempts to connect to his machine on the specified port.
-
If the connection is successful, you accept the connection on a new socket and close the listen server (as we do not want anyone else connecting). Depending on the type of program you are creating, this is not always the case.
After the new socket is created, you listen for data and act accordingly. You also send data to the other party.
Before We Start
In this example, I have created a few miscellaneous aliases to make controlling the sockets easier. These are all local aliases and are only used internally by the script.
sc.close <socket>
This alias verifies that the socket still exists, and if it does, it closes it.
alias -l sc.close if ($sock($1)) sockclose $1
sc.echo <nick> <color> <text>
This alias simply echoes text in the specified color to the correct window.
alias -l sc.echo echo $2 -i2 @SockChat: $+ $1 $3-
I also make many references to $gettok() in this example. I use $gettok() to get the nickname from the socket name or the @window name. You don't have to name your sockets like this, you can use the .mark property of a socket to store information about the socket. I simply used this method to allow multiple dcc chats at one time.
Setting Up
The first thing that needs to be done is setting up the listen server. The way to do this is to create a unique socket and attach a port to it. The following code below illustrates how:
alias sockchat {
if ($1 == $null) {
echo -a * /sockchat: You must specify a nickname
return
}
var %sc.port = 1024
var %sc.sock = sc.listen. $+ $1
while ($portfree(%sc.port) == $false) {
inc %sc.port
}
socklisten sc.listen. $+ $1 %sc.port
.notice $1 DCC Chat ( $+ $ip $+ )
.raw privmsg $1 : $+ $chr(1) $+ DCC CHAT chat $longip($ip) %sc.port $+ $chr(1)
.timer 1 60 sc.close %sc.sock
window -kae @SockChat: $+ $1
sc.echo $1 $colour(info2) DCC Chat with $1
sc.echo $1 $colour(info) Waiting for other party to accept...
}
As you can see, this alias sets up the listen server and configures a custom @window to handle the new chat. The most important part about this procedure is finding an open port to listen on. This is to avoid conflicts with other programs that access sockets.
Accepting the Connection
Use the on socklisten event to wait for the other party to connect to you. Once we get the connection, we accept it and close the old listening socket.
on *:socklisten:sc.listen.*: {
var %sc.nick = $gettok($sockname, 3, 46)
sockaccept sc.accept. $+ %sc.nick
sockclose $sockname
sc.echo %sc.nick $colour(info) Connection established!
}
Again, depending on the type of application you are creating, closing the listening socket is not always needed.
Handling the Data
After the connection has been made, you need to set up two handlers for manipulating the data. You need one to receive the data from the other party and you need one to send your data. To do this, you would use the on sockread to get the data and on input to send the data.
Here is the on sockread code:
on *:sockread:sc.accept.*: {
var %sc.tmp, %sc.nick = $gettok($sockname, 3, 46)
sockread %sc.tmp
if ($sockbr) sc.echo %sc.nick $colour(normal) < $+ %sc.nick $+ > %sc.tmp
}
Here is the on input code:
on *:input:@SockChat*: {
if (($left($1, 1) != /) || ($ctrlenter)) {
var %sc.nick = $gettok($active, 2, 58)
var %sc.sock = sc.accept. $+ %sc.nick
if ($sock(%sc.sock)) {
sockwrite -n %sc.sock $1-
sc.echo %sc.nick $colour(normal) < $+ $me $+ $+ > $1-
}
halt
}
}
Closing the Connection
In order to properly handle closing the connection (or having the connection closed), we need two more events - on close and on sockclose. We use on close to handle closing the socket ourself (via closing the @window) and we use on sockclose to handle a termination of the socket by someone or something else.
Here is the on close code, in which we use our /sc.close alias to verify that the connection is still alive. If it isn't, then we do not have to do anything. This is called whenever *you* close the window.
on *:close:@SockChat*: sc.close sc.accept. $+ $gettok($target, 2, 58)
Here is the code for on sockclose. This is called whenever the other party closes the chat or if the connection is terminated for any reason other than your doing.
on *:sockclose:sc.accept.*: {
var %sc.nick = $gettok($sockname, 3, 46)
sc.echo %sc.nick $colour(info) DCC Chat connection with %sc.nick lost...
}
Extra Notes
In this example, there is only minimal error checking, but one thing that is important is when reading data (on sockread), always check that the identifier $sockbr (bytes read) is more than zero. You can also check $sockerr to see if an error has occurred in the read event.
Below is the full source code without the comments to save space. Enjoy!
alias sockchat {
if ($1 == $null) {
echo -a * /sockchat: You must specify a nickname
return
}
var %sc.port = 1024
var %sc.sock = sc.listen. $+ $1
while ($portfree(%sc.port) == $false) {
inc %sc.port
}
socklisten sc.listen. $+ $1 %sc.port
.notice $1 DCC Chat ( $+ $ip $+ )
.raw privmsg $1 : $+ $chr(1) $+ DCC CHAT chat $longip($ip) %sc.port $+ $chr(1)
.timer 1 60 sc.close %sc.sock
window -kae @SockChat: $+ $1
sc.echo $1 $colour(info2) DCC Chat with $1
sc.echo $1 $colour(info) Waiting for other party to accept...
}
alias -l sc.close if ($sock($1)) sockclose $1
alias -l sc.echo echo $2 -i2 @SockChat: $+ $1 $3-
on *:socklisten:sc.listen.*: {
var %sc.nick = $gettok($sockname, 3, 46)
sockaccept sc.accept. $+ %sc.nick
sockclose $sockname
sc.echo %sc.nick $colour(info) Connection established!
}
on *:sockclose:sc.accept.*: {
var %sc.nick = $gettok($sockname, 3, 46)
sc.echo %sc.nick $colour(info) DCC Chat connection with %sc.nick lost...
}
on *:sockread:sc.accept.*: {
var %sc.tmp, %sc.nick = $gettok($sockname, 3, 46)
sockread %sc.tmp
if ($sockbr) sc.echo %sc.nick $colour(normal) < $+ %sc.nick $+ > %sc.tmp
}
on *:input:@SockChat*: {
if (($left($1, 1) != /) || ($ctrlenter)) {
var %sc.nick = $gettok($active, 2, 58)
var %sc.sock = sc.accept. $+ %sc.nick
if ($sock(%sc.sock)) {
sockwrite -n %sc.sock $1-
sc.echo %sc.nick $colour(normal) < $+ $me $+ $+ > $1-
}
halt
}
}
on *:close:@SockChat*: sc.close sc.accept. $+ $gettok($target, 2, 58)