---------------------------------------------------------------------------- This file contains source code for examples to accompany the spring 1997 DECUS seminar "S312 - TCP/IP Programming"" Postscript of the accompanying slides is contained in this same directory. Filename: tcp_ip_programming.ps. Table of Contents (of this file): 1. UCX stream (TCP) client example using BG $QIOs (from talk) 2. UCX stream (TCP) server example using BG $QIOs (from talk) 3. UCX auxiliary server memo (not in talk - a brief memo on sockets w/ the UCX auxiliary server) Edit this file, cutting out the UCX_CLIENT.C and UCX_SERVER.C Compile and link them according to the instructions in the comments at the beginning of each program. If you find any bugs in these examples (or want to share neat enhancements) send them to "wwagner@process.com" Enjoy! ------------------ start of UCX_CLIENT.C --------------------------------- #define SERVER_PORT 65000 /* ** UCX-compatible Stream (TCP) Client using BGDRIVER $QIOs ** ** The client does the following: ** 1. Prompts you for the Internet address of the server's host ** 2. Connects to the server ** 3. Sends a short text message to the server ** 4. Closes the socket and exits ** ** To build against TCPware on VAX: ** $ CC/DEFINE=TCPWARE UCX_CLIENT ** $ LINK UCX_CLIENT,TCPWARE:UCX$IPC/LIB,SYS$INPUT/OPTIONS ** SYS$SHARE:VAXCRTL/SHARE ** ** To build against TCPware on AXP: ** $ CC/DEFINE=TCPWARE/STANDARD=VAXC UCX_CLIENT ** $ LINK UCX_CLIENT ** ** To build against UCX on VAX: ** $ CC UCX_CLIENT ** $ LINK UCX_CLIENT,SYS$LIBRARY:UCX$IPC/LIB,SYS$INPUT/OPTIONS ** SYS$SHARE:VAXCRTL/SHARE ** ** To build against UCX on AXP: ** $ CC/STANDARD=VAXC UCX_CLIENT ** $ LINK UCX_CLIENT ** ** Note: This example was originally based on the UCX$TCP_CLIENT_QIO.C ** example provided by Digital's UCX product in the directory ** SYS$EXAMPLES:UCX.DIR */ #include #include #include #include #include #include #ifdef TCPWARE #include "TCPWARE_INCLUDE:ucx$inetdef.h" /* Provided by TCPware install */ #else #include /* Provided during UCX installation */ #endif /* ** Macros to check VMS success/failure status */ #define SUCCESS(status) (status&1) #define FAILURE(status) (!(status&1)) /* ** Values for boolean variables */ #define FALSE 0 #define TRUE 1 /******************************/ /* Client Program */ /******************************/ main() { int status; /* For return status */ short channel; /* BG channel number */ short sck_parm[2]; /* Socket creation parameter */ short iosb[4]; /* I/O status block */ char buf[512]; /* buffer to transmit */ struct SOCKADDRIN remote_host; /* remote host's address & port */ struct hostent *he; /* returned from gethostbyname */ char server_name[256]; /* name of remote server */ int got_host; /* boolean */ /* ** UCX item list 2 descriptor used during $QIO IO$_ACCESS */ struct IL2 { unsigned int il2_length; char *il2_address; } rhst_adrs = {sizeof remote_host, &remote_host}; /* ** String descriptor used during $ASSIGN */ struct dsc$descriptor inet_dev = {10, DSC$K_CLASS_S, DSC$K_DTYPE_T, "UCX$DEVICE"}; /* ** Assign a channel to the UCX device. */ status = sys$assign( &inet_dev, &channel, 0, 0); if (FAILURE(status)) { printf("Failed to assign channel to UCX device.\n"); exit(status); } /* ** Prompt user for server name. ** Lookup the corresponding internet address. */ got_host = FALSE; while( got_host == FALSE ) { printf("Enter name of remote host:"); gets( server_name ); if ((he = gethostbyname( server_name )) == NULL) printf("Error, gethostbyname failed\n"); else got_host = TRUE; } memcpy((char *)&remote_host.SIN$L_ADDR, he->h_addr, he->h_length ); /* ** Create the socket */ sck_parm[0] = UCX$C_TCP; /* TCP/IP protocol */ sck_parm[1] = INET_PROTYP$C_STREAM; /* stream type of socket */ status = sys$qiow( 3, /* Event flag */ channel, /* Channel number */ IO$_SETMODE, /* I/O function */ iosb, /* I/O status block */ 0, 0, &sck_parm, 0, /* P1 Socket creation parameter */ 0, 0, 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) { printf("Failed to create and bind the device socket.\n"); exit(status); } /* ** Connect to specified host and port number */ remote_host.SIN$W_FAMILY = UCX$C_AF_INET; /* INET family */ remote_host.SIN$W_PORT = htons( SERVER_PORT ); /* port number */ status = sys$qiow( 3, /* Event flag */ channel, /* Channel number */ IO$_ACCESS, /* I/O function */ iosb, /* I/O status block */ 0, 0, 0, 0, &rhst_adrs, /* P3 Remote IP address */ 0, 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) { printf("Failed to connect to remote host.\n"); exit(status); } /* ** Send message to the server ** ** Note that this client/server combination implements ** a very simple message protocol - the client simply ** sends a text string of any size and then closes ** the connection. The server simply keeps reading ** (and printing the received text to SYS$OUTPUT) ** until the connection is closed by the client. ** */ strcpy( buf, "Hello there Mr. Server. Are you listening?"); status = sys$qiow( 3, /* Event flag */ channel, /* Channel number */ IO$_WRITEVBLK, /* I/O function */ iosb, /* I/O status block */ 0, 0, buf, /* P1 buffer */ strlen(buf), /* P2 buffer length */ 0, 0, 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) printf("Failed to write to socket.\n"); /* ** Close the socket (optional - could just let $DASSGN ** perform the close) */ status = sys$qiow( 3, channel, IO$_DEACCESS, iosb, 0, 0, 0, 0, 0, 0, 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) printf("Failed to close the socket.\n"); /* ** Deassign the UCX device channel. */ status = sys$dassgn( channel ); if (FAILURE(status)) printf("Failed to deassign the channel.\n"); } ------------------ end of UCX_CLIENT.C --------------------------------- ------------------- start of UCX_SERVER.C -------------------------------- #define SERVER_PORT 65000 /* server's port number to use */ /* ** ** UCX-compatible Stream (TCP) Server using BGDRIVER $QIOs ** ** This example implements a simple single-threaded server ** that does the following: ** 1. Bind and listen at port 65000 ** 2. Accept an incoming connection on port 65000 ** 3. Read whatever the client sends and print it ** to the server's SYS$OUTPUT ** 4. When the client closes it's end of the connection, ** close down the server's end and then go back to step 2 ** waiting to accept the next incoming connection ** ** To build against TCPware on VAX: ** $ CC/DEFINE=TCPWARE UCX_SERVER ** $ LINK UCX_SERVER,TCPWARE:UCX$IPC/LIB,SYS$INPUT/OPTIONS ** SYS$SHARE:VAXCRTL/SHARE ** ** To build against TCPware on AXP: ** $ CC/DEFINE=TCPWARE/STANDARD=VAXC UCX_SERVER ** $ LINK UCX_SERVER ** ** To build against UCX on VAX: ** $ CC UCX_SERVER ** $ LINK UCX_SERVER,SYS$LIBRARY:UCX$IPC/LIB,SYS$INPUT/OPTIONS ** SYS$SHARE:VAXCRTL/SHARE ** ** To build against UCX on AXP: ** $ CC/STANDARD=VAXC UCX_SERVER ** $ LINK UCX_SERVER ** ** Note: This example was originally based on the UCX$TCP_SERVER_QIO.C ** example provided by Digital's UCX product in the directory ** SYS$EXAMPLES:UCX.DIR */ #include #include #include #include #include #include #ifdef TCPWARE #include "TCPWARE_INCLUDE:ucx$inetdef.h" /* Provided by TCPware install */ #else /* assume UCX */ #include /* Provided during UCX installation */ #endif /* ** Macros to check VMS success/failure status */ #define SUCCESS(status) (status&1) #define FAILURE(status) (!(status&1)) /* ** Values for boolean variables */ #define FALSE 0 #define TRUE 1 /*********************************/ /* Server Program */ /*********************************/ main() { int status; /* For return status */ short listen_chan; /* Listening device's channel */ short con_chan; /* Accepted connection's channel */ short sck_parm[2]; /* Socket creation parameter */ short iosb[4]; /* I/O status block */ char buf[512]; /* buffer for received data */ int buflen = 512; /* buffer length */ unsigned char *ria; /* remote host's internet address */ int r_retlen; /* length of remote host name structure */ int connected; /* boolean */ /* ** Socket name structures for local and remote endpoints ** (filled in during IO$_ACCESS|IO$M_ACCEPT) */ struct SOCKADDRIN local_host, remote_host; /* ** UCX item list 2 descriptor used for local host name ** (filled in during IO$_ACCESS|IO$M_ACCEPT) */ struct IL2 { unsigned int il2_length; char *il2_address; } lhst_adrs = {sizeof local_host, &local_host}; /* ** UCX item list 3 descriptor used for remote host name ** (filled in during IO$_ACCESS|IO$M_ACCEPT) */ struct IL3 { unsigned int il3_length; char *il3_address; unsigned int il3_retlen; } rhst_adrs = {sizeof remote_host, &remote_host, &r_retlen}; /* ** String descriptor used during $ASSIGN BG0: */ struct dsc$descriptor inet_dev = {10, DSC$K_CLASS_S, DSC$K_DTYPE_T, "UCX$DEVICE"}; /* ** Socket options item list (used during IO$_SETMODE that ** creates the listen socket to set the REUSEADDR option) */ int optval = 1; /* option value = 1 means turn it on */ struct { short len, param; int *ptr; } item_list[] = {{sizeof(optval), UCX$C_REUSEADDR, &optval}}, options = {sizeof(item_list), UCX$C_SOCKOPT, item_list}; /* ** Assign a channel for the listen socket. */ status = sys$assign(&inet_dev, &listen_chan, 0, 0); if (FAILURE(status)) { printf("Failed to assign channel to UCX device.\n"); exit(status); } /* ** Create the listen socket and set the REUSEADDR option. */ sck_parm[0] = UCX$C_TCP; /* TCP protocol */ sck_parm[1] = INET_PROTYP$C_STREAM; /* stream type of socket */ status = sys$qiow( 3, /* Event flag */ listen_chan, /* Channel number */ IO$_SETMODE, /* I/O function */ iosb, /* I/O status block */ 0, 0, &sck_parm, 0, /* P1 Socket creation parameter */ 0, 0, &options, 0); /* P5 Socket option descriptor */ if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) { printf("Failed to create the device socket.\n"); exit(status); } /* ** Bind to specified port number (after REUSEADDR is set above). */ local_host.SIN$W_FAMILY = UCX$C_AF_INET; /* INET (TCP/IP) family */ local_host.SIN$L_ADDR = UCX$C_INADDR_ANY; /* any address */ local_host.SIN$W_PORT = htons( SERVER_PORT ); /* server's port # */ status = sys$qiow( 3, /* Event flag */ listen_chan, /* Channel number */ IO$_SETMODE, /* I/O function */ iosb, /* I/O status block */ 0, 0, 0, 0, &lhst_adrs, /* P3 local socket name */ 5, /* P4 Connection backlog */ 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) { printf("Failed to bind the device socket.\n"); exit(status); } else printf("Port is bound and listening\n"); /****************************************************/ /* Main server loop */ /* 1. accept new connection */ /* 2. read message from client */ /* 3. print message to SYS$OUTPUT */ /* 4. close connection */ /* 5. go to step 1 */ /****************************************************/ while ( 1 ) { /* sit in this loop forever (break with CTRL-Y) */ /* ** Assign channel for the new connection */ status = sys$assign(&inet_dev, &con_chan, 0, 0); if (FAILURE(status)) { printf("Failed to assign channel for accept.\n"); exit(status); } /* ** Accept a connection from a client. */ status = sys$qiow( 3, /* Event flag */ listen_chan, /* listen channel number */ IO$_ACCESS|IO$M_ACCEPT, /* I/O function */ iosb, /* I/O status block */ 0, 0, 0, 0, &rhst_adrs, /* P3 Remote IP address*/ &con_chan, /* P4 Channel for new socket */ 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) { printf("Failed to accept a connection from a client.\n"); exit(status); } /* ** Print out the connecting client's internet address & port */ ria = &remote_host.SIN$L_ADDR; printf("Connection from host: %d.%d.%d.%d, port: %d\n", ria[0], ria[1], ria[2], ria[3], htons(remote_host.SIN$W_PORT)); /* ** Read data from client (and write it to SYS$OUTPUT) ** until client closes it's end of the connection ** ** Note that this client/server combination implements ** a very simple message protocol - the client simply ** sends a text string of any size and then closes ** the connection. The server simply keeps reading ** (and printing the received text to SYS$OUTPUT) ** until the connection is closed by the client. ** ** Recall that the TCP byte-stream does NOT guarantee ** a one-to-one correspondence of client writes to server ** reads, so you may see several reads for the client's ** single write. (With the short message used here, ** you probably won't, but you might.) */ connected = TRUE; while ( connected ) { /* ** Read I/O buffer */ status = sys$qiow( 3, /* Event flag */ con_chan, /* Channel number */ IO$_READVBLK, /* I/O function */ iosb, /* I/O status block */ 0, 0, /* no AST, no AST parm */ buf, /* P1 buffer */ buflen, /* P2 buffer length */ 0, 0, 0, 0); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) { if (status != SS$_LINKDISCON ) { /* SS$_LINKDISCON indicates normal termination */ printf("Failed to read from socket.\n"); printf("iosb[0] = 0x%x\n", iosb[0] ); printf("iosb[1] = 0x%x\n", iosb[1] ); } connected = FALSE; /* terminate while loop */ } else { /* ** All is well, print message */ buf[iosb[1]] = '\0'; /* make sure message is null-terminated */ printf ("Received text: %s\n", buf); } } /* end of connected while loop */ /* ** Shut down the socket that was connected to the client ** and deassign the channel used with that socket. */ status = sys$qiow( 3, con_chan, IO$_DEACCESS|IO$M_SHUTDOWN, iosb, 0, 0, 0, 0, 0, UCX$C_DSC_ALL, /* P4 Discard all packets */ 0, 0 ); if (SUCCESS(status)) status = iosb[0]; if (FAILURE(status)) printf("Failed to shut down the client socket.\n"); status = sys$dassgn(con_chan); if (FAILURE(status)) printf("Failed to deassign the client socket.\n"); printf("FYI: Port is still bound and listening...\n"); } /* end of main while loop */ /* ** Note: We don't explicitly shutdown the listen socket ** and deassign the listen channel. This will be done ** during image run-down whey you CTRL-Y EXIT the program. */ } -------------------- end of UCX_SERVER.C ------------------------------------ ------------------- start of UCX Auxiliary server memo ----------------------- Q: "Where can I find an example of UCX Master Server programming?" A: Using UCX (or TCPware's UCX emulation), you will use what is called the "UCX Auxiliary Server" See the DEC TCP/IP Services manual "System Services and C Socket Programming". Appendix A.6 - "TCP/IP Server Accepting a Connection from the Auxiliary Server Example" - is a simple example. If you want to use sockets, your server program does the following: sock_fd = socket(UCX$C_AUXS, 0, 0) and now 'sock_fd' can be used with other socket calls (read/write/etc) To add this as a service in TCPware: 1) Write a STARTUP.COM file that will RUN your program 2) Add the service via: NETCU ADD SERVICE BG_TCP - /USER=SYSTEM - /INPUT=disk:[dir]STARTUP.COM If you were using an actual UCX system, you would add the service via: UCX> SET SERVICE - /USER=SYSTEM - /PROCESS= - /PORT= - /FILE=disk:[dir]STARTUP.COM For example: The TCP/IP server from the DEC TCP/IP programming guide is: #include #include #include #include "tcpware_include:ucx$inetdef.h" main() { int s; char *message = "Hello, world!\n"; s = socket(UCX$C_AUXS, 0, 0); write(s, message, strlen(message)); close(s); } Compile and link this with TCPware via: $ CC HELLO_WORLD.C $ LINK HELLO_WORLD,TCPWARE:UCX$IPC/LIB,SYS$INPUT/OPTIONS SYS$SHARE:VAXCRTL.EXE/SHARE Create STARTUP.COM containing: $ RUN SYS$SYSDEVICE:[USER]HELLO_WORLD.EXE Add as a service at port 7777 via: $ NETCU ADD SERVICE 7777 BG_TCP /USER=SYSTEM - /INPUT=SYS$SYSDEVICE:[USER]STARTUP.COM To verify the service is there: $ NETCU SHOW SERVICE TCPware(R) for OpenVMS NETCP Services: Protocol Port Active Limit Connects Errors Image -------- ---- ------ ----- -------- ------ ----- <...various TCPware services...> BG_TCP 7777 0 none 0 0 Use the service (from another host): (Merope) $ TELNET THETA 7777 %TCPWARE_TELNET-I-TRYING, trying THETA.process.com,7777 (192.42.95.72,7777) ... %TCPWARE_TELNET-I-ESCCHR, escape (attention) character is "^\" Hello, world! ----------------------- end of UCX_AUX_SERVER.TXT ----------------------------