Studio/Socket Driver

From J Wiki
Jump to: navigation, search

Lab: Socket Driver Server

Introduction

There is reference documentation of the Socket Driver in the release documentation available from the J help menu.

Socket Driver Server

A socket is an endpoint in a bi-directional communication channel. The other end can be in the same task, not usually very interesting, in another task on the same machine, or in a task on another machine that is accessed through a TCP/IP connection.


J provides support for driving sockets under Windows 9x/NT/2000. The Socket Driver is not available on the Macintosh.

This lab assumes that you are able to access sockets.


Script sockets.ijs has definitions for working with sockets. This loads into the jsocket locale, and coinsert puts this locale on the path.

   load'socket'
   coinsert 'jsocket'

A socket is identified by an integer handle. The verb sdsocket creates a socket.

All socket verbs have a result whose first item is a result code.

The result of sdsocket'' should be a 0 (no error) and an integer that is the socket handle.

   sdsocket
3 : 0"1
s=. res socketJ <"0 [3{.y,(0=#y)#PF_INET,SOCK_STREAM,IPPROTO_TCP
if. s=_1 do. 0;~sdsockerror'' return. end.
SOCKETS_jsocket_=: SOCKETS_jsocket_,s
0;s
)
   r=:sdsocket''
   r
+-+---+
|0|188|
+-+---+
   SK=:1 pick r
   SK
188

When you are through with a socket it should be closed.

   sdclose SK
0

sdcleanup closes all sockets and releases all socket resources.

   sdcleanup''
0

Let us see what an error would look like.

   sdclose 1234
10038

sderror converts an error number, the first item of its argument, to an error name that might give an idea about what is wrong.

   sderror sdclose 1234
ENOTSOCK

sdcheck checks for an error in the result of a socket verb. If there is an error it signals the error with the error number and name.

If there is no error it returns its argument with the error code removed.

   sdcheck sdclose 1234

sdgetsockets returns a list of sockets.

   sdsocket''
+-+---+
|0|684|
+-+---+
   sdsocket''
+-+---+
|0|188|
+-+---+
   sdgetsockets''
+-+-------+
|0|684 188|
+-+-------+
   sdcheck sdgetsockets''
+-------+
|684 188|
+-------+
   sdcheck sdcleanup''

   sdcheck sdgetsockets''
++
||
++

Sockets are used for communication between tasks so to do interesting things you need another task.

This task (session) will be the main one and will act as the server.

The other J task you start will be used as the client.

You will run the server lab in this task and the client lab in the other task.

Coordinating between labs in two tasks can be a little tricky and you will have to pay attention.

Switching between the two tasks will be indicated by a dialog box, as in the next section.

   wdinfo 'Server';(0 : 0 )
Start another copy of J to run as the client of this server.

In the new client task run the lab "Socket Driver Server - Client".

Do NOT continue the lab in this server task until instructed to do so by the lab in the client task.

You may now dismiss this dialog and proceed with the above instructions.

Lab: Socket Driver Server - Client

If you did not start this lab in response to instructions in the Socket Driver Server lab currently running in another J task you should stop running this lab. In order to see the socket lab illustrating client-server communication, start the "Socket Driver Server" lab, and follow the instructions there.


Position the 2 copies of J (server and client) as conveniently as possible on your screen. If you have a large screen you can probably position them so that you can see both sessions at the same time.

   toserver ''
OK

You should have now started a second J task to use as a client and you should have stepped through the Socket Driver Client lab up to the step that told you to return to this server task.

This session is the socket server.


As a server we need to create a socket that will listen for requests from a client.

   load'socket'
   sdcleanup''
0
   SKLISTEN =: 0 pick sdcheck sdsocket''
   SKLISTEN
188

The first thing is to give the socket an address.

Addresses used with sockets consist of 3 parts: address_family, address_name, port

The first item an integer which indicates the type of address. Currently this is always AF_INET, i.e. address family internet.

The second part is an internet address of a host. An internet address is a string of 1 to 4 numbers separated by dots.

The third part is an integer port.


The sdbind verb sets the address for our socket. The empty string for the host name indicates that the socket is bound to any name for this host. A host machine can have more than one name.

The port of 1500 identifies us as a server for port 1500. The port number is arbitrary, but must be known by both the server who offers the service and clients who want that service.

   sdcheck sdbind SKLISTEN ; AF_INET_jsocket_ ; '' ; 1500

As a server socket, the socket needs to be set to listen for requests for service.

This is done with sdlisten.

The second value is the number of incoming connections that will be queued by the system.

   sdcheck sdlisten SKLISTEN , 1

sdselect is used to see if there are any sockets that need work. For example when a client makes a connection to our listening socket we need to know and will want to accept the connect and then start sending and receiving data.

sdselect with an empty argument returns information about all sockets.


The sdselect result is a boxed list. The first element is the result code. The result code is stripped off by sdcheck.

