Network programming is the writing of programs that will communicate over a network with programs running another computer. Sockets programming offers a standardized way to write these programs.The Sockets programming model, AKA "Berkeley Sockets", consists of a set of data structures, predefined constants, and functions that perform system calls. In effect, sockets programming provides the low-level I/O that enables applications to each other across networks and to send and receive blocks of data between each other. Most of TCP/IP's plethora of protocols operate on the application level, whereas sockets programming only deals with two protocols: tcp and udp. The complex, innovative, and interesting part of a network program lies in the application's protocol, whereas the sockets part of the application is fairly standard and straight-forward and normally hardly changes at all from one application to the next. Indeed, in his book, Effective TCP/IP Programming: 44 Tips to Improve Your Network Programs, Jon Snader recommends that you create a library of skeleton sockets code that you can then reuse in all of your network programs (Tip #4).
Although it originated as a TCP/IP programming model, socket programming is flexible enough to be used for other protocol families, including UNIX domain, IPX/SPX, XEROX NS, X.25, SNA, DECnet, AppleTalk, and NetBEUI. And although it was originally written in C on UNIX, it has been ported to several other programming languages and operating systems; e.g., Perl, Visual BASIC, Delphi, Java, Python, Windows, and LabView. From what I've seen, the concepts and most of the function names are the same, so much of what you learn in one development environment should be transferable to the others.
My approach on this page is in C and mainly under Linux. On my Winsock Page I discuss how to apply the information on this page to Windows.
Please note that in the function descriptions given on this page, only the header files for UNIX/Linux are given. If you are developing under Windows and hence are using Winsock, then in each case replace those header files with either "winsock.h" or "winsock2.h".
Most sockets applications involve a small set of sockets operations that tend to follow the same basic sequence. A common example would be a client/server application, which actually consists of two different applications:A most basic overview of the process would be:
- the server, which provides a service
- the client, which contacts the server to request the service
- The server waits for a client to connect to it.
- A client connects to the server.
- The client and server exchange messages as "packets" which contain data and/or commands/requests.
- When the message exchange is completed, the client closes the connection.
- The server continues to wait for a client to connect to it.
Although there are a number of detailed ways to implement a server and a client, the basic steps remain essentially the same (differences between client's steps and server's steps are noted):
- [both] Create a socket with
socket()
.
This creates a socket file descriptor (in UNIX/Linux) or handle (Windows) that is required by most of the sockets functions and that the operating system needs to perform network operations.
- [both] Set up the socket address by filling in the
sockaddr_in
data structure with the IP address and port number.
- [server] Bind the socket to a port with
bind()
.
This uses thesockaddr_in
data structure to bind the server's socket to a pre-determined known port so that the client will know which port to connect to. This step could be skipped in which case the operating system would automatically pick a port to bind the socket to, but there's no way to predict which port that will be.
- [server] Listen for a connection with
listen()
.
This changes the server's socket into a special type, a "listening socket", that waits for clients to attempt to make a connection. It can serve no other purpose after this point. This function will be called once and only once for every listening socket in the server.
This is only used in the tcp protocol.
- [client] Connect to a Server with
connect()
.
This uses thesockaddr_in
data structure to attempt to connect to the server whose address is insockaddr_in
.
This is only used in the tcp protocol.
- [server] Accept a Connection with
accept()
.
This accepts the connection that a client is attempting and returns asockaddr_in
data structure containing the client's address and a new socket that is connected to that client and with which all communication with the client is to be performed.
This is only used in the tcp protocol.
- [both] Send a message (tcp) with
send()
.
Send a block of data on the socket.
- [both] Receive a message (tcp) with
recv()
.
Read a block of data from the socket.
- [both] Send a message (udp) with
sendto()
.
Send a block of data to the address indicated in thesockaddr_in
data structure.
- [both] Receive a message (udp) with
recvfrom()
.
Read a block of data from the socket. The source address of the message is returned in thesockaddr_in
data structure.
- [both] Close the connection with
shutdown()
.
This terminates a tcp connection.
This is only used in the tcp protocol.
- [both] Close the socket with
close()
.
This closes and destroys the socket.
Before we can use a socket, we need to create it. When we create a socket, the system gives us a "socket handle", called a "descriptor" in UNIX/Linux, which is an integer value that the operating system uses to access the actual socket. In every function we perform on that socket, we need to provide that socket handle in order to identify which socket to use. In WinSock this handle is of a "special" type calledSOCKET
, which incidentally is an unsigned integer, and which can take on values that UNIX/Linux socket descriptors cannot.So, in order to create a socket, you need to specify the protocol family (PF_INET in our case), the protocol (tcp or udp with the pre-defined constants
IPPROTO_TCP
andIPPROTO_UDP
, respectively), and the "socket type" (SOCK_STREAM
for tcp andSOCK_DGRAM
for udp).You create a socket with the function call:
socket()
Creates a socket
#include "sys/types.h"
#include "sys/socket.h"
int socket(int protocol_family, int type, int protocol);
On success, socket() returns the descriptor of the new socket.
- protocol_family -- for our purposes, always
PF_INET
- type -- Type of socket (
SOCK_STREAM
orSOCK_DGRAM
)- protocol -- Socket protocol (
IPPROTO_TCP
orIPPROTO_UDP
)
On failure, it returns -1 and the error is in errno.NOTE: the type and protocol fields must agree. In your code, this function call will always be:
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
or
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
An address in sockets programming is defined by a unique IP-address/port/protocol combination and a connection (AKA "a socket") is uniquely identified by a pair of addresses.The protocol (tcp or udp) was already established when you created the socket with the call to
socket()
. This section will show you how to define the IP address and port for the socket.The sockets API provides data structures and functions for translating address data into the required format and for setting the address. These include functions for performing Domain Name Service (DNS) lookup and translation. However, I will describe all that on another page. After all, I don't want to scare you off yet.
The Generic Address Structure, struct sockaddr
The basic data structure for network addressing isstruct sockaddr
. Every sockets function that requires or returns an address structure uses this struct. It is declared thus:I've stated elsewhere that sockets addressing is very flexible and
#include "sys/socket.h"
struct sockaddr { unsigned short sa_family; /* Address family (e.g., AF_INET) */ char sa_data[14]; /* Protocol-specific address information */ };struct sockaddr
is why that is. There are over 20 different address families, each of which will redefine those fourteen bytes ofsa_data
differently. But since each of those redefinitions will fit inside asockaddr
variable, all you need to do is to declare a variable of your address family's redefined address structure, fill it, and then cast a pointer to it to a pointer tosockaddr
when you pass it to a sockets function. The function itself then reads the address-family field,sa_family
, in order to see how to interpret the rest of the address structure. It's really a lot more simple that it just sounded, as you will see in the examples.If you wish to see what the different address families are, you can find a list of the pre-defined address family constants in the include file,
linux/socket.h
under Linux or inwinsock.h
under Windows (see table to the right). By the way, the protocol family constants used in thesocket()
function are actually the same as the corresponding address family constants and it is becoming increasingly common to useAF_INET
in all cases instead ofPF_INET
.
The Internet Address Structure, struct sockaddr_in
For our purposes, we will be using theAF_INET
form of thesockaddr
structure, which issockaddr_in
and is declared thus:As you can see, the two fields of address data (two bytes each) plus the 8 bytes of padding make up the full 14 bytes in the
NOTE: All values must be in network byte order
#include "netinet/in.h" struct in_addr { unsigned long s_addr; /* IP address (32 bits) */ }; struct sockaddr_in { unsigned short sin_family; /* Address family (e.g., AF_INET) */ unsigned short sin_port; /* Address port (16 bits) */ struct in_addr sin_addr; /* IP address (32 bits) */ char sin_zero[8]; /* Not used */ };sa_data
field ofsockaddr
.By the way, the reason that
in_addr
is struct is because it's actually a union (a feature in the C language that enables you to view the same location in memory as different data types, in this case as one unsigned long, as two unsigned shorts, or as four individual bytes. However, the other definitions are only of historical interest and are no longer put to any practical use. In all the sockets code I've seen, we only use the unsigned longs_addr
declaration and so in many references that's all that's ever listed, even though you will see the union in the header file.
Actually Setting the Address
Given an IP address and a port number, here is how you stuff them into asockaddr_in
structure. This is most commonly done in in-line code, but I'm presenting it as a function to also give the data declarations:Notes:
void StuffSockAddr_in(struct sockaddr_in *addr, const char *IP, short int port) { memset(addr, 0, sizeof(*addr)); /* Zero out structure */ addr->sin_family = AF_INET; /* Internet address family */ addr->sin_addr.s_addr = inet_addr(IP); /* IP address */ addr->sin_port = htons(port); /* Port */ }Curiously, the
- It is a good idea to zero out the address structure before you start filling it, hence the call to
memset()
. Some systems also have a function calledbzero()
that does the same thing.- In our examples, we always set the address family to AF_INET.
inet_addr()
converts the dotted-decimal string to the binary address. Please note that the return value ofinet_addr()
is already in network byte order.- The port needs to be converted from host byte order to network order (I personally got bitten by this one). This byte-order conversion is performed by four built-in functions for host-to-network (short int:
htons()
; long int:htonl()
) and for network-to-host (short int:ntohs()
; long int:ntohl()
).sin_family
field does not need to be converted to network byte order. I just realized that and haven't figured it out yet.
inet_addr()
Converts a dotted-decimal IP address from a string to an unsigned long
#include "sys/socket.h"
#include "netinet/in.h"
#include "arpa/inet.h"
unsigned long inet_addr(const char *addr);
On success, inet_addr() returns the binary form of the address in network byte order.
- addr -- Pointer to character string containing dotted-decimal IP address
On failure, it returns -1 and the error is in errno.
htons(), htonl(), ntohs(), ntohl(),
Convert byte order between host and netword for short int or long int
#include "netinet/in.h"
short int htons(short int shortHost);
long int htonl(long int longHost);
short int ntohs(short int shortNet);
long int ntohl(long int longNet);
Returns the converted value.
- shortHost -- short int in host byte order
- longHost -- long int in host byte order
- shortNet -- short int in network byte order
- longNet -- long int in network byte order
There is no failure condition.
Caveat Programmator -- Replace inet_addr with inet_aton
I normally use inet_addr because that's what I had learned. However, that function is considered to be obsolete and it is advised against using it in new programs.Here's the reason: to indicate failure, inet_addr returns INADDR_NONE, which is defined as 0xffffffff. However, that is also the valid return value for "255.255.255.255", which is commonly used as the universal broadcast address. Therefore, if inet_addr returns INADDR_NONE, you don't know whether it had failed or it had successfully converted "255.255.255.255".
It is advised to use inet_aton ("ASCII to number") instead of inet_addr. inet_aton removes any ambiguity by separating the error indication, which it returns as the function value, from the binary IP address, which it returns through a pointer parameter.
inet_aton()
Converts a dotted-decimal IP address from a string to a struct in_addr
#include "sys/socket.h"
#include "netinet/in.h"
#include "arpa/inet.h"
int inet_aton(const char * cp, struct in_addr * inp);
inet_aton returns nonzero if the address is valid, zero if not.
- cp -- Pointer to character string containing dotted-decimal IP address
- inp -- Pointer to the in_addr struct that will receive the binary IP address in network byte order
However, inet_aton is not a "pin-for-pin" replacement for inet_addr, because it does not return the IP address per se, but rather it stuffs the IP address into a in_addr struct that is provided by the calling function. Here is how we would rewrite the StuffSockAddr_in function to use inet_aton:
void StuffSockAddr_in(struct sockaddr_in *addr, const char *IP, short int port) { memset(addr, 0, sizeof(*addr)); /* Zero out structure */ addr->sin_family = AF_INET; /* Internet address family */ inet_aton(IP, &(addr->sin_addr)); /* IP address */ addr->sin_port = htons(port); /* Port */ }Another nice thing about inet_aton is that it offers nice symmetry with the reverse function, inet_ntoa (number to ASCII), that converts a 32-bit binary IP address to a dotted-decimal string and which takes an in_addr struct:
inet_ntoa()
Converts a struct in_addr into an equivalent dotted-decimal IP address string
#include "sys/socket.h"
#include "netinet/in.h"
#include "arpa/inet.h"
char *inet_ntoa(struct in_addr in );
inet_ntoa returns the dotted-decimal string. The string is returned in a statically allocated buffer, which subsequent calls will overwrite, so copy it if you want to save it for future use.
- in -- the in_addr struct containing the binary IP address
Miscellaneous Addressing Topics
- An application in a multihomed host (has more than one IP address), can specify which ethernet interface to use or else can work with any of them. To work with any of its ethernet interfaces, there is a special pre-defined address,
INADDR_ANY
, which would be used thus:
Please note that you do not need to convert it to network byte order. Actually, it's defined as an unsigned long with a value of zero, which is always the same no matter how you order its bytes.addr->sin_addr.s_addr = INADDR_ANY; /* Any incoming interface */
- There are several functions for working with addresses; eg:
Rather than bog you down with all those details here, I'll cover them on their own page.
- Domain Name Service (DNS) translations; eg, www.yahoo.com -> 66.94.230.41
- Service name to port number translations; eg, telnet -> 23
- Getting host and peer names and other information
Now that we have created a socket, we need to associate it with a port. This is called "binding".Actually, if we just start to use a socket without having called
bind()
first, the system automatically binds it to a randomly chosen port. This works just fine for a client, but not for a server. In order for a remote client to connect to a server, that client must know ahead of time which port the server is bound to. This means that a server must bind explicitly to a specific port. However, there can also be a valid need for a client to bind to a specific port, such as in a peer-to-peer application or in a broadcast client.
bind()
Binds a socket to a specific port
#include "sys/types.h"
#include "sys/socket.h"
int bind(int socket, struct sockaddr *localAddress, unsigned int addresslength);
On success, bind() returns a zero.
- socket -- the socket to be bound to the port
- localAddress -- the address structure containing the IP address of the network interface and the port number. The special address ANY_ADDR can be used to allow the connection to be made on any of the host's intefaces.
- addresslength -- the size in bytes of the sockaddr structure
On failure, it returns -1 and the error is in errno. Usually EADDRINUSE.
This function is used for stream/TCP sockets only and is used in conjunction withaccept()
.In a TCP connection, one application, the server, binds to a well-known port and waits for a client to try to connect to it. This is called "listening" and it is performed by calling the
listen()
function.
listen()
changes the socket to a special type whose only function is to process new attempts to connect on that port. It must already be bound to a local port; ie,bind()
must have already been called.When a client's connection request comes in, if the queue has not been filled (queue length is set with the "backlog" parameter), then the new connection request is placed in the queue. If the queue is full, then the new connection request will be rejected, which will result in an error in the client application. Queued connections are removed from the queue and completed with the
accept()
function, which also creates a new socket for communicating with the client.
listen()
does not block. It returns immediately from the function call.
listen()
Sets a socket to listen for incoming connections
tcp sockets only
#include "sys/types.h"
#include "sys/socket.h"
int listen(int socket, int backlog);
On success, listen() returns 0 On failure, it returns -1 and the error is in errno.
- socket -- Socket (returned from socket())
- backlog -- Maximum number of new connections (sockets) to allow to wait
Requests a connection to a specified port at a specified peer address. While it's most commonly used with the tcp protocol, it can also be used with udp. In the tcp protocol, success means that a TCP connection has been established. Failure can occur for any of several possible reasons; eg, failure to find the server system, no server was listening on that port, the server rejected the connection (eg, because the queue was full). In case of failure, you will need to examine the error code to determine the reason. Calling
connect()
initiates the "Triple Handshake".If the client socket had not already been bound to a port with
bind()
, thenconnect()
will bind it to a randomly assigned port and fill in its local address information as well as the peer's information. After this point, you may read the socket's local address information withgetsockname()
and its peer address information withgetpeername()
.
connect()
Requests a connection to a peer
#include "sys/types.h"
#include "sys/socket.h"
int connect(int socket, struct sockaddr *peerAddress, int addressLength);
On success, connect() returns zero.
- socket -- Socket (returned from
socket()
)- peerAddress -- Populated
sockaddr
structure describing the peer socket address to connect to- addressLength -- Number of bytes in the peerAddress structure
On failure, it returns -1 (SOCKET_ERROR in Winsock) and the error is in errno.
getsockname()
Get a local socket's address information
#include "sys/socket.h"
int getsockname(int socket, struct sockaddr *localAddress, int addressLength);
On success, getsockname() returns zero.
- socket -- Socket (returned from
socket()
)- localAddress --
sockaddr
structure to receive local address- addressLength -- Number of bytes in the localAddress structure
On failure, it returns -1.
getpeername()
Get the remote peer's address information
#include "sys/socket.h"
int getpeername(int socket, struct sockaddr *peerAddress, int addressLength);
On success, getpeername() returns zero.
- socket -- Socket (returned from
socket()
)- peerAddress --
sockaddr
structure to receive peer address- addressLength -- Number of bytes in the peerAddress structure
On failure, it returns -1.
This function is used for stream/TCP sockets only and is used in conjunction withlisten()
.
accept()
completes the process of establishing a TCP connection between the server and the client. It removes the first waiting entry in the listen queue, creates a new socket for it, and fills in that new socket's address information with both the local and the remote address information, which are accessible throughgetsockname()
andgetpeername()
respectively.However, if there is no pending connection waiting in the queue, then
accept()
won't return until one does come in. This means thataccept()
blocks; it's the first function we've considered here that does block. This means that that thread of code cannot continue untilaccept()
returns. If the program only consists of a single thread of code, then the entire program "freezes" until a connection is made. This may be acceptable in the most simple of servers that will only handle one client at a time, but it is unacceptable in most serious applications.Blocking is a non-trivial problem and a very real issue in sockets programming, that has led to a number of solutions, some of which are system-dependent. Some solutions include using multiple processes or threads (one for each blocking socket), changing the sockets to non-blocking mode which requires special handling, and using the
select()
function (to test whether there's anything for the blocking function call to process before actually calling it). I describe and discuss these in my "Dealing With and Getting Around Blocking Sockets" topic page.And, obviously, if
listen()
had not been called to create a listening socket, then there would be no queue for waiting connections. In that case,accept()
would return with an error.
accept()
Completes a connection with a client
tcp sockets only
Blocks
#include "sys/types.h"
#include "sys/socket.h"
int accept(int socket, struct sockaddr *clientAddress, int *addressLength);
On success, accept() returns the new socket descriptor.
- socket -- Listening socket
- clientAddress -- Receives client socket IP address and port
- addressLength -- Length of
sockaddr
buffer for client address
On failure, it returns -1 (SOCKET_ERROR in Winsock) and the error is in errno.
Note:
There is a basic rule in sockets programming that you cannot use the same port for two different things at the same time. That only makes sense. If one process is listening on a port for incoming messages, another process cannot use the same port to send messages nor to also start listening.That rule seems very straight-forward, but I got confused for a long time because of a misunderstanding about the new socket that
accept()
returns. I had associated ports and sockets too closely together, so I interpreted that new socket as also requiring a new port. Needless to say, I was very surprised when our Linux-server instructor ran thenetstat
utility on a web server and I saw every single client connecting to it through TCP port 80.Remember from the discussion, "Ports and Sockets", that:
"Each socket is uniquely identified by a pair of addresses: the local address and the peer's address. This is important to remember, because we must be careful not to associate a socket too closely with a local port. There is a cardinal rule that a given socket can only be used for one connection at a time. But if we run a server, say a web server on TCP port 80, and ten clients connect to us, then we will see that our local TCP port 80 is involved in each of those ten connections. That appears to violate that cardinal rule. However, what is really happening is that each of those are different sockets, because each of them has a different peer address. We are not using the same socket ten times over, but rather we are using ten different sockets. There is no actual conflict nor violation of the rule."So bear in mind that the new socket returned byaccept()
will be on the same local port as the listening socket; you can verify that with a call togetsockname()
. But it is still a different socket.
Sends the bytes contained in the buffer over the given connected socket. The actual format or contents of the data buffer are immaterial to the function.When sent over a TCP socket, the size of the data can exceed the maximum size for a data packet. The tcp protocol will simply break that data up into multiple packets that get sent separately. Then the peer receiving the packets will put them back together again to reconstruct the original block of data. It should be noted that every
send()
will have a single correspondingrecv()
at the receiving peer. Even though thesend()
resulted in multiple packets, it appears to the receiving peer application as if that data had been sent in a single packet.
send()
Sends a message on a connected socket
connected sockets only; normally TCP, but could be UDP
#include "sys/types.h"
#include "sys/socket.h"
int send(int socket, const void *msg, unsigned int msglength, int flags);
On success, send() returns the number of bytes actually sent.
- socket -- Socket (must be in connected state)
- msg -- Pointer to data to be transmitted
- msglength -- Number of bytes to be sent
- flags -- Control flags for special protocol features; set to O in most cases
On failure, it returns -1 and the error is in errno.
The opposite number ofsend()
,recv()
takes the data received over the connected socket and copies to the buffer address provided to it for up to the maximum number specified.Its return value is the number of bytes received, or -1 if there was an error. However, if the connection closes, then the return value will be zero.
recv()
blocks until there is at least one byte of data to be read or else the connection closes. Therefore, this is another part of the program that will need to address the blocking problem.
recv()
Receives a message on a connected socket
connected sockets only; normally TCP, but could be UDP
Blocks
#include "sys/types.h"
#include "sys/socket.h"
int recv(int socket, void *rcvbuffer, int bufferlength, int flags);
On success, recv() returns the number of bytes received.
- socket -- Socket (must be in connected state)
- rcvbuffer -- Where to put the data
- bufferlength -- Maximum number of bytes to put in buffer
- flags -- Control flags, 0 in most cases
On failure, it returns -1 and the error is in errno.
One thing to keep in mind is that the packet that the other host had sent could have been fragmented into several smaller packets for transmission, which were then reassembled as they were received. That means that that when you start reading in data via
recv()
, you might have not yet received the entire packet. You may need to callrecv()
multiple times to read everything in. How do you know whether you've received the entire packet? That would part of the application protocol. Just something to keep in mind.
sendto()
sends the bytes contained in the buffer over the given socket. Very similar tosend()
, except that the destination address must be specified.Normally used with a UDP datagram socket for sending a single packet. As such, the size of the block of data being sent can be no larger than what can be contained in a single data packet.
sendto()
Sends a message
normally used with UDP datagrams, but could also be used with a TCP connected socket
#include "sys/types.h"
#include "sys/socket.h"
int sendto(int socket, const void *msg, int msglength, int flags, struct sockaddr *destaddr, int destAddrLen);
On success, sendto() returns the number of bytes sent.
- socket -- Socket
- msg -- Pointer to data to be transmitted
- msglength -- Number of bytes to be sent
- flags -- Control flags (O in most cases)
- destaddr -- Destination address for data
- destAddrLen -- Length of destination address structure
On failure, it returns -1 and the error is in errno.
The opposite number ofsendto()
,recvfrom()
takes the data received in a datagram and copies it to the buffer address provided to it for up to the maximum number specified. It also loads the sender's address information into the address structure passed to it.It is very similar to
recv()
, except that the sender's address is also returned. And likerecv()
,recvfrom()
also blocks until there is at least one byte of data to be read. Therefore, this is another part of the program that will need to address the blocking problem.And it also differs from
recv()
in that you are assured of having received the either packet, by the very nature of UDP.
recvfrom()
Receives a datagram
normally used with UDP datagrams, but could also be used with a TCP connected socket
Blocks
#include "sys/types.h"
#include "sys/socket.h"
int recvfrom(int socket, void *buffer, int buffersize, int flags, struct sockaddr *fromAddr, unsigned int *fromAddrLen);
On success, recvfrom() returns the number of bytes received.
- socket -- Socket (must be in connected state)
- buffer -- Where to put the data
- buffersize -- Max number of bytes to put in buffer
- flags -- Control flags (O in most cases)
- fromAddr -- Address data of sender
- fromAddrLen -- Length of sender address structure
On failure, it returns -1 and the error is in errno.
shutdown()
is used to close the connection in an orderly and graceful manner (yet another hot topic). This would normally only be done with TCP sockets, but it could also be done with UDP sockets that were connected.Basically, with the parameters you indicate whether you are done sending or done receiving or done with both. With that, you can indicate your intention to terminate the session and close the connection. And with the proper choice of parameter, you can ensure that no data will be lost in the process.
Normally, this is the shutdown procedure for a TCP application:
And that essentially is how a "graceful shutdown" should work.
- You will start by signalling that you will not send any more packets by calling
shutdown(1)
.- The remote peer detects your shutdown when
recv()
returns a zero.- The peer finishes sending the rest of its data, if any.
- The peer also calls
shutdown(1)
to signal that it will send no more data.- Since it already knows it won't receive anything more from you, the peer calls
close()
to close its socket.- You detect the peer's shutdown when
recv()
returns a zero, whereupon you know you can safely close your socket.
WinSock's handling of the shutdown() function and of the shutdown process in general is considered as not being as graceful as in UNIX and there are warnings that it can cause unexpected errors. Be mindful of this when things aren't working exactly as you expect them to.
shutdown()
Closes a connection
#include "sys/socket.h"
int shutdown(int socket, int how);
On success, shutdown() returns zero.
- socket -- Socket (must be in connected state)
- how --
- Disallow further receiving
- Disallow further sending
- Disallow further receiving or sending
On failure, it returns -1 and the error is in errno.For example, in UNIX
recv()
returns a zero when the connection has been broken. Winsock is intended to do the same thing and sometimes does, but it could also return a -1 with an error of WSAECONNRESET, WSAECONNABORTED or WSAESHUTDOWN. Similarly, in UNIXsend()
will return a -1 and a EPIPE error, whereas in Winsock it could behave exactly asrecv()
by returning either a zero or a -1 with the same errors asrecv()
.So if you're working with Winsock, you will need to become familiar with all of its peculiarities.
Just like closing a file (which is what you are doing on UNIX/Linux),close()
closes the socket, releases that resource, and makes the socket cease to exist. All operations still pending on the socket, including any communications, are immediately terminated. Any subsequent attempts to use that socket (except to create a new one) will result in error. That socket is history. That's all she wrote.
close()
Closes the socket
#include "unistd.h"
int close(int socket);
On success, close() returns 0.
- socket -- The socket to be closed
On failure, it returns -1 and the error is in errno.
The Winsock Way
Yet again, Winsock is a bit different. While almost everything on UNIX is a file, including sockets, Windows has not followed that philosophy. So while any kind of file in UNIX can be closed with the sameclose()
function, in Windows system objects such as a SOCKET must be closed with a special function that handles that specific kind of system object.Therefore, in Winsock you must use a special function,
closesocket()
. It works pretty much the same as theclose()
function on UNIX, but the effects of callingclosesocket()
can become quite involved depending on what the socket's options were set to, so you should read the help page on Microsoft's MSDN site for more detailed information (that page also links to the Winsock error codes).
closesocket()
Closes the Winsock socket
#include "winsock.h"
/* or "winsock2.h" */
int closesocket(SOCKET socket);
On success, close() returns 0.
- socket -- The Winsock socket to be closed
On failure, it returns SOCKET_ERROR; retrieve the error code by calling WSAGetLastError().
Return to Top of Page
Return to DWise1's Sockets Programming Page
Return to DWise1's Programming Page
Share and enjoy!
First uploaded on 2004 April 15.
Updated on 2011 July 18.