Annotated code sample
The following code implements a client and server that communicate
using TLI over a TCP/IP 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 TCP/IP
connection, then writes it to the standard output. The client
communicates with the server running on the host specified on the
command line.
This code includes the following features:
-
The server is concurrent: that is, it communicates with the client
through a child process that uses a transport endpoint (conn_fd)
different from the one that the parent process listens on for
incoming connection requests (listen_fd). This, along with a
value for bind
->qlen
that is greater than one
(five, in this example) in the call to t_bind, allows
the server to respond to (five) outstanding connect indications.
-
The client and server implement an application-level protocol to ensure
that the client receives all the data that the server sends.
Example TLI client
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <fcntl.h>
4 #include <sys/tiuser.h>
5 #include <sys/socket.h>
6 #include <netdb.h>
7 #include <sys/netinet/in.h>
8 #define TCP_PATH "/dev/inet/tcp"
9 #define TCP_UNIQUE 5555
10 extern int t_errno;
11 main (int argc, char *argv[])
12 {
13 int flags = 0;
14 char *netpath = TCP_PATH;
15 int fd;
16 int nbytes;
17 struct t_call *sndcall;
18 struct hostent *hp;
19 struct sockaddr_in srv_addr;
20 struct {
21 unsigned short length;
22 char buf[1024];
23 } msg;
24 if (argc < 2) {
25 printf("usage: %s <hostname>\n", argv[0]);
26 exit(1);
27 }
28 if ((fd = t_open(netpath, O_RDWR, (struct t_info *) NULL)) < 0) {
29 t_error("t_open failed");
30 exit(1);
31 }
32 if (t_bind(fd, (struct t_bind *) NULL,
33 (struct t_bind *) NULL) < 0) {
34 t_error("t_bind error");
35 exit(2);
36 }
37 if ((sndcall =
38 (struct t_call *)t_alloc(fd, T_CALL_STR, T_ADDR)) == NULL) {
39 t_error("t_alloc failed");
40 exit(3);
41 }
42 hp = gethostbyname(argv[1]);
43 if (hp == 0) {
44 fprintf(stderr, "%s: unknown host\n", argv[1]);
45 exit(1);
46 }
47 sndcall->udata.len = 0;
48 sndcall->addr.len = sizeof(srv_addr);
49 sndcall->addr.buf = (char *)&srv_addr;
50 srv_addr.sin_family = hp->h_addrtype;
51 srv_addr.sin_port = TCP_UNIQUE;
52 memcpy((caddr_t)&srv_addr.sin_addr, hp->h_addr, hp->h_length);
53 if (t_connect(fd, sndcall, (struct t_call *) NULL) < 0) {
54 t_error("t_connect failed for fd");
55 exit(4);
56 }
57 while ((nbytes = t_rcv(fd, &msg, sizeof(msg), &flags)) >=
58 sizeof(msg.length)) {
59 if (fwrite(msg.buf, 1, msg.length, stdout) < 0) {
60 fprintf(stderr, "fwrite failed");
61 exit(5);
62 }
63 if (msg.length == 0)
64 break;
65 }
66 if (nbytes == sizeof(msg.length) && msg.length == 0) {
67 if (t_snddis(fd, NULL) < 0) {
68 t_error("t_snddis failed");
69 exit(6);
70 }
71 exit(0);
72 }
73 if (nbytes < sizeof(msg.length)) {
74 fprintf(stderr, "received invalid message, hanging up");
75 t_snddis(fd, (struct t_call *) NULL);
76 exit(7);
77 }
78 else {
79 if (t_errno == TLOOK) {
80 if (t_look(fd) == T_DISCONNECT) {
81 printf("Got T_DISCONNECT, doing t_rcvdis");
82 if (t_rcvdis(fd, (struct t_discon *) NULL) < 0) {
83 t_error("t_rcvdis failed");
84 exit(8);
85 }
86 exit(0);
87 }
88 }
89 else {
90 t_error("t_rcv failed");
91 exit(9);
92 }
93 }
94 }
.
.
.
line 3-
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 29).
lines 5-7-
includes the header files that define the TCP/IP socket-specific
structures such as the hostent hostentry structure, socket types,
address families and options.
line 8-
defines a symbol for the device name of the connection-oriented
SCO TCP/IP transport.
line 9-
defines the unique network-byte-ordered 16-bit port number the
server listens on, that the client will connect to.
line 13-
initializes the flags argument that will be passed
into t_rcv.
line 14-
assigns the device name of the transport provider to open.
line 18-
declares the variable hp to be of type hostent
data structure to hold the host entry specific data which is
returned by gethostbyname() in line 42.
line 19-
declares the variable srv_addr to be of
the struct sockaddr_in data structure.
lines 20-23-
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.
lines 24-27-
check user input for correct usage.
line 28-
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 32-33-
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 37-38-
allocate a struct t_call data structure.
The fields of the structure will be assigned the
socket address information.
line 42-
a gethostbyname() of the argument specified on the command
line is performed in order to fetch the network host (on which
server is running) specific data to be used later (see line 53.)
line 47-
since user data cannot be exchanged during connection establishment
phase, set the udata.len field to 0.
line 48-
initializes the length field of the struct sockaddr_in
data structure.
line 49-
initializes the pointer field in the struct sockaddr_in
data structure with the address of the buffer where the Internet
socket address information will be stored.
lines 50-52-
assigns the address family type (gotten from the call to
gethostbyname()), the unique 16-bit network-byte-ordered
port number, and the 32-bit network-byte-ordered network/host ID, to the
corresponding fields of the variable declared to be of type
struct sockaddr_in.
line 53-
connects to the server.
line 57-58-
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 59-
writes msg.length bytes of data from msg.buf
to the standard output.
lines 63-72-
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 that provides only an abortive
disconnect (using t_snddis).
lines 73-77-
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 79-88-
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 86).
lines 90-91-
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 57. The client could check what error
has occurred by examining the value of t_errno.
In this example, the client simply gives up.
Example TLI server
1 #include <stdio.h>
2 #include <signal.h>
3 #include <stropts.h>
4 #include <sys/tiuser.h>
5 #include <fcntl.h>
6 #include <errno.h>
7 #include <sys/socket.h>
8 #include <sys/netinet/in.h>
9 #define TCP_PATH "/dev/inet/tcp"
10 #define DISCONNECT -1
11 #define TCP_UNIQUE 5555
12 extern int t_errno;
13 void run_server(int);
14 int accept_call(int, struct t_call *);
15 void connrelease();
16 int conn_fd;
17 char *netpath = TCP_PATH;
18 main(int argc, char *argv[])
19 {
20 int listen_fd;
21 struct t_bind *bind;
22 struct t_call *call;
23 struct sockaddr_in srv_addr;
24 if ((listen_fd =
25 t_open(netpath, O_RDWR, (struct t_info *) NULL)) < 0) {
26 t_error("t_open failed for listen_fd");
27 exit(1);
28 }
29 if ((bind = (struct t_bind *)t_alloc(listen_fd, T_BIND_STR, T_ADDR))
30 == NULL) {
31 t_error("t_alloc of t_bind structure failed");
32 exit(2);
33 }
34 bind->qlen = 5;
35 bind->addr.len = sizeof(srv_addr);
36 bind->addr.buf = (char *)&srv_addr;
37 srv_addr.sin_family = AF_INET;
38 srv_addr.sin_addr.s_addr = INADDR_ANY;
39 srv_addr.sin_port = TCP_UNIQUE;
40 if (t_bind(listen_fd, bind, bind) < 0) {
41 t_error("t_bind failed for listen_fd");
42 exit(3);
43 }
44 if ((call = (struct t_call *)t_alloc(listen_fd, T_CALL_STR, T_ALL))
45 == NULL) {
46 t_error("t_alloc failed");
47 exit(5);
48 }
49 call->addr.len = sizeof(srv_addr);
50 call->udata.maxlen = 0;
51 while (1) {
52 if (t_listen(listen_fd, call) < 0) {
53 t_error("t_listen failed for listen_fd");
54 exit(6);
55 }
56 if ((conn_fd = accept_call(listen_fd, call)) != DISCONNECT)
57 run_server(listen_fd);
58 }
59 }
60 int
61 accept_call(int listen_fd, struct t_call *call)
62 {
63 int resfd;
64 if ((resfd = t_open(netpath, O_RDWR, (struct t_info *) NULL))
65 < 0) {
66 t_error("t_open for responding fd failed");
67 exit(7);
68 }
69 if (t_bind(resfd, (struct t_bind *) NULL,
70 (struct t_bind *) NULL) < 0) {
71 t_error("t_bind for responding fd failed");
72 exit(8);
73 }
74 if (t_accept(listen_fd, resfd, call) < 0) {
75 if (t_errno == TLOOK) {
76 if (t_rcvdis(listen_fd, (struct t_discon *) NULL) < 0) {
77 t_error("t_rcvdis failed for listen_fd");
78 exit(9);
79 }
80 if (t_close(resfd) < 0) {
81 t_error("t_close failed for responding fd");
82 exit(10);
83 }
84 return(DISCONNECT);
85 }
86 t_error("t_accept failed");
87 exit(11);
88 }
89 return(resfd);
90 }
91 void
92 connrelease()
93 {
94 exit(0);
95 }
96 void
97 run_server(int listen_fd)
98 {
99 int nbytes;
100 FILE *logfp;
101 struct {
102 unsigned short length;
103 char buf[1024];
104 } msg;
105 switch (fork()) {
106 case -1:
107 perror("fork failed");
108 exit(12);
109 default: /* parent */
110 /* close conn_fd and go up to listen again */
111 if (t_close(conn_fd) < 0) {
112 t_error("t_close failed for conn_fd");
113 exit(13);
114 }
115 return;
116 case 0: /* child */
117 /* close listen_fd and do service */
118 if (t_close(listen_fd) < 0) {
119 t_error("t_close failed for listen_fd");
120 exit(14);
121 }
122 if ((logfp = fopen("logfile", "r")) == NULL) {
123 perror("cannot open logfile");
124 exit(15);
125 }
126 signal(SIGPOLL, connrelease);
127 if (ioctl(conn_fd, I_SETSIG, S_INPUT) < 0) {
128 perror("ioctl I_SETSIG failed");
129 exit(16);
130 }
131 if (t_look(conn_fd) != 0) {
132 fprintf(stderr, "t_look returned unexpected event");
133 exit(17);
134 }
135 while ((nbytes = fread(msg.buf, 1, 1024, logfp)) > 0) {
136 msg.length = nbytes;
137 if (t_snd(conn_fd, &msg, nbytes +
138 sizeof(msg.length), 0) < 0) {
139 t_error("t_snd failed");
140 exit(18);
141 }
142 }
143 msg.length = 0;
144 if (t_snd(conn_fd, &msg, 2, 0) < 0) {
145 t_error("can't send 0 bytes");
146 exit(19);
147 }
148 pause(); /* until disconnect indication arrives */
149 }
150 }
.
.
.
line 5-
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 25)
lines 7-8-
includes the header files that define the TCP/IP socket-specific
structures such as types, address families and options.
line 9-
defines a symbol for the device name of the connection-oriented
SCO TCP/IP transport.
line 11-
defines the unique 16-bit port number that's network byte ordered,
that the server will listen on.
line 16-
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. In this example, the variable
is global to avoid passing it around as a parameter. It is
referenced both by main and run_server.
line 20-
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 24-25-
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 29-30-
allocate the struct t_bind data structure
to be used in the call to t_bind.
line 34-
set the length of the queue for incoming connection requests to
five.
line 36-
points
addr.buf
to the socket structure.
lines 37-39-
assign values to the different fields of the socket structure.
The network-byte-ordered wildcard address is used for the 32-bit
network/host ID on line 38.
line 40-
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 44-45-
allocate the struct t_call data structure
to be used in the call to t_listen.
line 50-
since user data cannot be exchanged during the connection
establishment phase, the udata.maxlen field of call
should be 0 before the call to t_listen.
lines 51-59-
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 56-57-
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 TLI functions.
lines 60-150-
open a new transport endpoint, let
the transport bind any valid name to it, then accept
the incoming connection request on that endpoint.
line 64-
opens a new transport endpoint.
line 69-
binds the transport endpoint to the address information specified
in the srv_addr structure.
line 74-
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 75-85-
respond to an asynchronous event. The server assumes the
client is now trying to disconnect. The server responds by
consuming the disconnect event (line 76) and shutting
down the transport endpoint on which it intended to accept the
connection (line 80).
An alternative is to call t_look
after line 75 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 84-
returns DISCONNECT to indicate that the connection request
was aborted by the client.
line 89-
returns the value of the transport endpoint on which the
connection request has been accepted.
lines 92-95-
declare the signal handler that is set in line 126. 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.
lines 101-104-
declare the variable to hold the next chunk of data to be read
from the log file and transmitted to a client.
line 105-
spawns a child process to communicate with the client.
lines 109-115-
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 116-120-
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 122-125-
open the log file for reading.
The child process executes these lines.
lines 126-150-
list the remaining instructions to be executed by the child process.
line 126-
sets connrelease as the SIGPOLL signal handler.
line 127-
establishes that a SIGPOLL signal will be
generated if an unexpected message arrives at the transport
endpoint denoted by conn_fd.
lines 135-142-
read from the log file in 1K chunks and send both the data and
the number of bytes read to the client.
lines 143-147-
signal the end of the file by sending a message with the length
field set to zero.
line 148-
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.
Previous topic:
t_unbind
© 2003 Caldera International, Inc. All rights reserved.
SCO OpenServer Release 5.0.7 -- 11 February 2003