The first item is a list of sockets that have data ready for us to read or are trying to connect to a listening socket.

The second item is a list of sockets that are ready to have data sent.

The last item is a list of sockets that have had an error.

Right now all three lists should be empty.

   sdcheck sdselect''
++++
||||
++++

   toclient 'The server is listening for incoming connections on the socket.'
OK

We connect to the server at its address.

The address has 3 parts: The first part indicates the type of address and must be AF_INET, i.e. address family internet.

The second part is the internet address of the host on which the server is running.

The last part is the port number.


You need to get the internet address of the system.

sdgethostbyname converts a "user friendly" host name into the string of 4 numbers that is the internet address. The hostname "localhost" is a name for your system.

The result of sdgethostname includes the AF_INET value of 2.

   load'socket'
   coinsert 'jsocket'
   sdcleanup''
0
   hostaddress=: sdcheck sdgethostbyname 'localhost'
   hostaddress
+-+---------+
|2|127.0.0.1|
+-+---------+
sdconnect connects to a server.

The port number of 1500 is the service that we want.

The sdconnect parameters give the address of the socket to connect to. They are similar to the bind parameters the server used.

   skclient=: 0 pick sdcheck sdsocket''
   skclient
252
   sdcheck sdconnect skclient ; hostaddress , <1500
----
   toserver 0 : 0
We have requested a connection to the server. We now switch to the server task to see if it knows about the connection request.
)
OK

You should be at this step after you have just finished running the steps on the client to request a connection.

The sdselect should now show that the listening socket is ready for a connect.

   SKLISTEN
188
   sdcheck sdselect''
+---+++
|188|||
+---+++

You can now accept the connection from the client.

The result of the accept is a new socket that is cloned from the listening socket.

This new socket is connected to the client and can be used to send and receive data with the client.

   sdcheck sdselect''
+---+++
|188|||
+---+++
   skserver=: 0 pick sdcheck sdaccept SKLISTEN
   skserver
748

The listening socket is still listening for connections. In this example we will only work with the single connection. To simplify things and avoid confusion we will now close the listening socket so that we only have the socket that is connected to the client to worry about.

   sdcheck sdclose SKLISTEN

sdselect should now indicate that the new socket is ready for writing.

Writeable sockets are in the second socket list.

The ASSERT verb checks a condition to try to make sure that the client and server labs are in synch and are working properly together. It will display an error if there is a problem.

   sdcheck sdselect''
++---++
||748||
++---++

   ASSERT skserver e. 1 pick sdcheck sdselect''

sdsend sends data to the other end of the socket connection.

The left argument is the data to send.

The right argument is the socket that sends the data and a flags value that is normally 0.

The result of sdsend is the number of bytes actually sent.

   sdcheck 'welcome to server 1500' sdsend skserver , 0
+--+
|22|
+--+

   toclient'We now switch back to the client to read the data we sent.'
OK

You should have just finished working in the server task and sending some data to this task.

sdselect should show that our socket is now ready to read data. This is indicated by our socket in the readable socket list.

The socket is also marked as ready to write.

   r=: sdcheck sdselect''
   r
+---+---++
|252|252||
+---+---++
   ASSERT skclient e. 0 pick r
sdrecv receives data from the socket. The argument after the

socket is the maximum amount of data that will be received. The 0 argument after that is flags.

The amount of data received may be less than the amount in the argument.

   data=: ; sdcheck sdrecv skclient , 1000 , 0
   data
welcome to server 1500
Let us send some data to the server.

Notice that we do sends in a row. The server just sees this as a string of incoming data, similar to reading data from a file. The boundaries of chunks of sent data have no relationship to the boundaries of chunks of received data.

   sdcheck 'Good to be aboard.' sdsend skclient , 0
+--+
|18|
+--+
   sdcheck ' What can you do?' sdsend skclient , 0
+--+
|17|
+--+
----
   toserver 'We now switch back to the server to read the data.'
OK

Use sdselect to see if there is data to be read.

The socket should be listed in the first list because it is readable, and again in the second list because it is writeable.

   r=: sdcheck sdselect''
   r
+---+---++
|748|748||
+---+---++
   ASSERT skserver e. 0 pick r

   data=. ; sdcheck sdrecv skserver,1000,0
   data
Good to be aboard. What can you do?

Now the sdselect should indicate that there is no more data available for reading, but that you could write data to the socket.

   sdcheck sdselect''
++---++
||748||
++---++
   ASSERT 0 = # 0 pick sdcheck sdselect''

A socket is created as a blocking socket.

This means that an operation such as sdrecv that cannot complete immediately it waits (hangs) until it can be completed.

Right now the sdselect tells us that there is no data ready to read. Since it is a blocking socket if we do an sdrecv now this task will hang until there is data to be read.


The sdrecv in the next section will hang this task until there is data to receive. You need to switch to the client task and send some data.

THE NEXT SECTION WILL HANG THIS TASK!

