C-based Server That Can Accept Single Clientã¢â‚¬â„¢s Request Using Sockets
Sockets provide the programmer with a facility to let their applications to communicate over a network. This lesson shows how to use sockets to let to LiveCode apps to talk to each other as well as providing an example of how LiveCode apps can share data with Java apps.
What are Sockets?
Sockets provide two networked machines with a bidirectional communication channel. One machine will be the server, listening in for connections and the other, the client, attempting to make a connection with the server. An example of this would exist when you fetch a web page. Your spider web browser, or the client, attempts to brand a connection to the spider web server. The spider web server, listening in for clients, will have the connection and so proceed to handle the clients request. Here, the browser will ask the web server for the folio, before the spider web server responds with the page data.
Servers are accessed via socket addresses, a combination of the server'southward IP address (or domain proper name) and a port number. The port can be thought every bit a connection bespeak on the sever, like USB or Firewire ports, with each port serving a specific purpose. For instance, web pages are served on port 80 (HTTP) , emails are sent via port 25 (SMTP).
Once 2 machines are connected, they tin so communicate streams of bytes with each other. It's up to the customer and server to format these byte streams into structured data chunks. A elementary example would exist an echo sever, which receives a stream of bytes from a client, assumes they course an ascii string and sends the string back to the customer.
As noted previously, sockets use IP based networks. There are a range of transport layers that can be used on top of IP. In this lesson we will consider TCP sockets.
How to Employ Sockets in LiveCode - Server Side
Using sockets in LiveCode is very elementary. First of all allow us consider a server: A server must listen in on a specific port for clients. To do this in LiveCode, we simply use the following command:
have connections on port 1234 with message "clientConnected"
Here, our server will listen in on port 1234 for connecting clients. When a customer is connected, the message "clientConnected" will be sent, detailing the newly opened socket. The server can now mind in on this socket for messages from the client:
on clientConnected pSocket read from socket pSocket with message "messageReceived" end clientConnected
In the example to a higher place, the server is reading from the client in non-blocking mode. That means that the read request will exit instantly. When a information is received from the client, the "messageReceived" message volition be sent, detailing the socket it came from and the information sent. The alternative to this is to omit the "with message". Hither the read volition part in blocking way, non returning until information has been received from the client. When data is received, the read control volition return and the data will exist placed in it.
An additional parameter can be passed to the read control, detailing how much information nosotros want to read. This can be a number of characters or lines (or any other chunk blazon - read from socket pSocket for 5 lines) or until a specific character is reached (read from socket pSocket until return).
Let us consider the read as detailed earlier, in non-blocking mode. When data is received from the client the "messageReceived" message will be sent. At this point, the server tin choose to write a response to the client. Here, we will only echo the information back to the client using the write command.
on messageReceived pSocket, pMsg write pMsg to socket pSocket read from socket pSocket with message "messageReceived" end messageReceived
Similar the read command, write tin can be blocking or non-blocking. If we desire a not blocking write, we only specify the message to be sent when the write is complete. Note that in lodge to go on reading from the client, we must issue some other read command.
When the server no longer wishes to communicate with the customer, the socket tin simply exist closed with the command close socket tSocket. If the client closes the socket, the server volition be sent a socketClosed message, detailing the socket that has been closed.
How to User Sockets in LiveCode - Customer Side
Using sockets on the client side is every bit as easy in LiveCode. To begin communicating with the server, we must first open up a socket:
open up socket to "host:port" with message "clientConnected"
Like the read and write commands, the callback message on the open command is optional, and defines if the command is blocking or not-blocking. If used in blocking manner, in one case connected the newly opened socket will be accessible via the it variable. In non-blocking style, it will exist passed every bit a parameter in the callback message.
If there has been an error connecting to the server the "socketError" message will exist sent. (If the mistake is due to a problem finding the specified host, the error message is returned in the result, and no "socketError" message is sent.)
Let u.s. consider the open command in non-blocking mode. Once the socket is opened, we can write a bulletin to the server, read the response and then shut the connectedness:
on clientConnected pSocket write "hullo" & return to socket pSocket read from socket pSocket until return put it close socket pSocket end clientConnected
The read and write commands function in exactly the same manner every bit defined previously for the server.
Creating a Simple Broadcast Server
Let the states expand on these commands and create a simple broadcast server. Our broadcast server will mind in for connecting clients. Once a client is connected, the server will place the client on its list of connected clients earlier attempting to read a line of data from it. When a line of data is read from a client, that data will be broadcast to each connected client.
The list of connected clients volition only be a script variable property the socket for each customer separated by return. Our server will exist started and stopped using elementary outset and stop commands. 2 further handlers will be defined: One called when a client is connected, adding the customer to the listing before beginning reading. The other will be called when the read is complete, dissemination the read bulletin to all connected clients.
local sConnectedClients -- list of authorised clients local sRunning -- if the server is currently running constant kPort = 8010 -- Start the server listening for connections. When a client attempts to -- connect, the broadcastServerClientConnected message will exist sent. command broadcastServerStart if not sRunning so put truthful into sRunning take connections on port kPort \ with message "broadcastServerClientConnected" end if finish broadcastServerStart -- Stop the server listening for connections. Shut all open advice -- channels. control broadcastServerStop if sRunning and so put false into sRunning put empty into sConnectedClients echo for each line tSocket in the opensockets close socket tSocket end repeat end if finish broadcastServerStop -- Sent when a customer attempts to make a connection. Shop the customer -- in the continued list and begin reading lines of text from them. The -- broadcastServerMessageReceived bulletin volition be sent when a line is received. on broadcastServerClientConnected pSocket put pSocket & render later on sConnectedClients read from socket pSocket until render \ with bulletin "broadcastServerMessageReceived" end broadcastServerClientConnected -- Sent when a line of text is read from a client. Circulate that line of -- text to all connected clients. on broadcastServerMessageReceived pSocket, pMsg repeat for each line tSocket in sConnectedClients write pMsg to socket tSocket terminate repeat read from socket pSocket until return \ with message "broadcastServerMessageReceived" finish broadcastServerMessageReceived -- Sent when a client disconnects. Remove the customer from continued listing on socketClosed pSocket delete line lineoffset (pSocket, sConnectedClients) of sConnectedCLients end socketClosed
Creating a Broadcast Client
Now lets define our client. This is really easy. Nosotros just, every bit before, connected to the server. Once continued, we tin can brainstorm sending messages past calling the "sendMessage" handler. This handler will simply write the message to the server using the socket we connected with.
The other task of the client is to heed for whatsoever messages broadcast by the server. Here, the client volition merely read from the server in non-blocking mode, logging the message read before continuing the read.
local sSocket constant kPort = 8010 -- Connect the client to the given server. Will ship a broadcastClientConnected -- when the client has connected. command broadcastClientStart pServer if sSocket is empty and so open up socket to pServer & ":" & kPort \ with message "broadcastClientConnected" end if terminate broadcastClientStart -- Disconnect the client from the circulate server. command broadcastClientStop if sSocket is not empty and then close socket sSocket put empty into sSocket terminate if terminate broadcastClientStop -- Write the given message to the communication channel the customer -- has opended with the broadcast server. command broadcastClientSend pMsg if sSocket is not empty then write pMsg & render to socket sSocket end if terminate broadcastClientSend -- Sent once the client has continued to the broadcaset server. -- Store the socket for futurure reference and begin reading information -- from the server. on broadcastClientConnected pSocket put pSocket into sSocket read from socket sSocket until render \ with message "broadcaseClientMessageReceived" terminate broadcastClientConnected -- Sent when a message has been received from the server. Output the -- bulletin and keep reading data from the server. on broadcaseClientMessageReceived pSocket, pMsg put pMsg later on field "log" read from socket sSocket until render \ with message "broadcaseClientMessageReceived" end broadcaseClientMessageReceived -- Sent when at that place is an mistake opening the socket. Log the error. -- and close the socket. on socketError pSocket, pError broadcastClientDisconnect put pError & return after field "log" end socketError -- Sent when the connexion to the server is closed by the server. on socketClosed put empty into sSocket end socketClosed
The image above shows our broadcast client communicating with the server and another customer connected via telnet.
Devising a Communication Protocol
Our circulate customer/server used a very simple communication protocol, where messages were sent equally ASCII text blocks separated by a return character. A communication protocol just defines the ways in which the transmitted data is encapsulated. Producing a simple and robust protocol allows your system to implemented in range of dissimilar manners, using various dissimilar technologies. Consider the number of different web browsers (and servers) on offering.
Let united states of america develop a very simple conversation protocol. First of all, each message transmitted volition have a header. Our header will comprise of a 4 graphic symbol message type code and an integer detailing the number of bytes (or characters) in our message trunk. The header volition be separate from the message trunk by a return character.
Nosotros volition include 5 message types:
AUTH: Sent past the client, requesting authorization.
VERI: Returned by the server if the customer'southward authority request is successful.
MESG: Sent past both client and server, indicating a simple ASCII text message to exist circulate.
ERRO: Sent by the server to bespeak that at that place has been a error (authorization failed etc) and the client will be asunder.
WARN: Sent by the server to bespeak a warning, such as the customer has sent an invalid message.
Such a protocol may seem a little excessive (especially in our example) but it provides a robust programmers interface and a solid platform for hereafter extensions. For example, nosotros may wish to include file sharing, making the message size an important piece of data.
LiveCode Conversation Server
Nosotros will implement our chat server protocol using LiveCode. Our server will function in a very similar manner to our broadcast server. This time, withal, once a client is continued, the volition go on the pending list, pending say-so. Authorization will be a simple tasks of ensuring the client has a uniques user proper noun. Once authorized, the customer will be moved on to the authorized list and be allowed to send and receive messages.
The main handler of interest will exist the message received callback. Here, the server must parse the header and if required read the message torso. To do this, we will use the "read from for" control, reading the number of characters defined in the message header.
local sConnectedClients -- assortment of authorised clients [customer] => [proper name] local sPendingClients -- list of all pending clients local sClientNames -- list of all currently used customer names local sRunning -- if the server is currently running abiding kPort = 8020 -- Start the server listening for connections. When a customer -- attempts to connect, the chatServerClientConnected message will be sent. command chatServerStart if not sRunning and so put true into sRunning accept connections on port kPort \ with bulletin "chatServerClientConnected" terminate if terminate chatServerStart -- Finish the server listening for connections. Close all open up advice -- channels. control chatServerStop if sRunning then put false into sRunning put empty into sConnectedClients put empty into sPendingClients put empty into sClientNames repeat for each line tSocket in the opensockets close socket tSocket end repeat end if end chatServerStop -- Sent when a client attempts to make a connection. Store the client as -- awaiting and begin reading message headers (line of text) from the client. -- When a header is received, a chatServerMessageReceived bulletin will be sent. on chatServerClientConnected pSocket put pSocket & render after sPendingClients read from socket pSocket until return \ with message "chatServerMessageReceived" terminate chatServerClientConnected -- Sent when a bulletin header is received from a client. Parse the header and -- the read message body if required. Do this by switching accross the bulletin -- blazon (kickoff item in header). -- -- If information technology is an authorisation request, read the message body (clients name), -- bank check it'due south unique, add together client to list of authorised clients and send -- say-so success message to client. -- -- If it is a broadcast message, check the client is authorised and then send -- to all connected clients. -- -- One time bulletin is handled, keep reading from customer. on chatServerMessageReceived pSocket, pMsg put char 1 to - ii of pMsg into pMsg local tAuth, tCommand, tLength, tMsg put pSocket is among the keys of sConnectedClients into tAuth put item i of pMsg into tCommand put item 2 of pMsg into tLength if tLength is non an integer then put "Invalid message length" into tMsg write "WARN," & the number of chars in tMsg \ & return & tMsg to socket pSocket else switch tCommand case "MESG" if tAuth then read from socket pSocket for tLength chars chatServerBroadcast sConnectedClients[pSocket] & ":" && information technology else put "Client not verified" into tMsg write "ERRO," & the number of chars in tMsg \ & render & tMsg to socket pSocket end if intermission case "AUTH" if tAuth then put "Client already verified" into tMsg write "WARN," & the number of chars in tMsg \ & render & tMsg to socket pSocket else read from socket pSocket for tLength chars if it is not among the lines of sClientNames then put it into sConnectedClients[pSocket] put it & render after sClientNames write "VERI,0" & return to socket pSocket delete line \ lineoffset (pSocket, sPendingClients) of sPendingClients chatServerBroadcast it && "continued" else put "Username already taken" into tMsg write "ERRO," & the number of chars in tMsg \ & return & tMsg to socket pSocket end if terminate if break default put "Unknown command" into tMsg write "ERRO," & the number of chars in tMsg \ & render & tMsg to socket pSocket intermission end switch end if read from socket pSocket until return \ with bulletin "chatServerMessageReceived" end chatServerMessageReceived -- Broadcasts the given bulletin to all continued and verified clients. command chatServerBroadcast pMsg local tMsg put "MESG," & the number of chars in pMsg & return & pMsg into tMsg echo for each line tSocket in the keys of sConnectedClients write tMsg to socket tSocket finish repeat end chatServerBroadcast -- Sent when a client disconnects. Remove the client from the pending -- list if they are awaiting, or authorised list if they are authorised. on socketClosed pSocket if pSocket is among the lines of sPendingClients then delete line lineoffset (pSocket, sPendingClients) of sPendingClients else if sConnectedClients[pSocket] is not empty then local tName put sConnectedClients[pSocket] into tName delete variable sConnectedClients[pSocket] delete line lineoffset (tName, sClientNames) of sClientNames chatServerBroadcast tName && "disconnected" end if terminate socketClosed
Java Chat Client
Though we could easily develop our client in LiveCode, in this final part of the lesson we will consider Coffee. Doing so demonstrates how we can use sockets for inter-application communication and the importance of using a clearly defined protocol.
I won't go into to too much detail hither, but will note a few key points. Since we will be working with sockets, we'll need to import the Java "net" library. Also, since we will be using input and output streams, nosotros'll need the "io" library. In lodge to open a socket, we create a new object of type Socket:
Socket tSocket = new Socket( "hostname" , "port" );
Once nosotros accept opened our socket, we can then fetch the input and output streams. These streams let us to read and write information to and from the server. Java provides various wrappers allowing the data read and written to be formatted in various ways: At the simplest level, you may wish to read and write directly to the socket, handling data a byte at a time. Alternatively, yous may wish to operate at a higher level, sending data equally unicode text.
In our case nosotros use a BufferedReader for the input stream (as information technology allows us to read a unmarried line of text - our message header - as well equally a fixed block of text - our message body).
BufferedReader tInput = new BufferedReader( new InputStreamReader(tSocket.getInputStream())); Cord tHeader = tInput.readLine(); For output, we use a DataOutputStream as it allows out to write a stream of bytes - our ASCII encoded message. DataOutputStream tOutput = new DataOutputStream(tSocket.getOutputStream()); tOutput.write( "MESG,5\nHello" .getBytes()); tOutput.flush();
Notice that afterwards we write to the output stream, we telephone call the flush method. This ensures that the data is sent immediately, rather than queued upwards in the stream.
Once the customer is connected to the server, we starting time a new thread that listens to the input stream for information from the server. Nosotros exercise this by making our client class implement the Runnable interface, then filling out the run method of our client. The run method will be chosen one time the new thread is created and must continually listen to the input stream whilst the customer is connected.
All the main functionality will be wrapped up in a single Customer class. Our Client class will have three main public methods available to the user: connect, disconnect and send. Annotation the use of an enumerative type, used to shop the clients electric current connexion status, and the ClientUI type used to handle all user interaction.
import coffee.net.*; import java.io.*; public enum ConnectionStatus { Disconnected, CONNECTING, CONNECTED, } public form ChatClient implements Runnable { private ChatClientUI mUI; private Socket mSocket; individual BufferedReader mInput; individual DataOutputStream mOutput; individual Thread mListener; individual ConnectionStatus mStatus = ConnectionStatus.Asunder; private static final int kPort = 8020; public ChatClient(ChatClientUI pUI) { this .mUI = pUI; } /* * Connect to the given server with the given user name. * Opens a communication aqueduct with the server, sets upward the input and * output streams, sends an authorization request to the server * and starts a new thread listening for any response from the server. */ public void connect(String pServer, String pUsername) { if (pServer.equals( "" )) { this .mUI.displayError( "Connection Error: Missing server proper noun" ); } else if (pUsername.equals( "" )) { this .mUI.displayError( "Connectedness Error: Missing user name" ); } else if ( this .mStatus != ConnectionStatus.DISCONNECTED) { this .mUI.displayError( "Connection Error: Client already continued" ); } else { effort { this .setStatus(ConnectionStatus.CONNECTING); this .mSocket = new Socket(pServer, ChatClient.kPort); this .mInput = new BufferedReader( new InputStreamReader( this .mSocket.getInputStream())); this .mOutput = new DataOutputStream( this .mSocket.getOutputStream()); this .mListener = new Thread( this ); this .mListener.start(); this .writeMessage( "AUTH" , pUsername); } catch (IOException e) { this .mUI.displayError( "Connexion Error: " + e.toString()); this .disconnect(); } } } /* * Attempt to disconnect from the server. Shut socket earlier endmost the * input and output streams. Updates the customer'south electric current connexion status. */ public boolean disconnect() { if ( this .mStatus != ConnectionStatus.DISCONNECTED) { this .setStatus(ConnectionStatus.DISCONNECTED); attempt { this .mSocket.close(); this .mInput.shut(); this .mOutput.close(); } catch (Exception eastward) { } render true ; } else { this .mUI.displayError( "Disconnect Mistake: Customer not continued" ); return false ; } } /* * Send the given chat message to the server. Will format the message with * type MESG earlier sending to the server for broadcast to all othe clients. */ public void send(String pMsg) { if ( this .mStatus == ConnectionStatus.CONNECTED) { this .writeMessage( "MESG" , pMsg); } else { this .mUI.displayError( "Communication mistake: Client not verified" ); } } /* * Returns the clients current connection status. */ public ConnectionStatus getStatus() { return this .mStatus; } /* * Method defined past Runnable interface. Called one time client starts listening * for server messages. While the customer is connected, attempt to read * and process headers (single lines of text) from the input stream. */ public void run() { try { while ( this .mStatus != ConnectionStatus.DISCONNECTED) { this .handleResponse( this .mInput.readLine()); } } catch (IOException e) { if ( this .mStatus != ConnectionStatus.Asunder) { this .mUI.displayError( "Advice Error: " + due east.toString()); this .disconnect(); } } } /* * Gear up the clients current connection status. * Update the ui with the new status. */ private void setStatus(ConnectionStatus pStatus) { if (pStatus != this .mStatus) { this .mStatus = pStatus; this .mUI.statusUpdate(pStatus); } } /* * Send the given message to the server. Format the header based on the * message type and bulletin torso length, attach the body so write to * the output stream. */ private void writeMessage(String pType, String pMsg) { if ( this .mStatus != ConnectionStatus.DISCONNECTED) { endeavor { String tMsg = pType + "," + pMsg.length() + "\n" + pMsg; this .mOutput.write(tMsg.getBytes()); this .mOutput.flush(); } take hold of (IOException e) { this .mUI.displayError( "Communication Error: " + e.toString()); this .disconnect(); } } else { this .mUI.displayError( "Communication Error: Client not connected" ); } } /* * Parse the passed header. The header should be a single line of text, * comma split into the message type, the bulletin length. Based on the type * parse the bulletin, reading the body if required. */ private void handleResponse(String pMsg) { if (pMsg == goose egg ) { this .disconnect(); this .mUI.displayMessage( "Server shutdown" ); } else { String[] tItems = pMsg.divide( "," ); String tMsg = tItems[0]; int tLength = Integer.parseInt(tItems[ane]); if (tMsg.equals( "MESG" )) { this .mUI.displayMessage( this .readData(tLength)); } else if (tMsg.equals( "WARN" )) { this .mUI.displayError( this .readData(tLength)); } else if (tMsg.equals( "ERRO" )) { this .mUI.displayError( this .readData(tLength)); this .disconnect(); } else if (tMsg.equals( "VERI" )) { this .setStatus(ConnectionStatus.Continued); } else { this .mUI.displayError( "Communication Mistake: Unknown message " + tMsg); } } } /* * Read pLength bytes froim the input channel and pasre as a cord. * Used to read message bodies (when lenght is defined in header). */ private String readData( int pLength) { char [] tBuffer = new char [pLength]; effort { this .mInput.read(tBuffer, 0, pLength); render new Cord(tBuffer); } catch (IOException e) { this .mUI.displayError( "Communication Error: " + e.toString()); this .disconnect(); render goose egg ; } } }
Source: https://lessons.livecode.com/m/4071/l/12924-how-to-communicate-with-other-applications-using-sockets
0 Response to "C-based Server That Can Accept Single Clientã¢â‚¬â„¢s Request Using Sockets"
Enviar um comentário