Sockets, Continued
This is part 2 of the article regarding socket programming. Information about system calls is in Sockets (Part 1).
Client / Server
Program flow of client/server in terms of system calls can be visualized as:
| Server
|
| Client
|
|
| socket()
|
|
|
|
Bind to an IP address and port
| bind()
|
|
|
|
Queue up connection requests
| listen()
|
| socket()
|
|
Remove connection from queue
| accept()
| ←
| connect()
| Try to connect to server
|
| recv() recvfrom()
| ←
| send() sendto()
|
|
| send() sendto()
| →
| recv() recvfrom()
|
|
| shutdown()
|
| shutdown()
|
|
| close()
|
| close()
|
|
Note that, on the server side accept()
is blocking the process until a connection is made. So in order to handle multiple connections we can use the following concept:
listen(fd, 10);
do {
client = accept(fd, &daddr, &daddr_len);
pid = fork();
if (pid == 0) {
// this is the child process for handling new
// socket descriptor 'client'
close(client);
exit(0);
}
else {
close(client);
// parent goes back to accepting connections
// immediately
}
} while(1);
Another technique to multiplex I/O is using poll()
function, to which you pass a list of data structure containing the descriptor, whether you intend to read or write, and the function will tell you if the descriptor is ready for reading or writing. This function doesn't exist under Windows Sockets 2, but is implemented as a member function of System.Net.Socket
class, so is an option if you are using .Net Framework.
Above diagram and code segment are based on work by Thomas Yu and Peter Burden; please see references below for original documents.
Discovery and Resolution
Find Peer
To find out who you are connected to:
int getpeername(int fd, struct sockaddr *addr, int *addr_len);
This function will provide the socket address information based on socket descriptor. Then the data structure can be used with:
char* inet_ntoa(struct sockaddr addr);
to resolve the name of an IP address.
Find Host Environment
Host environment information (name, aliases, IP address, etc.) is stored in this data structure:
struct hostent {
char *h_name;
char **h_aliases;
short h_addrtype;
short h_length;
char **h_addr_list; // contains the IP address
};
If you have the name of a machine, this function can be used to find its network information:
struct hostent* gethostbyname(const char *name);
struct hostent* getipnodebyname(const char *name, int type, int flags, int *error);
or if you have the address instead, use these:
struct hostent* gethostbyaddr(const char *addr, int len, int type);
struct hostent* getipnodebyaddr(const void *name, int type, int flags, int *error);
The functions gethostbyname()
and gethostbyaddr()
are legacy functions, but they exist in Windows Sockets 2 implementation.
Sockets for Windows
Include winsock2.h
and most of the system calls have the same name; the only exception is closesocket()
for close
. See Winsock Functions for reference. Some of the constants are different between Linux and Windows:
Function
| Linux
| Windows
|
socket()
| PF_UNIX
|
|
| PF_INET
| AF_INET
|
| PF_INET6
| AF_INET6
|
|
| AF_IRDA
|
|
| AF_BTM
|
shutdown()
| SHUT_RD
| SD_RECEIVE
|
| SHUT_WR
| SD_SEND
|
| SHUT_RDWR
| SD_BOTH
|
When you get a SOCKET_ERROR
return value, you can call WSAGetLastError()
to find out why an error took place.
You can use System.Net.SocketAddress
and System.Net.Sockets.Socket
when working with .Net Framework. There are also classes you can use: TcpClient
, TcpListener
, and UdpClient
.
Notes
If you are using TCP, then it is possible that the message you are sending is too small in relation to the send buffer, and the system will try to buffer it to send along with other data. This will work against short messages such as key stroke, etc. One way to work around this issue is to send message as out-of-band. Or alternatively, you can set the socket option TCP_NODELAY to disable the Nagle algorithm. You can find more information here and here.
When working with .Net Framework sockets, sometimes it'll appear that one of your thread is stuck on Socket.Send()
. This may be related to the fact that sockets are blocking by default. You can find more information here, for some examples and analysis. You can make a socket non-blocking by setting the Blocking
property:
References
- OSI Model document from Wikipedia has an excellent diagram outlining the function and data unit of each layer:
| Data unit
| Layer
| Function
|
Host layers
| Data
| (7) Application
| Network process to application
|
(6) Presentation
| Data representation and encryption
|
(5) Session
| Interhost communication
|
Segments
| (4) Transport
| End-to-end connections and reliability (TCP)
|
Media layers
| Packets
| (3) Network
| Path determination and logical addressing (IP)
|
Frames
| (2) Data link
| Physical addressing (MAC and LLC)
|
Bits
| (1) Physical
| Media, signal and binary transmission
|
Number
| Protocol
|
0x1
| ICMP
|
0x6
| TCP
|
0x11
| UDP
|