int main( int argc, char *argv[] ) {
fprintf( stderr, "mTCP Sample program by M Brutman (mbbrutman@gmail.com) (C)opyright 2012-2020\n\n" );
// Read command line arguments
parseArgs( argc, argv );
// Setup mTCP environment
if ( Utils::parseEnv( ) != 0 ) {
exit(-1);
}
The program first checks its own command line parameters by calling its
own parseArgs routine. After that it calls Utils::parseEnv so that mTCP
can find and read its configuration file. There are no parameters to
use but there is a return code to check. Any return code other than 0
is a failure.// Initialize TCP/IP stack
if ( Utils::initStack( 2, TCP_SOCKET_RING_SIZE, ctrlBreakHandler, ctrlCHandler ) ) {
fprintf( stderr, "\nFailed to initialize TCP/IP - exiting\n" );
exit(-1);
}
// From this point forward you have to call the shutdown( ) routine to
// exit because we have the timer interrupt hooked.
TcpSocket *mySocket;Dns::resolve is used to convert server names to IP addresses. If you used a numerical IP address then the first call to Dns::resolve will resolve it and you will not have to wait. Otherwise, you need to enter a loop to wait for DNS to complete.
int8_t rc;
if ( Listening == 0 ) {
fprintf( stderr, "Resolving server address - press Ctrl-Break to abort\n\n" );
IpAddr_t serverAddr;
// Resolve the name and definitely send the request
int8_t rc2 = Dns::resolve( ServerAddrName, serverAddr, 1 );
if ( rc2 < 0 ) {
fprintf( stderr, "Error resolving server\n" );
shutdown( -1 );
}
uint8_t done = 0;The loop has to process incoming packets, retry Arp requests if necessary, and retry DNS requests if necessary. Those functions handled by PACKET_PROCESS_SINGLE, Arp::driveArp, and Dns::drivePendingQuery.
while ( !done ) {
if ( CtrlBreakDetected ) break;
if ( !Dns::isQueryPending( ) ) break;
PACKET_PROCESS_SINGLE;
Arp::driveArp( );
Tcp::drivePackets( );
Dns::drivePendingQuery( );
}
// Query is no longer pending or we bailed out of the loop.When DNS completes or times out the loop will end. At the end of the loop another call to Dns::resolve tells you the final result.
rc2 = Dns::resolve( ServerAddrName, serverAddr, 0 );
if ( rc2 != 0 ) {
fprintf( stderr, "Error resolving server\n" );
shutdown( -1 );
}
mySocket = TcpSocketMgr::getSocket( );If DNS resolved the name and gave you an IP address you will need to allocate a socket, set the TCP receive buffer size for the socket, and make a TCP socket connect call. mTCP owns all of the socket data structures - you just get a pointer to them. The call to TcpSocketMgr?::getSocket gets you a socket to use. When you are done with a socket you should return it using the TcpSocketMgr?::freeSocket call.
mySocket->setRecvBuffer( RECV_BUFFER_SIZE );
fprintf( stderr, "Server resolved to %d.%d.%d.%d - connecting\n\n",
serverAddr[0], serverAddr[1], serverAddr[2], serverAddr[3] );
// Non-blocking connect. Wait 10 seconds before giving up.
rc = mySocket->connect( LclPort, serverAddr, ServerPort, 10000 );
}
else {
fprintf( stderr, "Waiting for a connection on port %u. Press [ESC] to abort.\n\n", LclPort );
TcpSocket *listeningSocket = TcpSocketMgr::getSocket( );
listeningSocket->listen( LclPort, RECV_BUFFER_SIZE );
Here we allocate a socket using TcpSocketMgr?::getSocket but instead of
using this socket to make an outgoing connection to a server we are
going to use it to listen for incoming sockets. The listen call tells
mTCP what port to listen on and what receive buffer size to use for
newly created sockets.// Listen is non-blocking. Need to waitAfter that, we wait! The loop checks for keyboard input to see if the user wants to end the program early. It also processes incoming packets. (If a user hits Ctrl-Break the CtrlBreakDetected? variable will be set by the new interrupt handler we installed after TCP/IP went live. Instead of ending the program prematurely, we can do an orderly shutdown.)
while ( 1 ) {
if ( CtrlBreakDetected ) {
rc = -1;
break;
}
PACKET_PROCESS_SINGLE;
Arp::driveArp( );
Tcp::drivePackets( );
mySocket = TcpSocketMgr::accept( );
if ( mySocket != NULL ) {
listeningSocket->close( );
TcpSocketMgr::freeSocket( listeningSocket );
rc = 0;
break;
}
if ( _bios_keybrd(1) != 0 ) {
char c = _bios_keybrd(0);
if ( (c == 27) || (c == 3) ) {
rc = -1;
break;
}
}
}
}
while ( programIsNotEnding ) {
Process Incoming Packets
Perform some processing on those packets
Check for User input
}
Lets get into more detail ...// Service the connectionPACKET_PROCESS_SINGLE is a macro that checks for new packets from the packet driver and if any are found it processes those packets.
PACKET_PROCESS_SINGLE;
Arp::driveArp( );
Tcp::drivePackets( );
if ( mySocket->isRemoteClosed( ) ) {
done = 1;
}
// Process incoming packets first.
int16_t recvRc = mySocket->recv( recvBuffer, RECV_BUFFER_SIZE );
if ( recvRc > 0 ) {
write( 1, recvBuffer, recvRc );
}
else if ( recvRc < 0 ) {
fprintf( stderr, "\nError reading from socket\n" );
done = 1;
}
If the socket is not closed then the code will try to receive data on
it. A return code of 0 indicates no data, which is not an error. A
negative return code indicates a socket error and a positive return
code is the number of bytes that were received. Any bytes that are
received are written to to STDOUT right away. if ( CtrlBreakDetected ) {
fprintf( stderr, "\nCtrl-Break detected\n" );
done = 1;
}
if ( _bios_keybrd(1) ) {
uint16_t key = _bios_keybrd(0);
char ch = key & 0xff;
if ( ch == 0 ) {
uint8_t ekey = key >> 8;
if ( ekey == 45 ) { // Alt-X
done = 1;
}
else if ( ekey == 35 ) { // Alt-H
fprintf( stderr, "\nSample: Press Alt-X to exit\n\n" );
}
}
else {
int8_t sendRc = mySocket->send( (uint8_t *)&ch, 1 );
// Should check the return code, but we'll leave that
// as an exercise to the interested student.
}
}
Note the first check is to check that the user has not hit Ctrl-Break.
If they did our interrupt handler would have set the flag, which tells
us to end the program.static void shutdown( int rc ) {
Utils::endStack( );
Utils::dumpStats( stderr );
fclose( TrcStream );
exit( rc );
}
Utils::endStack will take care of the timer interrupt that it had
hooked and the Ctrl-Break handler. Both the Ctrl-Break and Ctrl-C handlers
were installed but only the Ctrl-Break handler needs to be restored;
DOS will take care of restoring the Ctrl-C handler automatically.Originally created in 2011, Last updated January 1st, 2020
(C)opyright Michael B. Brutman, mbbrutman at gmail.com