Download Socket Programming: Interface and Implementation and more Study notes Communication in PDF only on Docsity!
Programming With Sockets 2
This chapter presents the socket interface and illustrates them with sample
programs. The programs demonstrate the Internet domain sockets.
Sockets are Multithread Safe
The interface described in this chapter is multithread safe. Applications that
contain socket function calls can be used freely in a multithreaded application.
SunOS Binary Compatibility
There are two major changes from SunOS 4.x that hold true for Solaris 2.x
releases. The binary compatibility package allows SunOS 4.x–based
dynamically linked socket applications to run in Solaris 2.x.
1. You must explicitly specify the socket library (-lsocket) on the
compilation line.
What Are Sockets page 12 Socket Tutorial page 14 Standard Routines page 31 Client-Server Programs page 34 Advanced Topics page 41 Moving Socket Applications to Solaris 2.x page 54
12 Transport Interfaces Programming Guide — November 1995
2. You must recompile all SunOS 4.x socket-based applications with the socket
library to run under Solaris 2.x. The differences in the two socket
implementations are outlined in “Moving Socket Applications to Solaris 2.x”
on page 54.
What Are Sockets
Sockets are the 4.2 Berkeley software distribution (BSD) UNIX interface to
network protocols. It has been an integral part of SunOS releases since 1981.
They are commonly referred to as Berkeley sockets or BSD sockets. Since the
days of early UNIX, applications have used the file system model of
input/output to access devices and files. The file system model is sometimes
called open-close-read-write after the basic system calls used in this model.
However, the interaction between user processes and network protocols are
more complex than the interaction between user processes and I/O devices.
A socket is an endpoint of communication to which a name can be bound. A
socket has a type and one associated process. Sockets were designed to
implement the client-server model for interprocess communication where:
- The interface to network protocols needs to accommodate multiple
communication protocols, such as TCP/IP, XNS, and UNIX domain.
- The interface to network protocols need to accommodate server code that
waits for connections and client code that initiates connections.
- They also need to operate differently, depending on whether communication
is connection-oriented or connectionless.
- Application programs may wish to specify the destination address of the
datagrams it delivers instead of binding the address with the open() call.
To address these issues and others, sockets are designed to accommodate
network protocols, while still behaving like UNIX files or devices whenever it
makes sense to. Applications create sockets when they need to. Sockets work
with the open(), close(), read(), and write() system calls, and the
operating system can differentiate between the file descriptors for files, and file
descriptors for sockets.
UNIX domain sockets are named with UNIX paths. For example, a socket may
be named /tmp/foo. UNIX domain sockets communicate only between
processes on a single host. Sockets in the UNIX domain are not considered part
14 Transport Interfaces Programming Guide — November 1995
Socket Tutorial
This section covers the basic methodologies of using sockets.
Socket Creation
The socket() call creates a socket,
s = socket(domain, type, protocol);
in the specified domain and of the specified type. If the protocol is unspecified
(a value of 0 ), the system selects a protocol that supports the requested socket
type. The socket handle (a file descriptor) is returned.
The domain is specified by one of the constants defined in <sys/socket.h>.
For the UNIX domain the constant is AF_UNIX. For the Internet domain it is
AF_INET. Constants named AF_< suite > specify the address format to use in
interpreting names.
Socket types are defined in <sys/socket.h>. SOCK_STREAM, SOCK_DGRAM,
or SOCK_RAW is supported by AF_INET and AF_UNIX. The following creates a
stream socket in the Internet domain:
s = socket(AF_INET, SOCK_STREAM, 0);
This call results in a stream socket with the TCP protocol providing the
underlying communication. A datagram socket for intramachine use is created
by:
s = socket(AF_UNIX, SOCK_DGRAM, 0);
Use the default protocol (the protocol argument is 0 ) in most situations. You can
specify a protocol other than the default, as described in “Advanced Topics” on
page 41.
Binding Local Names
A socket is created with no name. A remote process has no way to refer to a
socket until an address is bound to it. Communicating processes are connected
through addresses. In the Internet domain, a connection is composed of local
and remote addresses, and local and remote ports. In the UNIX domain, a
connection is composed of (usually) one or two path names. In most domains,
connections must be unique.
Programming With Sockets 15
In the Internet domain, there may never be duplicate ordered sets, such as:
<protocol, local address, local port, foreign address, foreign
port>. UNIX domain sockets need not always be bound to a name, but when
bound there may never be duplicate ordered sets such as: <local pathname,
foreign pathname>. The path names may not refer to existing files.
The bind() call allows a process to specify the local address of the socket. This
forms the set <local address, local port> (or )
while connect() and accept() complete a socket’s association. The bind()
system call is used as follows:
bind ( s, name, namelen );
s is the socket handle. The bound name is a byte string that is interpreted by
the supporting protocol(s). Internet domain names contain an Internet address
and port number. UNIX domain names contain a path name and a family.
Code Example 2-1 binds the name /tmp/foo to a UNIX domain socket.
Code Example 2-1 Bind Name to Socket
#include <sys/un.h> ... struct sockaddr_un addr; ... strcpy(addr.sun_path, "/tmp/foo"); addr.sun_family = AF_UNIX; bind (s, (struct sockaddr *) &addr, strlen(addr.sun_path) + sizeof (addr.sun_family));
Note that in determining the size of an AF_UNIX socket address, null bytes are
not counted, which is why strlen() use is fine.
The file name referred to in addr.sun_path is created as a socket in the
system file name space. The caller must have write permission in the directory
where addr.sun_path is created. The file should be deleted by the caller
when it is no longer needed. AF_UNIX sockets can be deleted with unlink().
Binding an Internet address is more complicated. The call is similar:
#include <sys/types.h> #include <netinet/in.h> ... struct sockaddr_in sin; ... bind (s, (struct sockaddr *) &sin, sizeof sin);
Programming With Sockets 17
with the address of the client. A NULL pointer may be passed. fromlen is the
length of the structure. (In the UNIX domain, from is declared a struct
sockaddr_un.)
accept() normally blocks. accept() returns a new socket descriptor that is
connected to the requesting client. The value of fromlen is changed to the actual
size of the address.
There is no way for a server to indicate that it will accept connections only
from specific addresses. The server can check the from-address returned by
accept() and close a connection with an unacceptable client. A server can
accept connections on more than one socket, or avoid blocking on the accept
call. These techniques are presented in “Advanced Topics” on page 41.
Connection Errors
An error is returned if the connection is unsuccessful (however, an address
bound by the system remains). Otherwise, the socket is associated with the
server and data transfer may begin.
Table 2-2 lists some of the more common errors returned when a connection
attempt fails.
Table 2-2 Socket Connection Errors
Socket Errors Error Description
ENOBUFS Lack of memory available to support the call.
EPROTONOSUPPORT Request for an unknown protocol.
EPROTOTYPE Request for an unsupported type of socket.
ETIMEDOUT No connection established in specified time. This happens when the destination host is down or when problems in the network result in lost transmissions.
ECONNREFUSED The host refused service. This happens when a server process is not present at the requested address.
ENETDOWN or EHOSTDOWN
These errors are caused by status information delivered by the underlying communication interface.
18 Transport Interfaces Programming Guide — November 1995
Data Transfer
This section describes the functions to send and receive data. You can send or
receive a message with the normal read() and write() system calls:
write(s, buf, sizeof buf); read(s, buf, sizeof buf);
Or the calls send() and recv() can be used:
send(s, buf, sizeof buf, flags); recv(s, buf, sizeof buf, flags);
send() and recv() are very similar to read() and write(), but the flags
argument is important. The flags, defined in <sys/socket.h>, can be
specified as a nonzero value if one or more of the following is required:
MSG_OOB send and receive out-of-band data
MSG_PEEK look at data without reading
MSG_DONTROUTE send data without routing packets
Out-of-band data is specific to stream sockets. When MSG_PEEK is specified
with a recv() call, any data present is returned to the user but treated as still
unread. The next read() or recv() call on the socket returns the same data.
The option to send data without routing applied to the outgoing packets is
currently used only by the routing table management process and is unlikely
to be interesting to most users.
Closing Sockets
A SOCK_STREAM socket can be discarded by a close() system call. If data is
queued to a socket that promises reliable delivery after a close(), the
protocol continues to try to transfer the data. If the data is still undelivered
after an arbitrary period, it is discarded.
ENETUNREACH or EHOSTUNREACH
These operational errors can occur either because there is no route to the network or host, or because of status information returned by intermediate gateways or switching nodes. The status returned is not always sufficient to distinguish between a network that is down and a host that is down.
Table 2-2 Socket Connection Errors (Continued)
Socket Errors Error Description
20 Transport Interfaces Programming Guide — November 1995
Figure 2-1 Connection-Oriented Communication Using Stream Sockets
Code Example 2-2 Internet Domain Stream Connection (Client) #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h>
#define DATA "Half a league, half a league.. ."
socket()
bind()
listen()
Connection establishment
Server
Client
Data transfer
accept()
read()/ write()
shutdown() and/or close()
read()/ write()
shutdown() and/or close()
socket()
connect()
Programming With Sockets 21
- This program creates a socket and initiates a connection with the
- socket given in the command line. Some data are sent over the
- connection and then the socket is closed, ending the connection.
- The form of the command line is: streamwrite hostname portnumber
- Usage: pgm host port */ main(argc, argv) int argc; char *argv[]; { int sock; struct sockaddr_in server; struct hostent *hp, *gethostbyname(); char buf[1024];
/* Create socket. / sock = socket( AF_INET, SOCK_STREAM, 0 ); if (sock == -1) { perror("opening stream socket"); exit(1); } / Connect socket using name specified by command line. / server.sin_family = AF_INET; hp = gethostbyname(argv[1] ); /
- gethostbyname returns a structure including the network address
- of the specified host. */ if (hp == (struct hostent *) 0) { fprintf(stderr, "%s: unknown host\n", argv[1]); exit(2); } memcpy((char *) &server.sin_addr, (char *) hp->h_addr, hp->h_length); server.sin_port = htons(atoi( argv[2])); if (connect(sock, (struct sockaddr *) &server, sizeof server) == -1) { perror("connecting stream socket"); exit(1); } if (write( sock, DATA, sizeof DATA ) == -1) perror("writing on stream socket");
Programming With Sockets 23
perror("getting socket name"); exit(1); } printf("Socket port #%d\n", ntohs(server.sin_port)); /* Start accepting connections. */ listen(sock, 5); do { msgsock = accept(sock,(struct sockaddr *) 0,(int ) 0); if (msgsock == - perror("accept"); else do { memset(buf, 0, sizeof buf); if ((rval = read(msgsock,buf, 1024)) == -1) perror("reading stream message"); if (rval == 0) printf("Ending connection\n"); else printf("-->%s\n", buf); } while (rval != 0); close(msgsock); } while(TRUE); /
- Since this program has an infinite loop, the socket "sock" is
- never explicitly closed. However, all sockets will be closed
- automatically when a process is killed or terminates normally. */ exit(0); }
Datagram Sockets
A datagram socket provides a symmetric data exchange interface. There is no
requirement for connection establishment. Each message carries the destination
address. Figure 2-2 shows the flow of communication between server and
client.
Datagram sockets are created as described in “Socket Creation” on page 14. If a
particular local address is needed, the bind() operation must precede the first
data transmission. Otherwise, the system sets the local address and/or port
when data is first sent. To send data, the sendto() call is used:
sendto(s, buf, buflen, flags, (struct sockaddr *) &to, tolen);
24 Transport Interfaces Programming Guide — November 1995
The s , buf , buflen , and flags parameters are the same as in connection-oriented
sockets. The to and tolen values indicate the address of the intended recipient
of the message. A locally detected error condition (such as an unreachable
network) causes a return of –1 and errno to be set to the error number.
To receive messages on a datagram socket, the recvfrom() call is used:
recvfrom(s, buf, buflen, flags, (struct sockaddr *) &from, &fromlen);
Before the call, fromlen is set to the size of the from buffer. On return it is set to
the size of the address from which the datagram was received.
Datagram sockets can also use the connect() call to associate a socket with a
specific destination address. It can then use the send() call. Any data sent on
the socket without explicitly specifying a destination address is addressed to
the connected peer, and only data received from that peer is delivered. Only
one connected address is permitted for one socket at a time. A second
connect() call changes the destination address. Connect requests on
datagram sockets return immediately. The system simply records the peer’s
address. accept(), and listen() are not used with datagram sockets.
While a datagram socket is connected, errors from previous send() calls may
be returned asynchronously. These errors can be reported on subsequent
operations on the socket, or an option of getsockopt, SO_ERROR, can be used
to interrogate the error status.
26 Transport Interfaces Programming Guide — November 1995
#include <stdio.h>
/*
- The include file <netinet/in.h> defines sockaddr_in as:
- struct sockaddr_in {
- short sin_family;
- u_short sin_port;
- struct in_addr sin_addr;
- char sin_zero[8];
- };
- This program creates a datagram socket, binds a name to it, then
- reads from the socket. */
main() { int sock, length; struct sockaddr_in name; char buf[1024];
/* Create socket from which to read. / sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { perror("opening datagram socket"); exit(1); } / Create name with wildcards. */ name.sin_family = AF_INET; name.sin_addr.s_addr = INADDR_ANY; name.sin_port = 0; if (bind(sock,(struct sockaddr )&name, sizeof name) == -1) { perror("binding datagram socket"); exit(1); } / Find assigned port value and print it out. */ length = sizeof(name); if (getsockname(sock,(struct sockaddr ) &name, &length) == -1) { perror("getting socket name"); exit(1); } printf("Socket port #%d\n", ntohs( name.sin_port)); / Read from the socket. */ if ( read(sock, buf, 1024) == -1 ) perror("receiving datagram packet"); printf("-->%s\n", buf);
Programming With Sockets 27
close(sock); exit(0); }
Code Example 2-5 Sending an Internet Domain Datagram
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h>
#define DATA "The sea is calm, the tide is full.. ."
/*
- Here I send a datagram to a receiver whose name I get from the
- command line arguments. The form of the command line is:
* dgramsend hostname portnumber
main(argc, argv) int argc; char *argv[]; { int sock; struct sockaddr_in name; struct hostent *hp, *gethostbyname();
/* Create socket on which to send. / sock = socket(AF_INET,SOCK_DGRAM, 0); if (sock == -1) { perror("opening datagram socket"); exit(1); } /
- Construct name, with no wildcards, of the socket to ‘‘send’’
- to. gethostbyname returns a structure including the network
- address of the specified host. The port number is taken from
- the command line. */ hp = gethostbyname(argv[1]); if (hp == (struct hostent *) 0) { fprintf(stderr, "%s: unknown host\n", argv[1]); exit(2); } memcpy((char *) &name.sin_addr, (char *) hp->h_addr, hp->h_length); name.sin_family = AF_INET;
Programming With Sockets 29
select() normally returns the number of file descriptors selected. select()
returns a 0 if the time-out has expired. select() returns -1 for an error or
interrupt with the error number in errno and the file descriptor masks
unchanged.
For a successful return, the three sets indicate which file descriptors are ready
to be read from, written to, or have exceptional conditions pending.
Test the status of a file descriptor in a select mask with the FD_ISSET( fd ,
& mask ) macro. It returns a nonzero value if fd is in the set mask, and 0 if it is
not. Use select() followed by a FD_ISSET( fd , & mask ) macro on the read set
to check for queued connect requests on a socket.
Code Example 2-6 shows how to select on a “listening” socket for readability
to determine when a new connection can be picked up with a call to
accept(). The program accepts connection requests, reads data, and
disconnects on a single socket.
Code Example 2-6 Check for Pending Connections With select()
#include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h>
#define TRUE 1
/*
- This program uses select to check that someone is
- trying to connect before calling accept. */
main() { int sock, length; struct sockaddr_in server; int msgsock; char buf[1024]; int rval; fd_set ready; struct timeval to;
/* Open a socket and bind it as in previous examples. */
30 Transport Interfaces Programming Guide — November 1995
/* Start accepting connections. */ listen(sock, 5); do { FD_ZERO(&ready); FD_SET(sock, &ready); to.tv_sec = 5; to.tv_usec = 0; if (select(1, &ready, (fd_set *)0, (fd_set *)0, &to) == -1) { perror("select"); continue; } if (FD_ISSET(sock, &ready)) { msgsock = accept(sock, (struct sockaddr *)0, (int *)0); if (msgsock == -1) perror("accept"); else do { memset(buf, 0, sizeof buf); if ((rval = read(msgsock, buf, 1024)) == -1) perror("reading stream message"); else if (rval == 0) printf("Ending connection\n"); else printf("-->%s\n", buf); } while (rval > 0); close(msgsock); } else printf("Do something else\n"); } while (TRUE); exit(0); }
In previous versions of the select() routine, its arguments were pointers to
integers instead of pointers to fd_sets. This style of call still works if the
number of file descriptors is smaller than the number of bits in an integer.
select() provides a synchronous multiplexing scheme. The SIGIO and
SIGURG signals described in “Advanced Topics” on page 41 provide
asynchronous notification of output completion, input availability, and
exceptional conditions.