Here is something I picked up on the Net. This file contains exaples of client and servers using several protocols, might be very usefull. BSD Sockets Sockets are a generalized networking capability first introduced in 4.1cBSD and subsequently refined into their current form with 4.2BSD. The sockets feature is available with most current UNIX system releases. (Transport Layer Interface (TLI) is the System V alternative). Sockets allow communication between two different processes on the same or different machines. Internet protocols are used by default for communication between machines; other protocols such as DECnet can be used if they are available. To a programmer a socket looks and behaves much like a low level file descriptor. This is because commands such as read() and write() work with sockets in the same way they do with files and pipes. The differences between sockets and normal file descriptors occurs in the creation of a socket and through a variety of special operations to control a socket. These operations are different between sockets and normal file descriptors because of the additional complexity in establishing network connections when compared with normal disk access. For most operations using sockets, the roles of client and server must be assigned. A server is a process which does some function on request from a client. As will be seen in this discussion, the roles are not symmetric and cannot be reversed without some effort. This description of the use of sockets progresses in three stages: The use of sockets in a connectionless or datagram mode between client and server processes on the same host. In this situation, the client does not explicitly establish a connection with the server. The client, of course, must know the server's address. The server, in turn, simply waits for a message to show up. The client's address is one of the parameters of the message receive request and is used by the server for response. The use of sockets in a connected mode between client and server on the same host. In this case, the roles of client and server are further reinforced by the way in which the socket is established and used. This model is often referred to as a connection-oriented client-server model. The use of sockets in a connected mode between client and server on different hosts. This is the network extension of Stage 2, above. The connectionless or datagram mode between client and server on different hosts is not explicitly discussed here. Its use can be inferred from the presentations made in Stages 1 and 3. _________________________________________________________________ Socket Creation Using socketpair() #include #include int socketpair(int af, int type, int protocol, int sv[2]) socketpair() results in the creation of two connected sockets. sv[] is the array where the file descriptors for the sockets are returned. Each descriptor in sv[] is associated with one end of the communications link. Each descriptor can be used for both input and output. This means that full two-way communication between a parent process and one child process is possible. Normally, one descriptor is reserved for use by a parent process and the other descriptor is used by a child process. The parent process closes the descriptor used by the child process. Conversely, the child process closes the descriptor used by the parent process. fork() is still required to pass one of the sockets to a child. af represents the domain or address family to which the socket belongs. type is the type of socket to create. Domains refer to the area where the communicating processes exist. Commonly used domains include: * AF_UNIX for communication between processes on one system; * AF_INET for communication between processes on the same or different systems using the DARPA standard protocols (IP/UDP/TCP). Socket type refers to the "style" of communication. The two most commonly used values include: * SOCK_STREAM: A stream of data with no record boundaries. Delivery in a networked environment is guaranteed; if delivery is impossible, the sender receives an error indicator. * SOCK_DGRAM: A stream of records, each of a given size. Delivery in a networked environment is not guaranteed. A protocol value of 0 is very common. This permits the system to choose the first protocol which is permitted with the pair of values specified for family and type. Example (borrowed from 4.3BSD IPC Tutorial by Stuart Sechrest) #define DATA1 "test string 1" #define DATA2 "test string 2" #include #include #include main() { int sockets[2], child; char buf[1024]; /* Get the socket pair */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) { printf("error %d on socketpair\n", errno); exit(1); } /* create child process */ if ((child = fork()) == -1) { printf("fork error %d\n", errno); exit(1); } if (child != 0) { /* this is the parent */ /* close child's end of socket */ close(sockets[0]); /* read message from child */ if (read(sockets[1], buf, sizeof(buf)) < 0) { printf("error %d reading socket\n", errno); exit(1); } printf("-->%s\n", buf); /* write message to child */ if (write(sockets[1], DATA1, sizeof(DATA1)) < 0) { printf("error %d writing socket\n", errno); exit(1); } /* finished */ close(sockets[1]); } else { /* the child */ /* close parent's end of socket */ close(sockets[1]); /* send message to parent */ if (write(sockets[0], DATA2, sizeof(DATA1)) < 0) { printf("error %d writing socket\n", errno); exit(1); } /* get message from parent */ if (read(sockets[0], buf, sizeof(buf)) < 0) { printf("error %d reading socket\n", errno); exit(1); } printf("-->%s\n", buf); /* finished */ close(sockets[0]); } } _________________________________________________________________ Socket Creation Using socket() #include #include int socket(int af, int type, int protocol) socket() is very similar to socketpair() except that only one socket is created instead of two. This is most commonly used if the process you wish to communicate with is not a child process. The af, type, and protocol fields are used just as in the socketpair() system call. On success, a file descriptor to the socket is returned. On failure, -1 is returned and errno describes the problem. _________________________________________________________________ Giving a Socket a Name - bind() #include #include int bind(int s, struct sockaddr *name, int namelen) Recall that, using socketpair(), sockets could only be shared between parent and child processes or children of the same parent. With a name attached to the socket, any process on the system can describe (and use) it. In a call to bind(), s is the file descriptor for the socket, obtained from the call to socket(). name is a pointer to a structure of type sockaddr. If the address family is AF_UNIX (as specified when the socket is created), the structure is defined as follows: struct sockaddr { u_short sa_family; char sa_data[14]; }; name.sa_family should be AF_UNIX. name.sa_data should contain up to 14 bytes of a file name which will be assigned to the socket. namelen gives the actual length of name, that is, the length of the initialized contents of the data structure. A value of 0 is return on success. On failure, -1 is returned with errno describing the error. Example: struct sockaddr name; int s; name.sa_family = AF_UNIX; strcpy(name.sa_data, "/tmp/sock"); if((s = socket(AF_UNIX, SOCK_STREAM, 0) < 0) { printf("socket create failure %d\n", errno); exit(0); } if (bind(s, &name, strlen(name.sa_data) + sizeof(name.sa_family)) < 0) printf("bind failure %d\n", errno); _________________________________________________________________ Specifying a Remote Socket - connect() #include #include int connect(int s, struct sockaddr *name, int namelen) The bind() call only allows specification of a local address. To specify the remote side of an address connection the connect() call is used. In the call to connect, s is the file descriptor for the socket. name is a pointer to a structure of type sockaddr: struct sockaddr { u_short sa_family; char sa_data[14]; }; As with the bind() system call, name.sa_family should be AF_UNIX. name.sa_data should contain up to 14 bytes of a file name which will be assigned to the socket. namelen gives the actual length of name. A return value of 0 indicates success, while a value of -1 indicates failure with errno describing the error. A sample code fragment: struct sockaddr name; name.sa_family = AF_UNIX; strcpy(name.sa_data, "/tmp/sock"); if (connect(s, &name, strlen(name.sa_data) + sizeof(name.sa_family)) < 0) { printf("connect failure %d\n", errno); } _________________________________________________________________ Sending to a Named Socket - sendto() int sendto(int s, char *msg, int len, int flags, struct sockaddr *to, int tolen) This function allows a message msg of length len to be sent on a socket with descriptor s to the socket named by to and tolen, where tolen is the actual length of to. flags will always be zero for our purposes. The number of characters sent is the return value of the function. On error, -1 is returned and errno describes the error. An example: struct sockaddr to_name; to_name.sa_family = AF_UNIX; strcpy(to_name.sa_data, "/tmp/sock"); if (sendto(s, buf, sizeof(buf), 0, &to_name, strlen(to_name.sa_data) + sizeof(to_name.sa_family)) < 0) { printf("send failure\n"); exit(1); } _________________________________________________________________ Receiving on a Named Socket - recvfrom() #include #include int recvfrom(int s, char *msg, int len, int flags, struct sockaddr *from, int *fromlen) This function allows a message msg of maximum length len to be read from a socket with descriptor s from the socket named by from and fromlen, where fromlen is the actual length of from. The number of characters actually read from the socket is the return value of the function. On error, -1 is returned and errno describes the error. flags may be 0, or may specify MSG_PEEK to examine a message without actually receiving it from the queue. If no message is available to be read, the process will suspend waiting for one unless the socket is set to nonblocking mode (via an ioctl call). The system I/O call read() can also be used to read data from a socket. _________________________________________________________________ Disposing of a Socket #include void close(int s). The I/O call close() will close the socket descriptor s just as it closes any open file descriptor. Example - sendto() and recvfrom() /* receiver */ #include #include struct sockaddr myname; struct sockaddr from_name; char buf[80]; main() { int sock; int fromlen, cnt; sock = socket(AF_UNIX, SOCK_DGRAM, 0); if (sock < 0) { printf("socket failure %d\n", errno); exit(1); } myname.sa_family = AF_UNIX; strcpy(myname.sa_data, "/tmp/tsck"); if (bind(sock, &myname, strlen(myname.sa_data) + sizeof(name.sa_family)) < 0) { printf("bind failure %d\n", errno); exit(1); } cnt = recvfrom(sock, buf, sizeof(buf), 0, &from_name, &fromlen); if (cnt < 0) { printf("recvfrom failure %d\n", errno); exit(1); } buf[cnt] = '\0'; /* assure null byte */ from_name.sa_data[fromlen] = '\0'; printf("'%s' received from %s\n", buf, from_name.sa_data); } /* sender */ #include #include char buf[80]; struct sockaddr to_name; main() { int sock; sock = socket(AF_UNIX, SOCK_DGRAM, 0); if (sock < 0) { printf("socket failure %d\n", errno); exit(1); } to_name.sa_family = AF_UNIX; strcpy(to_name.sa_data, "/tmp/tsck"); strcpy(buf, "test data line"); cnt = sendto(sock, buf, strlen(buf), 0, &to_name, strlen(to_name.sa_data) + sizeof(to_name.sa_family)); if (cnt < 0) { printf("sendto failure %d\n", errno); exit(1); } } _________________________________________________________________ A Refinement: Client-Server Connections Sockets can be used to write client-server applications using a connection-oriented client-server technique. Some characteristics of this technique include: * The server can handle multiple client requests for connection and service. * The server responds to any one client's request independently of all other clients. * A client knows how to establish a connection with the server. The client-server connection, when established, remains in existence until either the client or the server explicitly breaks it, much like a trouble-free telephone call. The socket used for this connection is called a connection-oriented socket. The socket type is specified as SOCK_STREAM. As a result, the process receiving a message processes that message by the following rules: * The data transmitted has no boundaries. * All bytes in a received message must be read before the next message can be processed. * Bytes in a received message can be read in a loop program control structure since no data bytes are discarded. * The server will usually fork() a child process upon establishment of a client connection. * This child server process is designed to communicate with exactly one client process. * The child server process performs the requested service for its connected client. * The child server process terminates when the service request has been completed. Functions listen() and accept() enable the server to listen for service requests. read() and write() may be used by client and server to send/receive messages; send() and recv() may also be used. _________________________________________________________________ Make a Socket a Listen-only Connection Endpoint - listen() #include #include int listen(int s, int backlog) listen establishes the socket as a passive endpoint of a connection. It does not suspend process execution. No messages can be sent through this socket. Incoming messages can be received. s is the file descriptor associated with the socket created using the socket() system call. backlog is the size of the queue of waiting requests while the server is busy with a service request. The current system-imposed maximum value is 5. 0 is returned on success, -1 on error with errno indicating the problem. Example: #include #include int sockfd; /* socket file descriptor */ if(listen(sockfd, 5) < 0) printf ("listen error %d\n", errno); _________________________________________________________________ Connection Establishment by Server - accept() #include #include int accept(int sockfd, struct sockaddr *name, int *namelen) The accept() call establishes a client-server connection on the server side. (The client requests the connection using the connect() system call.) The server must have created the socket using socket(), given the socket a name using bind(), and established a listen queue using listen(). sockfd is the socket file descriptor returned from the socket() system call. name is a pointer to a structure of type sockaddr as described above struct sockaddr { u_short sa_family; char sa_data[14]; }; Upon successful return from accept(), this structure will contain the protocol address of the client's socket. The data area pointed to by namelen should be initialized to the actual length of name. Upon successful return from accept, the data area pointed to by namelen will contain the actual length of the protocol address of the client's socket. If successful, accept() creates a new socket of the same family, type, and protocol as sockfd. The file descriptor for this new socket is the return value of accept(). This new socket is used for all communications with the client. If there is no client connection request waiting, accept() will block until a client request is queued. accept() will fail mainly if sockfd is not a file descriptor for a socket or if the socket type is not SOCK_STREAM. In this case, accept() returns the value -1 and errno describes the problem. _________________________________________________________________ Data Transfer over Connected Sockets - send() and recv() Two additional data transfer library calls, namely send() and recv(), are available if the sockets are connected. They correspond very closely to the read() and write() functions used for I/O on ordinary file descriptors. #include #include int send(int sd, char *buf, int len, int flags) int recv(int sd, char * buf, int len, int flags) In both cases, sd is the socket descriptor. For send(), buf points to a buffer containing the data to be sent, len is the length of the data and flags will usually be 0. The return value is the number of bytes sent if successful. If not successful, -1 is returned and errno describes the error. For recv(), buf points to a data area into which the received data is copied, len is the size of this data area in bytes, and flags is usually either 0 or set to MSG_PEEK if the received data is to be retained in the system after it is received. The return value is the number of bytes received if successful. If not successful, -1 is returned and errno describes the error. _________________________________________________________________ A Connection-Oriented Example - listen(), accept() /* Generic program structure for establishing connection-oriented client-server environment. */ /* server program */ #include #include #include #include #include struct sockaddr myname; char buf[80]; main() { int sock, new_sd, adrlen, cnt; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { printf("server socket failure %d\n", errno); perror("server: "); exit(1); } myname.sa_family = AF_UNIX; strcpy(myname.sa_data, "/tmp/billb"); adrlen = strlen(myname.sa_data) + sizeof(myname.sa_family); unlink("/tmp/billb"); /* defensive programming */ if (bind(sock, &myname, adrlen) < 0) { printf("server bind failure %d\n", errno); perror("server: "); exit(1); } if (listen(sock, 5) < 0 { printf("server listen failure %d\n", errno); perror("server: "); exit(1); } /* Ignore child process termination. */ signal (SIGCHLD, SIG_IGN); /* Place the server in an infinite loop, waiting on connection requests to come from clients. In practice, there would need to be a clean way to terminate this process, but for now it will simply stay resident until terminated by the starting terminal or the super-user. */ while (1) { if (new_sd = accept(sock, &myname, &adrlen)) < 0 { printf("server accept failure %d\n", errno); perror("server: "); exit(1); } /* Create child server process. Parent does no further processing -- it loops back to wait for another connection request. */ printf("Socket address in server %d is %x, %s\n", getpid(), myname.sa_data, myname.sa_data); if (fork() == 0) { /* child process */ close (sock); /* child does not need it */ /* . . . . . */ cnt = read(new_sd, buf, strlen(buf)); printf ("Server with pid %d got message %s\n", getpid(), buf); strcpy (buf, "Message to client"); cnt = write(new_sd, buf, strlen(buf)); printf("Socket address in server %d is %x, %s\n", getpid(), myname.sa_data, myname.sa_data); /* . . . . . */ close (new_sd); /* close prior to exiting */ exit(0); } /* closing bracket for if (fork() ... ) */ } /* closing bracket for while (1) ... ) */ } /* closing bracket for main procedure */ /* client program */ #include #include #include #include char buf[80]; struct sockaddr myname; main() { int sock, adrlen, cnt; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { printf("client socket failure %d\n", errno); perror("client: "); exit(1); } myname.sa_family = AF_UNIX; strcpy(myname.sa_data, "/tmp/billb"); adrlen = strlen(myname.sa_data) + sizeof(myname.sa_family); if (connect( sock, &myname, adrlen) < 0) { printf("client connect failure %d\n", errno); perror("client: "); exit(1); } /* . . . . . */ strcpy(buf, "Message sent to server"); cnt = write(sock, buf, strlen(buf)); cnt = read(sock, buf, strlen(buf)); printf("Client with pid %d got message %s\n", getpid(), buf); printf("Socket address in server %d is %x, %s\n", getpid(), myname.sa_data, myname.sa_data); /* . . . . . */ exit(0); } _________________________________________________________________ Connecting Across a Network Sending data across a network is done in much the same way as sending data on a single machine. Establishing a connection is more difficult as a simple file name is not common among different machines. This is why networking domains other than AF_UNIX exist. The following pages describe several of the routines available to establish a network connection. All these functions are for use with the AF_INET domain. The concept of network addressing is discussed first. _________________________________________________________________ Network Addresses - The IP Address The IP host address, or more commonly just IP address, is used to identify hosts connected to the Internet. IP stands for Internet Protocol and refers to the Internet Layer of the overall network architecture of the Internet. An IP address is a 32-bit quantity interpreted as 4 8-bit numbers or octets. Each IP address uniquely identifies the participating user network, the host on the network, and the class of the user network. An IP address is usually written in a dotted-decimal notation of the form N1.N2.N3.N4, where each Ni is a decimal number between 0 and 255 decimal (00 through ff hexadecimal). Addresses are controlled and assigned by the Internet Network Information Center at SRI International. There are currently 5 defined classes of networks, called A, B, C, D and E; classes A, B and C are available to users. Each interface on a host system connected to the Internet must have its own IP address. A brief synopsis of IP address classes A, B, and C is contained in the following table. Class/Attribute ABC Address Format N.H.H.HN1.N2.H.HN1.N2.N3.HFirst Octet0 < N < 127128 SubnetsAs the number of hosts on a given network grew large and more geographic ally separated, network management considerations and physical layer limitation s such as maximum cable length gave impetus to research into possible changes i n network installations without massive disruptions. Also, the use of heteroge neous physical layer networks (e.g., Ethernet and Token Ring) with the same IP network address was increasing. The concept of subnet was introduced to help solve these problems. The benefit s for Class A and Class B networks especially are worth the subnet planning and implementation effort. The basic idea in subnetworking (a common word also used is subnetting) is to p artition the host identifier portion of the IP address into two parts: 1. A subnet address within the network address itself; and 2. A host address on the subnet. For example, a common Class B address format is N1.N2.S.H, where N1.N2 identifi es the Class B network, the 8-bit S field identifies the subnet, and the 8-bit H field identifies the host on the subnet. __________________________________________________________________________ Network Host Names Using IP addresses to access hosts on a network is fine for the IP software. M ost people are more comfortable with names, and procedures for both proper name construction and translation of these names into IP addresses has been in exis tence for some time. The most commonly used is the Domain Name System (DNS), o ccasionally but inaccurately referred to as the Domain Name Service. Naming in DNS is done hierarchically, that is, in a tree-structure format, much like the UNIX file system naming. The top two levels are controlled by the Internet Ne twork Information Center (NIC) at SRI International. At the top of the domain are two-letter country designators and three-letter (u sually) general category designators within the USA. Some examples are: * fr -- France * gov -- government * nz -- New Zealand * com -- commercial business * us -- USA * edu -- educational institution * uk -- United Kingdom * mil -- military The next level usually identifies the institution. For example: * ibm -- IBM Corporation * utdallas -- UT-D * ti -- Texas Instruments (TI) * nasa -- NASA The combination of these should be enough to specify the name, type and locatio n of the organizational entity. For example: ukuug.uk -- UNIX Users Group in the United Kingdom utdallas.edu -- University of Texas at Dallas in the USA ti.com -- Texas Instruments in the USA Further hierarchies may be established within these organizations and are contr olled locally. Some examples of host names are: csservr2.utdallas.edu -- host csservr2 at UT-D pac1.pac.sc.ti.com -- host pac1 within domain pac within domain sc at TI sunk.ssc.gov -- host sunk at the Superconducting SuperCollider Laboratory DNS and other software help in maintenance of these naming conventions and in t he translation of host names to IP addresses and vice versa. __________________________________________________________________________ The /etc/hosts File The correspondence between host names and IP addresses is maintained in a file called hosts in the (top-level) directory /etc. On most systems, any user can read this file. (A word of caution: on many systems, printing this file may be injurious to local paper supply!!) Entries in this file look like the following: 127.0.0.1 localhost 192.217.44.208 snoopy beagle hound metlife 153.110.34.18 bugs wabbit wascal 153.110.34.19 elmer 153.110.34.20 sam Note that more than one name may be associated with a given IP address. This f ile is used when converting from IP address to host name and vice versa. __________________________________________________________________________ Selecting a Service Port The protocol specifications of the protocols used in the AF_INET domain require specification of a port. The port number is used to determine which process o n the remote machine to talk to. Certain network services have been assigned Well Known Ports. The port assignm ents to network services can be found in the file /etc/services. Selection of a Well Known Port involves searching this file and is done with the following f unctions: #include struct servent *getservbyname(char *name, char *proto) struct servent *getservbyport(int port, char *proto) The two options for proto in each call are tcp for stream oriented communicatio ns, and udp for datagram oriented communications. port is the (known) port num ber when the service name is requested, while name is the character string cont aining the service name when the port number is requested. The return value for each function is a pointer to a structure with the followi ng form: struct servent { char *s_name; /* official name of service */ char **s_aliases; /* alias service name list */ long s_port; /* port service resides at */ char *s_proto; /* protocol to use */ }; If a program does not need to communicate with a Well Known Port it is possible to choose an unused port for use by a program. Illustration of this technique is contained in the sample programs below. __________________________________________________________________________ Putting a Host Program Address Together Once a host address and port number are known then the complete process address must be put together in a form that may be used by the system calls already co vered. The structures set up to allow this follow: #include /* * Internet address (a structure for historical reasons) */ struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un; #define s_addr S_un.S_addr /* can be used for most tcp & ip code */ }; /* * Socket address, internet style. */ struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; Filling in the fields for sockaddr_in will produce an Internet version of a soc ket address. __________________________________________________________________________ Finding a Machine Address Finding an address that can be used to connect to a remote machine is done with either of the following commands: #include struct hostent *gethostbyname(char *name) struct hostent *gethostbyaddr(char *addr, int len, int type) name contains the host name for which the IP address is needed. addr points to a structure of type in_addr and len is the size in bytes of this structure. I n this discussion type is always AF_INET since the discussion is limited to use of IP addresses on the Internet. Both calls return a pointer to a host entry structure. This structure has the following form: struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses from name server */ #define h_addr h_addr_list[0] /* address for backward compatibility */ }; __________________________________________________________________________ Network Byte Order for Numbers Not all machine architectures use the same internal representations for numbers . For instance, in host systems based on Intel microprocessors and the DEC VAX architecture bytes are stored in increasing memory locations in reverse or "li ttle-endian" order. All other systems store bytes in increasing memory locatio ns in "normal" or "big-endian" order. Left as is, communications between, e.g., IBM-compatible PCs and Sun Microsyste ms workstations would be difficult at best. To overcome this all data sent ove r a network is converted to Network Byte Order which is, in the context of the above discussion, "big-endian" order. Routines for converting data between a host's internal representation and Netwo rk Byte Order are: #include #include u_long htonl(u_long hostlong); u_short htons(u_short hostshort); u_long ntohl(u_long netlong); u_short ntohs(u_short netshort); These functions are macros and result in the insertion of conversion source cod e into the calling program. On little-endian machines the code will change the values around to network byte order. On big-endian machines no code is insert ed since none is needed; the functions are defined as null. __________________________________________________________________________ Network Address Conversion Routines An Internet address is usually written and specified in the dotted-decimal nota tion described above. Internally it becomes part of a structure of type in_add r. To convert between these two representations two functions are available. #include #include #include /* in_addr structure */ unsigned long inet_addr(char *ptr) char *inet_ntoa(struct in_addr inaddr) inet_addr() converts a character string in dotted-decimal notation to a 32-bit Internet address. The return value is not consistent, unfortunately. The corr ect return should be a pointer to a structure of type in_addr but many systems, following an older convention, return only the internal representation of the dotted-decimal notation. The man pages will clarify the situation for the host system on which the function is used. inet_ntoa() expects a structure of type in_addr as a parameter (note that the s tructure itself is passed, not a pointer) and returns a pointer to a character string containing the dotted-decimal representation of the Internet address. __________________________________________________________________________ Internet Connection-Oriented Client-Server Example The next two sections contain sample programs for server and client when connec tion-oriented communication across the Internet (using TCP) is requested. The server and client programs are named vcserver and vcclient, respectively. As b uilt, the programs have some restrictions: * vcserver should be started first as a background job. * The port number reported by vcserver should be copied for use by the client. * The process id of vcserver should also be copied, for use in terminating vcserver when the test is finished. * vcclient is then started. If vcclient is started on the same host as vcserver is running, the command lin e to start vcclient is vcclient localhost server-port-number If vcclient is started on a host different from the host on which vcserver is r unning, the command line to start vcclient is vcclient server-host-name server-port-number vcserver will remain resident until the user terminates it. The cleanest way t o do this is kill -15 pid where pid is the process id of vcserver. __________________________________________________________________________ Server Source Listing - vcserver.c /* vcserver.c -- TCP network (virtual circuit) server */ #include #include #include #include #include /* sockaddr_in structure */ /* This entry allows the program to look up the name of the host and any alias names associated with it. */ #include /* /etc/hosts table entries */ main (int argc, char *argv[]) { int rc, /* system call return code */ new_sd, sock, /* server/listen socket descriptors */ adrlen, /* sockaddr length */ cnt; /* number of bytes I/O */ struct sockaddr_in myname; /* Internet socket name */ struct sockaddr_in *nptr; /* ptr to get port number */ struct sockaddr addr; /* generic socket name */ char buf[80]; /* I/O buffer, kind of small */ /* For lookup in /etc/hosts file. */ struct hostent *hp, *gethostbyaddr(); /* Identify the server process. */ printf("\nThis is the network server with pid %d\n", getpid() ); /* As in UNIX domain sockets, create a "listen" socket */ if (( sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { printf("network server socket failure %d\n", errno); perror("network server"); exit(1); /* Initialize the fields in the Internet socket name structure. */ myname.sin_family = AF_INET; /* Internet address */ myname.sin_port = 0; /* System will assign port # */ myname.sin_addr.s_addr = INADDR_ANY; /* "Wildcard" */ /* Bind the Internet address to the Internet socket */ if (bind(sock, &myname, sizeof(myname) ) < 0 ) { close(sock); /* defensive programming */ printf("network server bind failure %d\n", errno); perror("network server"); exit(2); } /* Get the port number assigned to the Internet socket. getsockname() obtains the port number associated with the bound socket and returns it as part of the information in the sockaddr addr structure. Note that, since the port number is not passed directly by this program to any client, the only way to "advertise" it is to print it, that is, send it to the user's stdout. Other than this printout, this code is not intrinsic to the connectivity process. */ adrlen = sizeof(addr); /* need int for return value */ if ( ( rc = getsockname( sock, &addr, &adrlen ) ) < 0 ) { printf("setwork server getsockname failure %d\n", errno); perror("network server"); close (sock); exit(3); } /* DEBUG CODE: the generic address "addr" is used to return the socket value obtained from the getsockname() call. Print this information. In the generic structure definition, all but the address family is defined as a char string. After this call, the generic address structure addr is used to hold information about the client process. */ printf("\nAfter getsockname():"); printf(" server listen socket data\n"); printf("\taddr.sa_family field value is: %d\n", addr.sa_family); printf("\taddr.sa_data string is %d bytes long;\n", sizeof ( addr.sa_data ) ); printf("\taddr.sa_data string is:"); for ( cnt = 0: cnt < sizeof (addr.sa_data); cnt++) printf(" %x", addr.sa_data[cnt]); printf("\n"); /* Now "advertise" the port number assigned to the socket. In this example, this port number must be used as the second command line parameter when starting up the client process. */ /* Note the use of the pointer nptr, with a different mapping of the allocated memory, to point to the generic address structure. */ nptr = (struct sockaddr_in *) &addr; /* port # */ printf("\n\tnetwork server: server has port number: %d\n", ntohs ( nptr -> sin_port ) ); /* Mark the socket as a "listen-only" or passive socket */ if ( listen ( sock, 5 ) < 0 ) { printf("network server bind failure %d\n", errno); perror("network server"); close (sock); exit(4); } /* Debug output: information contained in myname structure (the Internet socket). */ printf("Server has set up client socket with values:\n"); printf("\tInternet address is %lx\n", myname.sin_addr.s_addr); printf("\tPort number used is %d\n", myname.sin_port); printf("\tInternet family ID is %d\n", myname.sin_family); printf("\tValues are filled in after connection request "); printf("is accepted."); /* Set up "infinite loop" to listen for clients. Since the structure "myname" is bound to the listen socket, the socket structure name and socket length parameter values are omitted from the accept call. The bound values are used. */ while (1) { if ( ( new_sd = accept ( sock, 0, 0 ) ) < 0 ) { printf("network server accept failure %d\n", errno); perror("network server"); close (sock); exit(5); } /* Fork child process to handle client service request */ if ( ( fork() ) == 0 ) { /* Child process */ int pid; pid = getpid(); /* PID of child process */ close (sock); /* Do not need listen socket in child. */ /* Find out who the client is. Note the use of the generic address structure addr to hold information about the (connected) client. */ if ((rc = getpeername( new_sd, &addr, &adrlen )) < 0) { printf("network server %d getpeername failure %d\n", pid, errno); perror("network server"); close(new_sd); exit(6); } /* Just for grins, "announce" the client. Note that, since pointer nptr is of type struct sockaddr_in, the field names as defined in the structure template sockaddr_in can be used to access values in the addr generic structure. */ printf("\n\tnetwork server %d:", pid); printf(" client socket from host %s\n", inet_ntoa ( nptr -> sin_addr ) ); printf("\t has port number %d\n",nptr -> sin_port); /* Now find all names associated with the client; this is the reason for the /etc/hosts file lookup declarations. */ if (( hp = gethostbyaddr (&nptr -> sin_addr,4,AF_INET)) != NULL ) { printf ("\tfrom hostname: %s\n\twith aliases: ", hp -> h_name ); while ( *hp -> h_aliases ) printf ("\n\t\t\t%s", *hp -> h_aliases++ ); printf("\n\n"); } else { printf("network server %d ", pid); printf("gethostbyaddr failure %d\n", h_errno); perror("network server"); } /* Exchange data with client. Clear buffer first. */ do { /* Take your pick, depending on system pedigree. The System V function has not been tested as of this edition. */ bzero( buf, sizeof(buf)); /* zero buf, BSD call. */ /* memset (buf,0,sizeof(buf)); /* zero buf, S5. */ /* Read message from remote client; if message length = 0, quit. */ if (( cnt = read (new_sd, buf, sizeof(buf))) < 0 ) { printf("network server %d ", pid); printf("socket read failure &d\n", errno); perror("network server"); close(new_sd); exit(7); } else if (cnt == 0) { printf("network server received message"); printf(" of length %d\n", cnt); printf("network server closing"); printf(" client connection...\n"); close (new_sd); continue; /* break out of loop */ } else { /* Print out message received from client. Send a message back. */ printf("network server %d received message",pid); printf(" of length %d\n", cnt); printf("network server %d received", pid)); printf(" the message %s\n", buf); bzero (buf, sizeof(buf)); /* zero buf, BSD. */ /* memset(buf,0,sizeof(buf)); /* zero buf, S5. */ strcpy(buf, "Message from server to client"); write (new_sd, buf, sizeof(buf)); } /* end of message-print else */ } /* end of do loop statement */ while (cnt != 0); /* do loop condition */ exit(0); /* Exit child process */ } /* End of if-child-process true condition */ else /* Not child process; must be parent process */ close (new_sd); /* Parent doesn't need work socket. */ } /* end of while (1) */ } /* end of main procedure */ Client Source Listing - vcclient.c /* vcclient.c -- TCP network (virtual circuit) client */ #include #include #include #include #include /* sockaddr_in structure */ /* This entry allows the program to look up the name of the host and any alias names associated with it. */ #include /* /etc/hosts table entries */ main (argc, argv) int argc; char *argv[]; /* Expected command line parameters: argv[0] -- name of executable argv[1] -- the host name to which connection is desired argv[2] -- the port number to be used by the client: the value is the port number assigned to the server by the server's host system. This is not elegant, but is very useful for debugging the connectivity code. For example, if the host name ( argv[1] ) were speficied as "localhost" both client and server could run on the same system but the network connectivity code would still be fully exercised. */ { int sock, /* socket descriptor */ val, /* scratch variable */ cnt; /* number of bytes I/O */ struct sockaddr_in myname; /* Internet socket name (addr) */ struct sockaddr_in *nptr; /* pointer to get port number */ char buf[80]; /* I/O buffer, kind of small */ /* For lookup in /etc/hosts file. */ struct hostent *hp, *gethostbyaddr(); /* Check that the user supplied all parameters on the command line. If so, convert argv[2] to integer; copy it into the sin_port field of the myname structure. Use the htons function to insure that the value is stored in network byte order. */ if ( argc < 3 ) { printf("network client failure: required parameters"); printf(" missing from the command line\n"); printf("network client: usage"); printf("[executable-name] [host name] [port number]\n"); exit(1); } /* As in UNIX domain, create a client socket to request service */ if (( sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { printf("network client socket failure %d\n", errno); perror("network client"); exit(2); } /* Convert user-supplied port number to integer. There is no consistency check possible in this set-up. */ myname.sin_port = htons( atoi(argv[2]) ); /* Server port # */ myname.sin_family = AF_INET; /* Internet domain */ /* For display purposes only, print out the host name and the converted port number. */ printf("network client %s will try to connect to host %s\n", argv[0], argv[1]); printf("network client %s will use port number %d\n", argv[0], ntohs ( myname.sin_port ) ); /* Obtain server host information. */ hp = gethostbyname ( argv[1] ); /* This is mainly debug code; if it becomes necessary to insert it (or if it just feels more comfortable to have it in place!!!) don't remove it when all is working well. Rather, leave it in place as a comment. */ if ( hp == NULL ) { printf("network client gethostbyname failure %d\n", errno); perror("network client"); close ( sock ); exit(3); } else { printf("\nServer information obtained via"); printf(" gethostbyname:\n"); printf("\tThe official host name is %s\n", hp -> h_name); printf("\tThe address type is %d\n", hp -> h_addrtype); printf("\tThe length of the address is %d bytes\n", hp -> h_length); printf("\tThe first host address is %lx\n", ntohl ( * (int * ) hp -> h_addr_list[0] ) ); printf("\tAlias names for the host are:\n"); while ( *hp -> h_aliases ) printf( "\t\t%s\n", *hp -> h_aliases++ ); } /* Use either memcpy or bcopy as appropriate (System V vs BSD). */ bcopy ( hp -> h_addr_list[0], &myname.sin_addr.s_addr, hp -> h_length ); /* memcpy ( &myname.sin_addr.s_addr, hp -> h_addr_list[0], hp -> h_length ); */ /* More debug code: Verify the contents of structure myname prior to trying to connect to the (remote) server. */ printf("\nInformation provided in client's"); printf(" connect request\n"); printf("\tRemote host address is %lx\n", ntohl ( myname.sin_addr.s_addr ) ); printf("\tPort number supplied is %d\n", ntohs ( myname.sin_port ) ); printf("\tInternet family ID is %d\n", myname.sin_family); printf("\tsin_zero character string is: %s\n", myname.sin_zero); /* Establish socket connection with (remote) server. */ if ( ( connect ( sock, &myname, sizeof(myname) ) ) < 0 ) { printf("network client %s connect failure %d\n", argv[0], errno); perror("network client"); close (sock); exit(4); } /* Exchange data with client. Clear buffer bytes first. */ /* Take your pick, depending on your system's pedigree. The System V function has not been tested. */ bzero ( buf, sizeof( buf) ); /* zero buffer, BSD. */ /* memset ( buf, 0, sizeof( buf) ); /* zero buffer S5. */ strcpy ( buf, "Message from client to server" ); write ( sock, buf, sizeof(buf) ); /* Now read message sent back by server. */ if ( ( cnt = read (sock, buf, sizeof(buf) ) ) < 0 ) { printf("network client socket read failure &d\n", errno); perror("network client"); close(sock); exit(5); } else printf("network client received the message %s\n", buf); /* Now send a message with 0 bytes. */ bzero ( buf, sizeof( buf) ); /* zero buffer, BSD. */ /* memset ( buf, 0, sizeof( buf) ); /* zero buffer S5. */ write ( sock, buf, 0 ); close (sock); exit(0); } /* end of main procedure */