Chapter 5: Developing applications over NetBIOS using XTI

Table of contents

Chapter 5

Developing applications over NetBIOS using XTI

This chapter assumes you already know how to use the X/Open Transport Interface (XTI) over a non-NetBIOS transport provider. If not, read Chapter 3, ``Developing applications over TCP/IP using TLI'' before reading this chapter.

Using XTI over NetBIOS

When you write a client or server application using XTI to run over a NetBIOS transport provider, you use the same functions as you would for any other transport provider, except:

NetBIOS: from DOS to UNIX systems

NetBIOS (Network Basic Input/Output System) was the first implementation of a Local Area Network (LAN) by IBM® for its PCs. It offered both connection-oriented and connectionless transport service using character strings to name nodes on the network. Thus, NetBIOS provided a way of mapping network names to addresses that was transparent to the user-level application. This implementation also included the definition of an Application Programming Interface (API) to access NetBIOS services. Since then, many applications have been written to run on NetBIOS LANs offered by a variety of vendors.

At first NetBIOS was confined to the world of MS-DOS® and compatible operating systems. Later, implementations of NetBIOS transport providers were created for other operating systems, including the UNIX system. Applications written for NetBIOS providers can run on LANs that have a mixture of UNIX, DOS, OS/2 and other machines.

X/Open has defined a way to use XTI to write UNIX system clients and servers that access the services of any NetBIOS provider. The NetBIOS providers available from SCO are TCP/NetBIOS and NetBEUI. References in this chapter to SCO TCP/NetBIOS refer to the NetBIOS in Release 1.2.1 or later of SCO TCP/IP (also available as part of SCO Open Server and SCO Open Desktop Release 3.0 or later). References in this chapter to NetBEUI refer to Release 2.2 or later (available as part of Microsoft® LAN Manager for SCO Systems Release 2.2 or later.

NetBIOS names and addresses

The definition (in /usr/include/xti.h) of a NetBIOS transport address is:

   struct nb_addr {
   	char    nb_type;
   	char    nb_name[NB_NAMELEN];
   };
The value of nb_type is either NB_UNIQUE or NB_GROUP, and the value of NB_NAMELEN is 16. The constants NB_UNIQUE, NB_GROUP, and NB_NAMELEN (NetBIOS name length) are defined in xti.h.

A NetBIOS name consists of 16 alphanumeric bytes. This address identifies a client or server process to the LAN. When specifying NetBIOS names: 


Sending NetBIOS broadcast datagrams

To enable receipt of broadcast datagrams on a transport endpoint: 

  1. In the call to t_open, open the transport endpoint using the device name of a connectionless NetBIOS transport provider as the name parameter.

  2. In the call to t_bind, specify the NetBIOS name NB_BCAST_NAME and a queue length of zero. (NB_BCAST_NAME is defined in xti.h.)
You must use a connectionless NetBIOS provider to send broadcast datagrams. If you call t_bind for a connection-oriented transport provider, the function returns -1 and t_errno is set to TBADADDR.

Creating NetBIOS sessions

To establish a session with another host on the network: 

  1. Open the transport endpoint (with t_open) using the device name of a connection-oriented NetBIOS transport provider in the name parameter.

  2. If you are writing a client, specify the name of the server it will contact in the call to t_connect. If you are writing a server, specify the name assigned to the server in the call to t_bind.

How XTI functions behave differently over NetBIOS

Be aware of the following special considerations when using XTI functions over a NetBIOS provider. The XTI functions are listed in alphabetical order. 


t_accept

This function never returns any user data to the caller. Consequently, the value of call->udata.len is always zero.

If you call this function for a connectionless transport provider, the function returns -1 and t_errno is set to TNOTSUPPORT.


t_alloc

There are no special considerations when calling this function over a NetBIOS transport provider.


t_bind

In addition to specifying the name you wish to bind in the nb_name field of the struct nb_addr structure, you must set the nb_type field to either NB_UNIQUE or NB_GROUP. If the transport provider cannot bind the NetBIOS name to the transport endpoint, the t_bind returns -1 and t_errno is set to:

Only one process on the LAN can bind a given name with a type of NB_UNIQUE to a transport endpoint. If a process successfully binds a name with a type of NB_UNIQUE to a transport endpoint, and another process later attempts to bind the same name to a transport endpoint (whether it specifies NB_UNIQUE or NB_GROUP in the nb_type field), the error TADDRBUSY is returned to that other process. Only one process on the LAN can bind a name of type NB_UNIQUE.

However, multiple processes can bind the same name to their transport endpoints, provided that each one sets the nb_type field to NB_GROUP when calling t_bind. Group names are useful for logically segmenting the LAN. You can implement multicasting (the sending of datagrams to some, but not all of the hosts on a LAN) by specifying a group name when you send a datagram. 

t_close

There are no special considerations when calling this function over a NetBIOS transport provider. 

t_connect

If this function succeeds in establishing a connection, the NetBIOS name that the transport provider returns in rcvcall->addr.buf will always be the same as the name specified in sndcall->addr.buf.

You cannot send user data during session establishment. Therefore, sndcall->udata.len must be zero. This field will be set correctly if you call t_alloc to allocate sndcall.

Do not call this function over a connectionless transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.

With some NetBIOS providers, (but not SCO TCP/NetBIOS or NetBEUI) it is possible for the NetBIOS name you bound to the transport endpoint to be removed by the transport provider as a result of a name conflict resolution protocol. If the name is removed between the call to t_bind and the call to t_connect, t_connect returns -1 and t_errno is set to TBADF. See the documentation supplied with transport provider software for more information. 

t_error

There are no special considerations when calling this function over a NetBIOS transport provider. 

t_free

There are no special considerations when calling this function over a NetBIOS transport provider. 

t_getinfo

These fields are part of the the t_info structure returned by t_getinfo:

addr
always contains the value of sizeof(struct nb_addr).

options
always contains the value of -2. This indicates that no user-settable options are supported.

etsdu
always contains the value of -2. This indicates that you cannot send expedited data.

connect
always contains the value of -2. This indicates that you cannot send user data while establishing a connection.

discon
always contains the value of -2. This indicates that you cannot send user data while releasing a connection.

flags
always has the T_SENDZERO bit turned on. This indicates that you can send zero-length TSDUs (Transport Service Data Units).


t_getstate

There are no special considerations when calling this function over a NetBIOS transport provider. 

t_listen

You cannot send user data during connection establishment over a NetBIOS transport provider. Consequently, call->udata.len equals zero when the function returns.

Do not call this function over a connectionless transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.

With some NetBIOS providers, (but not SCO TCP/NetBIOS or NetBEUI) it is possible for the NetBIOS name you bound to the transport endpoint to be removed by the transport provider as a result of a name conflict resolution protocol. If the name is removed between the call to t_bind and the call to t_connect, t_connect returns -1 and t_errno is set to TBADF. See the documentation supplied with transport provider software for more information. 

t_look

Because you cannot send expedited data over NetBIOS transports, t_look never returns the events T_EXDATA and T_GOEXDATA. 

t_open

These fields are part of the the t_info structure returned by t_open:

addr
always contains the value of sizeof(struct nb_addr).

options
always contains the value of -2. This indicates that no user-settable options are supported.

etsdu
always contains the value of -2. This indicates that you cannot send expedited data.

connect
always contains the value of -2. This indicates that you cannot send user data while establishing a connection.

discon
always contains the value of -2. This indicates that you cannot send user data while releasing a connection.

flags
always has the T_SENDZERO bit turned on. This indicates that you can send zero-length TSDUs (Transport Service Data Units).


t_optmgmt

There are no special considerations when calling this function over a NetBIOS transport provider. 

t_rcv

Do not call this function over a connectionless transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.

Because you cannot send expedited data over NetBIOS transports, the T_EXPEDITED flag in the flags parameter is never set. 

t_rcvconnect

You cannot send user data during connection establishment over a NetBIOS transport provider. Consequently, call->udata.len equals zero when the function returns.

Do not call this function over a connectionless transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.


t_rcvdis

The disconnect reason codes listed below are supported by all valid NetBIOS transport providers that conform to the XPG4 specification of XTI. Following each reason code is the hexadecimal value of the equivalent reason code in PC NetBIOS.

NB_ABORT
session ended abnormally

NB_CLOSED
session closed

NB_NOANSWER
no answer, cannot find called name

NB_OPREJ
session open rejected
The function returns the reason code in the discon->reason field of the struct t_discon parameter. 

t_rcvrel

Do not call this function over either a connectionless transport endpoint or connection-oriented transport endpoint without orderly release. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.


NOTE: SCO TCP/NetBIOS and NetBEUI do not support connection-oriented service with orderly release. Therefore, you should never need to call t_rcvrel when using either of these two transports. Also, using t_rcvrel (and t_sndrel), even over NetBIOS transports that support these functions, limits portability and is not recommended.

When a transport user sends an orderly release request by calling t_sndrel, the transport user that receives the request sees the T_DISCONNECT event, not the T_ORDREL event. Therefore, the transport user receiving the request calls t_rcvdis to consume the event, not t_rcvrel.

To complete the orderly release handshake, the transport provider that receives the orderly release request transparently sends a release response to the originating transport endpoint. The originating transport user sees the T_ORDREL event, and consumes it by calling t_rcvrel.


t_rcvudata

Do not call this function over a connection-oriented transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.

If you want to receive broadcast datagrams, you must bind the name NB_BCAST_NAME to the transport endpoint. 

With some NetBIOS providers, (but not SCO TCP/NetBIOS or NetBEUI) it is possible for the NetBIOS name you bound to the transport endpoint to be removed by the transport provider as a result of a name conflict resolution protocol. If the name is removed between the call to t_bind and the call to t_connect, t_connect returns -1 and t_errno is set to TBADF. See the documentation supplied with transport provider software for more information. 

t_rcvuderr

Because there are no unit data error codes defined for XTI over NetBIOS, do not call this function. If you do, the function returns -1, and t_errno is set to TNOUDERR. 

t_snd

Do not set the T_EXPEDITED flag, because NetBIOS does not support the sending of expedited data. If you do, and you are using SCO TCP/NetBIOS, the function returns -1, and t_errno is set to TNOTSUPPORT. However, if you are using NetBEUI, the function returns zero and the flag is ignored.

Do not call this function over a connectionless transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.

The following information applies if you are using SCO TCP/NetBIOS or NetBEUI. If, when you call t_snd:

the function will return zero, but the data to be sent will be discarded. The next XTI function called will return TLOOK, and the call to t_look will return T_DISCONNECT.


t_snddis

Because NetBIOS transport providers do not support the sending of data with a disconnect request, set the call parameter to t_snddis to NULL.

Do not call this function over a connectionless transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.


t_sndrel

Do not call this function over either a connectionless transport endpoint or a connection-oriented transport endpoint without orderly release. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.


NOTE: SCO TCP/NetBIOS and NetBEUI do not support connection-oriented service with orderly release. Therefore, never call t_sndrel when using either of these two transports. Also, using t_sndrel (and t_rcvrel), even over NetBIOS transports that support these functions, limits portability and is not recommended.



t_sndudata

Do not call this function over a connection-oriented transport endpoint. If you do, the function returns -1, and t_errno is set to TNOTSUPPORT.

If you want to send a broadcast datagram with t_sndudata, you must specify the name NB_BCAST_NAME. Assign this name to the nb_name field of the struct nb_addr structure pointed to by unitdata->addr.buf. 

With some NetBIOS providers, (but not SCO TCP/NetBIOS or NetBEUI) it is possible for the NetBIOS name you bound to the transport endpoint to be removed by the transport provider as a result of a name conflict resolution protocol. If the name is removed between the call to t_bind and the call to t_connect, t_connect returns -1 and t_errno is set to TBADF. See the documentation supplied with transport provider software for more information.


t_sync

There are no special considerations when calling this function over a NetBIOS transport provider.


t_unbind

This function disassociates the NetBIOS name from the specified transport endpoint. The NetBIOS transport provider will also delete this name from the NetBIOS name table if no other transport endpoint is currently bound to that name.

How PC NetBIOS is different from XTI over NetBIOS

Unsupported PC NetBIOS services

XTI over NetBIOS does not provide user-level applications with interfaces for accessing the following services that are available with PC NetBIOS:

However, you can obtain adapter status (both remote and local) and session status information by using the appropriate ioctl. See ``Obtaining session and adapter status'' for more information.

Although you cannot set send and receive timeouts for an individual session, your NetBIOS transport provider may allow you to configure send and receive timeouts for all sessions over that provider. For information, see the documentation supplied with your NetBIOS transport.

Using XTI functions instead of NCBs

Both XTI and PC NetBIOS provide an interface that user-level applications can use to access the services of a NetBIOS transport provider. XTI uses a set of C language function calls, whereas PC NetBIOS uses Network Control Blocks (NCBs) and the DOS interrupt mechanism. Because the interfaces are not tied to the protocols that implement them, you can write some parts of your application using one interface and other parts using the other. For example, the client portion of an application can be written for a DOS machine using NCBs, while the server portion can be written for a UNIX system using XTI. As long as the same underlying transport (for example, SCO TCP/NetBIOS or NetBEUI) is used on both the client and the server, the client and server will be able to communicate with each other.

Shutting down a session with orderly release

The NetBIOS HANGUP command provides an orderly shutdown of a session. The XTI function t_sndrel provides a similar mechanism over transport providers that support connection-oriented service with orderly release.

If the transport provider does not support orderly release, you must call t_snddis to terminate a session. In this case, pending send and receive data may be lost. Set the call parameter to NULL, because NetBIOS providers do not support the sending of user data as part of a disconnect request.


NOTE: SCO TCP/NetBIOS and NetBEUI do not support connection-oriented service with orderly release. Therefore, do not call t_sndrel when using either of these two transports.

Obtaining session and adapter status

You can use the ioctl system call to obtain NetBIOS session status and adapter status (both local and remote) over SCO TCP/NetBIOS and NetBEUI. The header file /usr/include/sconetbios.h defines the constants and data structures you need. The following program provides an example of how to obtain session and adapter status over an existing transport endpoint. The transport endpoint can be either connection-oriented or connectionless.


Example 5-1 Example for obtaining session and adapter status

  1 #include <stropts.h>
  2 #include <xti.h>
  3 #include <sconetbios.h>

4 #define ASTAT_BUFSIZE 4096 5 #define SSTAT_BUFSIZE 4096

6 extern print_astatus(struct strioctl *); 7 extern print_sstatus(struct strioctl *);

8 demo_ioctl_user_function(int fd, char *name) 9 { 10 struct strioctl cmdbuf; 11 union { 12 struct astat adapter_status; 13 char name[NB_NAMELEN]; 14 } astat_buf; 15 struct sstat session_status;

16 cmdbuf.ic_cmd = NBIOCTL_ASTAT; 17 cmdbuf.ic_timout = 0; 18 cmdbuf.ic_dp = (char *)&astat_buf; 19 cmdbuf.ic_len = ASTAT_BUFSIZE;

20 memcpy(&astat_buf.name[0], name, NB_NAMELEN);

21 if (ioctl(fd, I_STR, &cmdbuf) < 0) { 22 perror("Error getting Adapter Status"); 23 exit(1); 24 } 25 print_astatus(&cmdbuf);

26 cmdbuf.ic_cmd = NBIOCTL_SSTAT; 27 cmdbuf.ic_timout = 0; 28 cmdbuf.ic_dp = (char *)&session_status; 29 cmdbuf.ic_len = SSTAT_BUFSIZE;

30 if (ioctl(fd, I_STR, &cmdbuf) < 0) { 31 perror("Error getting session status"); 32 exit(2); 33 } 34 print_sstatus(&cmdbuf); 35 }

line 1
includes the header file that defines constants and data structures you use to obtain session and adapter status.

lines 4-5
define the size of the buffers for the session and adapter status information. Define a buffer that is large enough to hold the largest possible session table on your system.

lines 6-7
declare functions in other source files that presumably format and display the session and adapter status information. They are shown here only as an illustration of one possible use of the status information.

line 8
declares the profile of the example function. The name parameter points to the name that identifies the ``adapter'' of interest for obtaining adapter status information. Any NetBIOS name registered in the name table for a given adapter can be used. The name parameter must point to an array of char of length NB_NAMELEN.

lines 10-14
declare the variable astat_buf, a buffer used to hold the name that is passed as a parameter in the ioctl call. This same buffer is used to hold the adapter status information returned by the NetBIOS transport. Because the same buffer is interpreted in two different ways, it is declared to be a union.

line 15
declares the variable session_status, a buffer to hold the session status information returned by the NetBIOS transport.

line 16
assigns the command field with a value indicating that adapter status is to be returned.

line 17
assigns the time out field with a zero, indicating that the ioctl call should block until the NetBIOS provider returns the requested information.

line 18
assigns the pointer field with the address of the data buffer. This buffer is initialized in line 18 with the NetBIOS name passed into the function on line 6. When the ioctl call returns successfully, this same buffer contains the adapter status information (the name passed in is overwritten in the process).

line 19
lets the NetBIOS driver know how large the buffer is.

line 20
copies the NetBIOS name passed into the function on line 6 to the data buffer astat_buf.

line 21
issues the request for adapter status.

lines 26-29
are similar to lines 16-19. However, the command field is set to retrieve session status, the pointer field is set to point to the session status buffer, and the length field is set to the length of the session status buffer.

line 30
issues the request for session status.

Annotated code sample

The following code implements a client and server that communicate using XTI over a NetBIOS connection. The server opens the file logfile, reads the contents in 1K chunks, and sends each chunk to the client. The client reads each chunk of data over the NetBIOS connection, then writes it to the standard output.

This code has at least two interesting features:



Example 5-2 Example NetBIOS client

  1 #include <stdio.h>
  2 #include <fcntl.h>
  3 #include <xti.h>

4 #define SRV_NAME "NB-XTI-DEMO-SERV" 5 #define RFCNBPATH "/dev/nbcots" 6 #define NBEPATH "/dev/netbeui"

7 extern int t_errno;

8 main (int argc, char *argv[]) 9 { 10 int flags = 0; 11 char *netpath = RFCNBPATH; 12 int fd; 13 int nbytes; 14 struct t_call *sndcall; 15 struct nb_addr srv_addr; 16 struct { 17 unsigned short length; 18 char buf[1024]; 19 } msg;

20 if ((fd = t_open(netpath, O_RDWR, (struct t_info *) NULL)) < 0) { 21 t_error("t_open failed"); 22 exit(1); 23 }

24 if (t_bind(fd, (struct t_bind *) NULL, 25 (struct t_bind *) NULL) < 0) { 26 t_error("t_bind error"); 27 exit(2); 28 }

29 if ((sndcall = 30 (struct t_call *)t_alloc(fd, T_CALL, T_ADDR)) == NULL) { 31 t_error("t_alloc failed"); 32 exit(3); 33 }

34 sndcall->addr.len = sizeof(srv_addr); 35 sndcall->addr.buf = (char *)&srv_addr;

line 2
includes the header file that defines the flags O_RDWR and O_NONBLOCK that can be passed in the flags argument to t_open (see line 21).

line 4
defines the name used to identify the server. Replace the string in this line with the name of the server on your network.

line 5
defines a symbol for the device name of the connection-oriented SCO TCP/NetBIOS transport.

line 6
defines a symbol for the device name of the connection-oriented NetBEUI transport.

line 7
declares the global variable used by all XTI routines to report an error.

line 10
initializes the flags argument that will be passed into t_rcv.

line 11
assigns the device name of the transport provider to open. Use the value NBEPATH (instead of RFCNBPATH) to access NetBEUI.

line 15
declares the variable that will contain the NetBIOS name of the server to be contacted.

lines 16-19
declare the data structure defined by this application for each chunk of data to be received. The buf field is a buffer to hold 1K of data. The length field is used to implement in the application itself a client/server handshake equivalent to orderly release.

line 20
opens a connection-oriented transport endpoint (netpath) with full duplex communication (O_RDWR). Because O_NONBLOCK is not specified along with O_RDWR, t_connect and t_rcv will block until either they succeed or an error is detected. To specify both O_RDWR and O_NONBLOCK, you would code
   t_open(netpath, O_NONBLOCK | O_RDWR, (struct t_info *)NULL)

lines 24-25
let the transport provider bind whatever name it chooses to the transport endpoint (the first NULL), and the client does not care to be informed what name was chosen (the second NULL). This is the normal case for a client.

lines 29-30
allocate a struct t_call data structure. The fields of the structure will be assigned the NetBIOS address information.

line 34
initializes the length field of the struct netbuf data structure.

line 35
initializes the pointer field in the struct netbuf data structure with the address of the buffer where the NetBIOS address information will be stored.
 36     srv_addr.nb_type = NB_UNIQUE;
 37     memcpy(srv_addr.nb_name, SRV_NAME, NB_NAMELEN);

38 if (t_connect(fd, sndcall, (struct t_call *) NULL) < 0) { 39 t_error("t_connect failed for fd"); 40 exit(4); 41 }

42 while ((nbytes = t_rcv(fd, &msg, sizeof(msg), &flags)) >= 43 sizeof(msg.length)) { 44 if (fwrite(msg.buf, 1, msg.length, stdout) < 0) { 45 fprintf(stderr, "fwrite failed"); 46 exit(5); 47 } 48 if (msg.length == 0) 49 break; 50 }

51 if (nbytes == sizeof(msg.length) && msg.length == 0) { 52 if (t_snddis(fd, NULL) < 0) { 53 t_error("t_snddis failed"); 54 exit(6); 55 } 56 exit(0); 57 }

58 if (nbytes < sizeof(msg.length)) { 59 fprintf(stderr, "received invalid message, hanging up"); 60 t_snddis(fd, (struct t_call *) NULL); 61 exit(7); 62 } 63 else { 64 if (t_errno == TLOOK) { 65 if (t_look(fd) == T_DISCONNECT) { 66 printf("Got T_DISCONNECT, doing t_rcvdis"); 67 if (t_rcvdis(fd, (struct t_discon *) NULL) < 0) { 68 t_error("t_rcvdis failed"); 69 exit(8); 70 } 71 exit(0); 72 } 73 } 74 else { 75 t_error("t_rcv failed"); 76 exit(9); 77 } 78 } 79 }

line 36
indicates that the name of the server to be contacted is a unique name, not a group name.

line 37
initializes the name field of the struct nb_addr structure with the name of the server.

line 38
connects to the server.

line 42
reads nbytes of data through the transport endpoint. The first two bytes are read into msg.length. The remaining bytes are read into msg.buf.

line 43
writes msg.length bytes of data from msg.buf to the standard output.

lines 48-57
check if the number of bytes sent was zero. This is the signal from the server that no more data will be sent. The client then exits normally. This is the protocol used by this sample application to implement the semantics of orderly release over a transport provider (NetBIOS) that provides only an abortive disconnect (using t_snddis).

lines 58-62
check if the client received the length field of the message. If not, something went wrong. The client bails out by disconnecting. A more robust client might implement an error recovery mechanism.

lines 64-73
check if an asynchronous event has occurred. If the client has received a disconnect request, it consumes the event by calling t_rcvdis, then exits normally (line 71).

lines 75-76
implement the client's error recovery algorithm. This code is reached only if an error has occurred on the call to t_rcv on line 42. The client could check what error has occurred by examining the value of t_errno. In this example, the client simply gives up.


Example 5-3 Example NetBIOS server

  1 #include <stdio.h>
  2 #include <signal.h>
  3 #include <stropts.h>
  4 #include <xti.h>
  5 #include <fcntl.h>

6 #define SRV_NAME "NB-XTI-DEMO-SERV" 7 #define RFCNBPATH "/dev/nbcots" 8 #define NBEPATH "/dev/netbeui" 9 #define DISCONNECT -1

10 extern int t_errno; 11 void run_server(int); 12 int conn_fd; 13 char *netpath = RFCNBPATH;

14 main(int argc, char *argv[]) 15 { 16 int listen_fd; 17 struct t_bind *bind; 18 struct t_call *call; 19 struct nb_addr srv_addr; 20 struct nb_addr client_addr;

21 if ((listen_fd = 22 t_open(netpath, O_RDWR, (struct t_info *) NULL)) < 0) { 23 t_error("t_open failed for listen_fd"); 24 exit(1); 25 }

26 if ((bind = (struct t_bind *)t_alloc(listen_fd, T_BIND, T_ADDR)) 27 == NULL) { 28 t_error("t_alloc of t_bind structure failed"); 29 exit(2); 30 }

line 12
declares the variable that will identify the transport endpoint to be used by the child process (to be spawned later) when it communicates with the client. The variable happens to be global in this example to avoid passing it around as a parameter. It is referenced both by main and run_server.

line 13
assigns the device name of the transport to be used. Use the value NBEPATH instead of RFCNBPATH to access NetBEUI.

line 16
declares the variable to be used by the parent process to identify the transport endpoint that the parent process will use to listen for incoming connection requests.

lines 21-22
open a connection-oriented full-duplex transport endpoint in non-blocking mode. The third argument is set to NULL in this example, indicating that the server does not care to examine any of the transport provider's attributes.

lines 26-27
allocate the struct t_bind data structure to be used in the call to t_bind.
 28     bind->qlen = 5;
 29     bind->addr.len = sizeof(srv_addr);
 30     bind->addr.buf = (char *)&srv_addr;

31 srv_addr.nb_type = NB_UNIQUE; 32 memcpy(srv_addr.nb_name, SRV_NAME, NB_NAMELEN);

33 if (t_bind(listen_fd, bind, bind) < 0) { 34 t_error("t_bind failed for listen_fd"); 35 exit(3); 36 }

37 if (memcmp(srv_addr.nb_name, SRV_NAME, NB_NAMELEN) != 0) { 38 fprintf(stderr, "t_bind bound wrong address\n"); 39 exit(4); 40 }

41 if ((call = (struct t_call *)t_alloc(listen_fd, T_CALL, T_ADDR)) 42 == NULL) { 43 t_error("t_alloc failed"); 44 exit(5); 45 } 46 call->addr.len = sizeof(srv_addr); 47 call->addr.buf = (char *)&client_addr;

48 while (1) { 49 if (t_listen(listen_fd, call) < 0) { 50 t_error("t_listen failed for listen_fd"); 51 exit(6); 52 } 53 if ((conn_fd = accept_call(listen_fd, call)) != DISCONNECT) 54 run_server(listen_fd); 55 } 56 }

line 28
sets the depth of the queue for incoming connection requests to five.

line 30
points addr.buf to the NetBIOS name structure.

lines 31-32
assign the server's well-known NetBIOS name. The name is termed ``well-known'' because all clients on the network are expected to know this name. The server expects to be the only one on the network with this name, so it assigns the name type a value of NB_UNIQUE.

line 33
binds the server's name and the length of the connection request queue to the transport endpoint. These are specified in the second argument (first occurrence of bind). The third argument points to the same bind data structure specified in the second argument. The transport provider will return in bind->qlen the actual value of the connection request queue length it can support, if that is less than the requested value.

lines 41-42
allocate the struct t_call data structure to be used in the call to t_listen.

lines 46-47
set up the call data structure. call->add.buf will point to the name of the client trying to connect to the server. This value will be filled in by the call to t_listen on line 49.

lines 48-56
loop forever listening for connection requests on one transport endpoint, accepting them on different transport endpoint, then spawning a child process to communicate with the client.

lines 53-54
accept the connection with the client on the new transport endpoint, conn_fd. The server parent process will continue to listen for incoming connection requests on listen_fd, while the child process spawned in run_server will respond to the client. The functions accept_call and run_server are application-level functions, not XTI functions.
 57 int
 58 accept_call(int listen_fd, struct t_call *call)
 59 {
 60     int resfd;

61 if ((resfd = t_open(netpath, O_RDWR, (struct t_info *) NULL)) 62 < 0) { 63 t_error("t_open for responding fd failed"); 64 exit(7); 65 }

66 if (t_bind(resfd, (struct t_bind *) NULL, 67 (struct t_bind *) NULL) < 0) { 68 t_error("t_bind for responding fd failed"); 69 exit(8); 70 }

71 if (t_accept(listen_fd, resfd, call) < 0) { 72 if (t_errno == TLOOK) { 73 if (t_rcvdis(listen_fd, (struct t_discon *) NULL) < 0) { 74 t_error("t_rcvdis failed for listen_fd"); 75 exit(9); 76 } 77 if (t_close(resfd) < 0) { 78 t_error("t_close failed for responding fd"); 79 exit(10); 80 } 81 return(DISCONNECT); 82 } 83 t_error("t_accept failed"); 84 exit(11); 85 }

86 return(resfd); 87 }

88 connrelease() 89 { 90 exit(0); 91 }

lines 57-87
open a new transport endpoint, let the transport bind any valid name to it, then accept the incoming connection request on that endpoint.

line 61
opens a new transport endpoint.

line 66
lets the transport select a NetBIOS name to bind to the endpoint.

line 71
accepts on resfd the connection request received earlier on listen_fd. By accepting the connection request on a file descriptor different from the one on which the connection request was received, the server can fork a child process to handle the client request, while the (parent) server continues to listen for new clients trying to connect on the original file descriptor.

lines 72-82
respond to an asynchronous event. The server assumes the client is now trying to disconnect. The server responds by consuming the disconnect event (line 73) and shutting down the transport endpoint on which it intended to accept the connection (line 77).

An alternative is to call t_look after line 72 to determine precisely what asynchronous event has occurred. The server can then respond appropriately. See the manual page for t_look for a description of the asynchronous events returned by this function.

line 81
returns DISCONNECT to indicate that the connection request was aborted by the client.

line 86
returns the value of the transport endpoint on which the connection request has been accepted.

lines 88-91
declare the signal handler that is set in line 122. If an unexpected input message arrives at the transport endpoint, this handler will be executed. The handler simply exits, tearing down the transport endpoint the server is listening on. The exit also kills all of the child processes that are communicating with clients, thus tearing down their transport endpoints. Clients will receive the T_DISCONNECT asynchronous event at their local transport endpoint.
 92 void
 93 run_server(int listen_fd)
 94 {
 95     int nbytes;
 96     FILE *logfp;

97 struct { 98 unsigned short length; 99 char buf[1024]; 100 } msg;

101 switch (fork()) {

102 case -1: 103 perror("fork failed"); 104 exit(12);

105 default: /* parent */ 106 /* close conn_fd and go up to listen again */ 107 if (t_close(conn_fd) < 0) { 108 t_error("t_close failed for conn_fd"); 109 exit(13); 110 } 111 return;

112 case 0: /* child */ 113 /* close listen_fd and do service */ 114 if (t_close(listen_fd) < 0) { 115 t_error("t_close failed for listen_fd"); 116 exit(14); 117 } 118 if ((logfp = fopen("logfile", "r")) == NULL) { 119 perror("cannot open logfile"); 120 exit(15); 121 }

lines 97-100
declare the variable to hold the next chunk of data to be read from the log file and transmitted to a client.

line 101
spawns a child process to communicate with the client.

lines 105-111
close conn_fd. The parent process executes these lines because it will not be using this transport endpoint. The transport endpoint associated with conn_fd is not destroyed, however, because the child process will leave it open to communicate with the client. The parent returns to the calling function to continue to listen for connections on listen_fd.

lines 112-116
close listen_fd. The child process executes these lines because it will not be using this transport endpoint. The transport endpoint associated with listen_fd is not destroyed, however, because the parent process will leave it open to listen for the next incoming connection request from a client.

lines 118-121
open the log file for reading. The child process executes these lines.
122         signal(SIGPOLL, connrelease);

123 if (ioctl(conn_fd, I_SETSIG, S_INPUT) < 0) { 124 perror("ioctl I_SETSIG failed"); 125 exit(16); 126 }

127 if (t_look(conn_fd) != 0) { 128 fprintf(stderr, "t_look returned unexpected event"); 129 exit(17); 130 }

131 while ((nbytes = fread(msg.buf, 1, 1024, logfp)) > 0) { 132 msg.length = nbytes; 133 if (t_snd(conn_fd, &msg, nbytes + 134 sizeof(msg.length), 0) < 0) { 135 t_error("t_snd failed"); 136 exit(18); 137 } 138 }

139 msg.length = 0;

140 if (t_snd(conn_fd, &msg, 2, 0) < 0) { 141 t_error("can't send 0 bytes"); 142 exit(19); 143 }

144 pause(); /* until disconnect indication arrives */ 145 } 146 }

lines 122-146
list the remaining instructions to be executed by the child process.

line 122
sets connrelease as the SIGPOLL signal handler.

line 123
establishes that a SIGPOLL signal will be generated if an unexpected message arrives at the transport endpoint denoted by conn_fd.

lines 131-138
read from the log file in 1K chunks and send both the data and the number of bytes read to the client.

lines 139-143
signal the end of the file by sending a message with the length field set to zero.

line 144
waits until the SIGPOLL signal is generated. This will occur when the client receives the message with the length field set to zero, because the client will send a disconnect indication to the server by calling t_rcvdis. The signal handler connrelease will then be called, which calls exit to terminate the process.

Getting more information

To get more information on the original definition of NetBIOS, read the IBM PC Network Technical Reference Manual (IBM Part Number 6322916).

To get more information on the official X/Open specification of the use of XTI over NetBIOS providers, see Appendix D of the X/Open CAE Specification: X/Open Transport Interface (XTI), January 1992, ISBN 1-872630-29-4.