WHEN YOU HANG, SWITCH TO THE CLIENT lab TO SEND SOME DATA. )

   ; sdcheck sdrecv skserver,1000,0

The server task is now hung waiting to receive data from us.

Send the server some data.

   sdcheck 'wake up!' sdsend skclient,0
+-+
|8|
+-+
----
   toserver 'We now switch to the server to see that it is running again.'
OK

wake up!

The use of sockets that block like this is generally not a good idea and it is much better to use a non-blocking socket.

A socket can be marked as non-blocking with the sdioctl.

FIONBIO (Blocking IO) is the control option to set to 1 for a non-blocking socket.

   sdcheck sdioctl skserver,FIONBIO_jsocket_,1
+-+
|1|
+-+

Check sdselect to see if there is any data available for reading.

There is no data to read. Previously the sdrecv hung when there was no data to read. Now it will give an immediate result indicating there is no data to read. An attempt to read further will give an error.

   sdcheck sdselect''
++---++
||748||
++---++
   sdcheck sdrecv skserver,1000 0

You have marked this end of the socket as non-blocking. This has not changed the other end of the connection. The client still has a blocking socket and the client would hang if it did an sdrecv when there was no data. If both ends want non-blocking sockets, then both ends must explicitly set the socket as non-blocking.


You could use sdselect to "poll" for actions to be taken. There is also a parameter to sdselect that gives a timeout value that makes this possible without staying in a tight loop. This form of select waits for a socket that is ready for reading, writing, or has an error and returns when an event occurs. But if there is nothing to do and the timer value has expired, then the sdselect returns so that you do not stay hung in the sdselect.


However, we will not explore this use of sdselect because there is a better solution.

sdasync marks a socket as non-blocking AND requests that any change in the state of the socket, such as new data to be read, causes the sentence socket_handler'' to be executed.

This is similar to how window events such as the press of a form button runs sentence wdhandler''.


socket_handler is not defined so the first thing to do is to give it a definition so we do not just see a value error when it runs.

   socket_handler=:3 : '''socket_handler run : Check sdselect for stuff to do!!!'''

Let us mark our server socket with sdasync.

   sdcheck sdasync skserver


socket_handler run : Check sdselect for stuff to do!!!

   toclient 'We now switch to the client to have it send us some data.'
OK

You should have just marked the server socket with sdasync.

Now when we send new data to the socket it will run socket_handler'' in the server.

   sdcheck 'this should cause an event' sdsend skclient,0
+--+
|26|
+--+
----
   toserver ''
OK

socket_handler run : Check sdselect for stuff to do!!!

You should see that the sentence socket_handler$0 was run when the client did the sdsend.

   toclient ''
OK

Do several sends to the server.

   sdcheck '1111111' sdsend skclient,0
+-+
|7|
+-+
   sdcheck '2222222222222222222' sdsend skclient,0
+--+
|19|
+--+
   sdcheck '333333333333333333333333'sdsend skclient,0
+--+
|24|
+--+
----
   toserver ''
OK

Even though the client did several sends, we got only one notification that there was a change. That is, socket_handler'' was not executed for each send the client did.

socket_handler is called whenever there is NEW information in the sdselect result. The socket_handler should do an sdselect and it should process all available information. If there was any new information that we might not have processed based on that first sdselect, then the socket_handler will be run again.

If the socket_handler takes care of all processing required by an sdselect then it can depend on the next call to socket_handler to take care of any new information.


Let us send a J noun to the client.

3!:1 converts the noun to a internal representation as a character vector that we can use as an argument to sdsend.

   a=.i.2 3 4
   data=: 3!:1 a
   sdcheck data sdsend skserver , 0
+---+
|124|
+---+

   toclient ''
OK

The server sent us a J variable. Read the data and create the variable here in our task.

   ASSERT skclient e. >0{sdcheck sdselect''


   d=: ; sdcheck sdrecv skclient,1000,0
   d=:3!:2 d
   d
 0  1  2  3
 4  5  6  7
 8  9 10 11

12 13 14 15
16 17 18 19
20 21 22 23
This is the end of the socket client lab.

Switch back to the server task and continue with the lab there.


In the simple examples here we have worked with small amounts of data and have made the assumption that the data all goes in one send and is all received in one sdrecv.

In a real application a send of a large amount of data may indicate that only part of the data could be sent. The application has to wait (probably for a socket_handler call) until sdselect indicates the socket is ready for the sending of more data before the rest of the data is sent. Similarly, to receive data in general it is necessary to do more than one receive to get all the data that belongs together.


Because data can be sent and received in parts an application needs a convention that indicates what data belongs together. If ascii text is moving back and forth then something as simple as an LF and other control characters can indicate record boundaries. In general adding a count of characters in a record the beginning of a record is probably the most general solution.


This is the end of the Socket Driver - Server/Client lab.

You can either close down the other J task now, or continue experimenting with the client-server socket connection on your own.