Socket Programming
What is a socket?
A socket is a way to speak to other programs using standard UNIX file descriptors.
As you know, everything in Unix is a file. So when you want to communicate with another program you’re gonna do it through a file descriptor.
Socket Descriptor
A file descriptor is simply an integer associated with an open file, it can be a network connection, a FIFO, a pipe, a terminal, a real on-the-disk file, or just about anything else.
Since a socket descriptor is a file descriptor, it can be used in the same way as a file descriptor. For example, it can be passed to the read
and write
functions to read and write data to the socket.
It used by the operating system to keep track of the different sockets that are open and to identify which socket a particular piece of data should be sent to or received from.
Socket() function
Syntax
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// returns: socket descriptor if OK, -1 on error
Parameters
1. Domain
The domain argument specifies a communication domain within which communication will take place. (The domains specified by POSIX.1)
The most common are:
AF_INET
for IPv4 Internet protocolsAF_INET6
for IPv6 Internet protocolsAF_UNIX
for UNIX domain protocolsAF_UNSPEC
for unspecified
Note:
AF_
is short for Address Family.PF_
is short for Protocol Family.The
AF_INET
andAF_INET6
domains are used for communication over the Internet.The
AF_UNIX
domain is used for communication within the same machine, and some systems also define theAF_LOCAL
domain for communication within the same machine.AF_LOCAL
is alias forAF_UNIX
.The
AF_UNSPEC
domain is used to specify that the socket can be used with any of the other domains.
2. Type
The type argument specifies the type of communication to be used by the socket. (The types specified by POSIX.1)
The most common are:
Type | Description |
---|---|
SOCK_STREAM | Provides sequenced, reliable, two-way, connection-based byte streams. |
SOCK_DGRAM | Provides fixed-length, connectionless, unreliable messages. |
SOCK_SEQPACKET | Provides fixed-length, sequenced, reliable, connection-oriented messages. |
SOCK_RAW | Provides raw network protocol access. |
Note:
- The
SOCK_SEQPACKET
is similar toSOCK_DGRAM
, but provides additional features such as error checking and flow control to ensure that data is delivered reliably and in the correct order.
3. Protocol
The protocol argument specifies a particular protocol to be used with the socket. It’s usually 0, which select the default protocol for the given domain and socket type.
When multiple protocols are supported for the same domain and socket type, we can use the protocol argument to select a particular protocol.
Note:
The default protocol for the
SOCK_STREAM
in theAF_INET
domain is TCP (RFC 793). For example: telnet, ftp, ssh, web browsers, etc.The default protocol for the
SOCK_DGRAM
in theAF_INET
domain is UDP (RFC 768). For example: DNS, DHCP, NTP, etc.Protocol Description IPPROTO_IP IPv4 Internet Protocol IPPROTO_IPV6 IPv6 Internet Protocol (optional in POSIX.1) IPPROTO_ICMP Internet Control Message Protocol IPPROTO_RAW Raw IP packets protocol (optional in POSIX.1) IPPROTO_TCP Transmission Control Protocol IPPROTO_UDP User Datagram Protocol
How socket() works?
Calling socket is similar to calling open. Both return a file descriptor that can be used for I/O.
When you done with the socket, you can close it by calling close and free the file descriptor for reuse.
Although the socket is actually a file descriptor, but you can’t use it with any function that accepts a file descriptor as an argument. Such as lseek since a socket doesn’t have a file offset.
The following table lists some common file descriptor operations, and whether they are supported by a socket.
Function | Behavior |
---|---|
dup | duplicate the socket |
close | deallocate the socket |
read | read data from the socket, equivalent to recv |
write | write data to the socket, equivalent to send |
select | wait for the socket to become ready for I/O |
mmap | Unsupported |
lseek | Unsupported |
Socket Addressing
In order to identify the particular process that we want to communicate with, we need to specify the address of the process.
Identifying a process is done by specifying the machine’s network address and port number of the process.
Byte Order
(Source: Hackaday - Don’t Let Endianness Flip You Around)
When we communicate with a process on the same machine, we don’t need to worry about the byte order of the data.
However, when we communicate with a process on a different machine, we need to make sure that the byte order of the data is the same on both machines.
big-endian vs little-endian
Big-endian: the most significant byte is stored in the smallest address.
The big-endian byte order is the same as the way humans write numbers.
big-endian example:
| n | n + 1 | n + 2 | n +3 |
|-----|-------|-------|------|
| MSB | | | LSB |
Little-endian: the least significant byte is stored in the smallest address.
Little-endian is the opposite of big-endian, but it’s more suitable for computers, because the CPU reads data from memory from low address to high address.
| n + 3 | n + 2 | n + 1 | n |
|-------|-------|-------|-----|
| MSB | | | LSB |
Usually, the Network protocols specify a byte ordering so that the data can be exchanged between different machines without confusing the byte order.
- The TCP/IP protocol suite uses big-endian byte order.
byte order conversion
Function | Description |
---|---|
htons() | host to network short |
htonl() | host to network long |
ntohs() | network to host short |
ntohl() | network to host long |
h
is for host
and n
is for network
.
l
is for long
and s
is for short
.
Snytax
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
Address Structure
An address identifies a socket endpoint in a particular protocol communication domain.
Different OS may have different implementation of the address structure.
On FreeBSD, the address structure is defined as follows:
struct sockaddr {
unsigned char sa_len; /* total length */
sa_family_t sa_family; /* address family */
char sa_data[14];/* variable-length address */
};
The address structure on the Linux as follows:
struct sockaddr {
sa_family_t sa_family; /* address family. AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sa_family
can be a variety of things, but it’ll be AF_INET (IPv4) or AF_INET6 (IPv6).
sa_data
contains a destination address and port number for the socket.
IPv4 Address Structure
sockaddr
structure for IPv4 is defined as follows: (“in” for “internet”)
// Defined in <sys/socket.h>
typedef uint16_t sa_family_t; /* short, 2 bytes */
// Defined in <netinet/in.h>
typedef uint16_t in_port_t; /* unsigned short, 2 bytes */
typedef uint32_t in_addr_t; /* unsigned long, 4 bytes */
struct in_addr {
in_addr_t s_addr; /* 32-bit IPv4 address, network byte order, load with inet_aton() */
};
// Defined in <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port number, htons(3490), network byte order */
struct in_addr sin_addr; /* IPv4 address, network byte order */
unsigned char sin_zero[8]; /* filler */
};
// total size: 16 bytes
IPv6 Address Structure
sockaddr
structure for IPv6 is defined as follows: (“in” for “internet”)
// Defined in <sys/socket.h>
typedef uint16_t sa_family_t; /* short, 2 bytes */
// Defined in <netinet/in.h>
typedef uint16_t in_port_t; /* unsigned short, 2 bytes */
// Defined in <netinet/in.h>
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address, 16 bytes */
};
// Defined in <netinet/in.h>
struct sockaddr_in6 {
sa_family_t sin6_family; /* address family: AF_INET6, 2 bytes */
in_port_t sin6_port; /* port number, 2 bytes */
uint32_t sin6_flowinfo; /* traffic class and flow info, 4 bytes */
struct in6_addr sin6_addr; /* IPv6 address, 16 bytes */
uint32_t sin6_scope_id; /* set of interfaces for scope, 4 bytes */
};
// total: 28 bytes
Although the sockaddr_in
and sockaddr_in6
structures are different, they both passed to the socket routines cast to a sockaddr
structure.
sockaddr_storage
The sockaddr_storage
structure is designed to be large enough to hold any address structure. Since you don’t know what address structure you’ll be dealing with, you can use this structure to hold any address structure, and then cast it to the appropriate type when you need to use it.
It is defined as follows:
// Defined in <sys/socket.h>
struct sockaddr_storage {
sa_family_t ss_family; /* address family */
/* Following fields are implementation-defined */
char __ss_pad1[_SS_PAD1SIZE];
int64_t __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
};
// total: 128 bytes
Convert between binary and string
The readable IP address like 198.168.0.1
is a numbers-and-dots notation, but stored in the computer in binary format. In order to print that readable address format, we use inet_ntop
functions to convert the binary address to numbers-and-dots notation. ntop
stands for “network to presentation”.
To store the IP address like 198.168.0.1
in the computer for further use, we use inet_pton
functions to convert the numbers-and-dots notation to binary. pton
stands for “presentation to network”.
Snytax
The inet_ntop
and inet_pton
functions are defined as follows:
#include <arpa/inet.h>
const char *inet_ntop(int domain, const void *restrict src, char *restrict dst, socklen_t size);
int inet_pton(int domain, const char *restrict src, void *restrict dst);
Return Value
inet_ntop
: return a pointer to the resulting string, or NULL if an error occurred.inet_pton
: return 1 if the address was successfully converted, 0 if the input is not a valid network address in the specified address family, or -1 if there was an error.
Parameters
domain
: the address family, eitherAF_INET
orAF_INET6
.src
: the source binary address.dst
: the destination text string.size
: the size of the buffer pointed to by str. Two constants are defined to make our job easier:- INET_ADDRSTRLEN is large enough to hold a text string representing an IPv4 address
- INET6_ADDRSTRLEN is large enough to hold a text string representing an IPv6 address.
For example:
struct sockaddr_in sa; // IPv4
struct sockaddr_in6 sa6; // IPv6
inet_pton(AF_INET, "10.12.110.57", &(sa.sin_addr)); // IPv4
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); // IPv6
// IPv4 example:
char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
struct sockaddr_in sa; // pretend this is loaded with something
inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);
printf("The IPv4 address is: %s\n", ip4);
// IPv6 example:
char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
struct sockaddr_in6 sa6; // pretend this is loaded with something
inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN);
printf("The address is: %s\n", ip6);
Note: The above code is not robust because there is no error checking.
The inet_pton
function returns -1 on error, 0 if the input is not a valid address in the specified address family, and 1 on success. So check the return value is greater than 0 to make sure the conversion is successful.
Address Lookup
getaddrinfo()
There are two functions to convert a host name to an IP address and vice versa. They are gethostbyname
and gethostbyaddr
.
However, they are OUT-OF-DATE and should NOT be used in new programs.
The getaddrinfo
and getnameinfo
functions are recommended instead.
syntax
The getaddrinfo
function is defined as follows:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
const char *service, // e.g. "http" or port number
const struct addrinfo *hints,
struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
Return Value
You give getaddrinfo() function three input parameters, and it gives you a pointer to a linked-list, res, of results.
Parameters
The first parameter, node, is the host name or IP address. If it is NULL, the IP address is set to INADDR_ANY for IPv4 or INADDR6_ANY_INIT for IPv6.
The second parameter, service, is the service name or port number. If it is NULL, the port number is set to 0. It can be a decimal number or a service name such as “http” or “ftp”. Here is a list of well-known port numbers.
The third parameter, hints, is a pointer to a struct addrinfo. It is used to specify the type of socket we want. If it is NULL, the default is SOCK_STREAM for TCP and AF_INET for IPv4.
The fourth parameter, res, is a pointer to a linked-list of struct addrinfo. It is used to store the host name and IP address.
Example
The following is an example of hints:
struct addrinfo{
int ai_flags; // AI_PASSIVE, AI_CANONNAME, etc.
int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
int ai_protocol; // use 0 for "any"
size_t ai_addrlen; // size of ai_addr in bytes
struct sockaddr *ai_addr; // struct sockaddr_in or _in6
char *ai_canonname; // full canonical hostname
struct addrinfo *ai_next; // linked list, next node
};
int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof(struct addrinfo)); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // Don't care if IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // Fill in my IP for me
if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
exit(1);
}
// servinfo now points to a linked list of 1 or more struct addrinfos
// ... do everything until you don't need servinfo anymore ....
freeaddrinfo(servinfo); // free the linked-list
Here is an example of a client who wants to connect to a server (www.example.com) on port 3490: (This does not actually connect to the server, but it sets up the structure for future connection)
int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
// get ready to connect
status = getaddrinfo("www.example.com", "3490", &hints, &servinfo);
getnameinfo()
The getnameinfo() function is the inverse of getaddrinfo(3): it converts a socket address to a corresponding host and service, in a protocol-independent manner.
Syntax
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *serv, size_t servlen, int flags);
Return Value
Returns: 0 on success, or a non-zero error code on failure. If the return value is non-zero, it can be passed to gai_strerror() to get a human-readable string.
Parameters
sa
, is a pointer to a struct sockaddr.salen
, is the length of the socket address.host
, is a pointer to a buffer that will contain the host name or IP address.hostlen
, is the length of the host buffer.serv
, is a pointer to a buffer that will contain the service name or port number.servlen
, is the length of the service buffer.
Example
struct sockaddr_in6 sa; // could be IPv4 if you want
char host[1024];
char service[20];
// pretend sa is full of good information about the host and port...
getnameinfo(&sa, sizeof sa, host, sizeof host, service, sizeof service, 0);
printf("host: %s\n", host); // e.g. "www.example.com"
printf("service: %s\n", service); // e.g. "http"
bind() function
Purpose
Once a socket is created, it must be bound to an address and port number so that other processes can connect to it.
The bind
function is called by the server to bind a socket to a specific port number and IP address.
Syntax
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
Parameters
sockfd
: the socket file descriptormy_addr
: a pointer to a struct sockaddr. It contains the port number and IP address of the server.addrlen
: the length of the struct sockaddr
Return value
0 on success, -1 on error and sets errno.
Example
// !!! THIS IS THE OLD WAY !!!
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr);
Example with getaddrinfo()
struct addrinfo hints, *res;
int sockfd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me (IP of the host it’s running on)
getaddrinfo(NULL, "3490", &hints, &res);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// bind it to the port we passed in to getaddrinfo():
bind(sockfd, res->ai_addr, res->ai_addrlen);
connect() function
Purpose
The connect
function is called by the client to connect to a server.
Syntax
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
Parameters
sockfd
: the socket file descriptorserv_addr
: a pointer to a struct sockaddr. It contains the port number and IP address of the server.addrlen
: the length of the struct sockaddr
Return value
0 on success, -1 on error and set the variable errno
.
Example
struct addrinfo hints, *res;
int sockfd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// connect!
connect(sockfd, res->ai_addr, res->ai_addrlen);
Note:
check the return value of
connect
to see if it was successful. -1 on error and set the variableerrno
.In the above example, it does call bind() before connect(). This is because we don’t care about our local port number. We only care where we are connecting to. The kernel will automatically assign a port number for us, and the remote server will automatically get that port number from us.
listen() function
Purpose
The listen
function is called by the server to listen for incoming connections.
Syntax
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
Parameters
sockfd
: the socket file descriptorbacklog
: the maximum number of pending connections in the queue until accept() called. If the queue is full, the client will receive an error with an indication of ECONNREFUSED.
Return value
0 on success, -1 on error and set the variable errno
.
Example
getaddrinfo();
socket();
bind();
listen();
/* accept() goes here */
Call bind
before listen
to make service runnning on a specific port number.
accept() function
Purpose
The accept
function is called by the server to accept an incoming connection request from a client at specified port number.
It returns a new socket file descriptor for the new connection.
Syntax
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Parameters
sockfd
: the socket file descriptoraddr
: a pointer to a struct sockaddr. It contains the port number and IP address of the client.addrlen
: the length of the struct sockaddr
Return value
On success, it returns a new socket file descriptor for the new connection. On error, it returns -1 and set the variable errno
.
Example
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#define MYPORT "3490" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
int main(void){
struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
getaddrinfo(NULL, MYPORT, &hints, &res);
// make a socket, bind it, and listen on it:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);
// now accept an incoming connection:
addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
// ready to communicate on socket descriptor new_fd!
}
Note:
The all send() and recv() functions are called on the new socket file descriptor returned by accept().
If you only want to accept one connection, you can call
close(sockfd)
afteraccept()
to close the listening socket to prevent other connections.
send() and recv() functions
Purpose
The send
and recv
functions are for communication between client and server over the stream socket or the connected datagram socket.
For unconnected datagram socket, use sendto
and recvfrom
functions instead.
Syntax for send()
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, const void *msg, int len, int flags);
Parameters for send()
sockfd
: the socket file descriptormsg
: a pointer to a buffer containing the message to be sent.len
: the length of the message in bytes.flags
: a set of flags that modify the behavior of the send() call.
Return value for send()
Returns the number of bytes actually sent out. (It may be less than the number requested to be sent in the len
parameter.)
On error, it returns -1 and set the variable errno
.
Example for send()
char *msg = "Hello, world!";
int len;
int bytes_sent;
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
Syntax for recv()
#include <sys/types.h>
#include <sys/socket.h>
int recv(int sockfd, void *buf, size_t len, int flags);
Parameters for recv()
sockfd
: the socket file descriptor to read from.buf
: a pointer to a buffer where the message should be stored.len
: the maximum length of the buffer.flags
: a set of flags that modify the behavior of the recv() call.
Return value for recv()
Returns the number of bytes actually read into the buffer.
-1
if an error occurred and set the variable errno
.
0
if the server side has closed the connection on the socket.
Example for recv()
char buf[100];
int bytes_received;
bytes_received = recv(sockfd, buf, 100, 0);
sendto() and recvfrom() functions
Purpose
The datagram socket are not connected to each other. So we need to provide the address of the destination before sending the message.
The sendto
and recvfrom
functions are for communication between client and server over the unconnected datagram socket.
Syntax for sendto()
#include <sys/types.h>
#include <sys/socket.h>
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, socklen_t tolen);
Parameters for sendto()
sockfd
: the socket file descriptormsg
: a pointer to a buffer containing the message to be sent.len
: the length of the message in bytes.flags
: a set of flags that modify the behavior of the send() call.to
: a pointer to a struct sockaddr that contains the address of the destination. It can be either a struct sockaddr_in or a struct sockaddr_in6.tolen
: the length of the struct sockaddr. An int deep-down, can simply be set to sizeof(struct sockaddr_storage).
Return value for sendto()
sendto() returns the number of bytes actually sent (might be less than the number of bytes you told it to send!), or -1 on error.
Example for sendto()
char *msg = "Hello, world!";
int len;
int bytes_sent;
struct sockaddr_storage their_addr;
socklen_t addr_len;
len = strlen(msg);
bytes_sent = sendto(sockfd, msg, len, 0, (struct sockaddr *)&their_addr, addr_len);
Syntax for recvfrom()
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, size_t len, unsigned int flags,
struct sockaddr *from, socklen_t *fromlen);
Parameters for recvfrom()
sockfd
: the socket file descriptor to read from.buf
: a pointer to a buffer where the message should be stored.len
: the maximum length of the buffer.flags
: a set of flags that modify the behavior of the recv() call.from
: from is a pointer to a local struct sockaddr_storage that will be filled with the IP address and port of the originating machine.fromlen
: fromlen is a pointer to a local int that should be initialized to sizeof *from or sizeof(struct sockaddr_storage). After the function returns, it contain the length of the address actually stored in from.
Return value for recvfrom()
Returns the number of bytes actually read into the buffer.
-1 on error and set the variable errno
.
Example for recvfrom()
char buf[100];
int bytes_received;
struct sockaddr_storage their_addr;
socklen_t addr_len;
bytes_received = recvfrom(sockfd, buf, 100, 0, (struct sockaddr *)&their_addr, &addr_len);
Summary of send() and recv() functions
Function | Connected | Unconnected |
---|---|---|
send() | Yes | No |
recv() | Yes | No |
sendto() | No | Yes |
recvfrom() | No | Yes |
If your program conect() a datagram socket, you can simply use send() and recv() functions instead of sendto() and recvfrom(). The socket itself is still a datagram socket and the packets still use UDP, but the socket interface will automatically add the destination and source information for you.
close() function
Purpose
The close()
function closes a file descriptor, so that it no longer refers to any file and may be reused.
This will prevent any more reads and writes to the socket. Anyone attempting to read or write the socket on the remote end will receive an error.
Syntax for close()
int close(int sockfd);
Parameters for close()
sockfd
: the socket file descriptor to close.
shutdown() function
Purpose
The shutdown()
function disables further send or receive operations on a socket.
Syntax for shutdown()
#include <sys/socket.h>
int shutdown(int sockfd, int how);
// returns: 0 if OK, -1 on error
Parameters
sockfd
: the socket file descriptor to shutdown.how
: the type of operation to disable.
The how argument specifies what types of operation will no longer be allowed.
Value | Constant | Description |
---|---|---|
0 | SHUT_RD | Disables further receive operations. |
1 | SHUT_WR | Disables further send operations. |
2 | SHUT_RDWR | Disables further send and receive operations. |
Return value
Returns 0 on success, or -1 on error and set the variable errno
.
Difference between close() and shutdown()
Why do we need to shutdown a socket when we can just close it?
The
close
function will deallocate the socket descriptor only when all references to the socket are closed. Especially, if we dup the socket descriptor, the socket will not be closed until all references to the socket are closed.The shutdown function will allow us to shut a socket down in one direction or both directions. For example, if we want to close the socket for receiving data, but still want to send data to the peer, we can use shutdown to close the socket for receiving data, but still keep the socket open for sending data.
The shutdown function will not deallocate the socket descriptor. To free the socket descriptor, we still need to call the close() function.
getpeername() function
Purpose
The getpeername()
function retrieves the name of the peer connected to the socket.
Syntax for getpeername()
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// returns: 0 if OK, -1 on error
gethostname() function
Purpose
The gethostname()
function returns the null-terminated hostname in the buffer pointed to by hostname, which has a length of len bytes.
Syntax for gethostname()
#include <unistd.h>
int gethostname(char **hostname, size_t len);
// returns: 0 if OK, -1 on error
References
Chapter 16 Network IPC: Sockets - Advanced Programming in the UNIX Environment
Chapter 6 Sockets - UNIX Network Programming, Volume 1, Third Edition
Beej’s Guide to Network Programmingrections. For example, if we want to close the socket for receiving data, but still want to send data to the peer, we can use shutdown to close the socket for receiving data, but still keep the socket open for sending data.
Last modified on 2022-12-30
Thank you for your support!