This is the first article in a series about how to use various available interfaces to develop network programs for Linux. Like most Unix-based operating systems, Linux supports using TCP/IP as the local network transmission protocol. In this series, we assume that you are familiar with C Programming on Linux and some system knowledge on Linux, such as signals and forking.
This article describes how to use BSD interfaces to create network programs. In the next article, we will solve the problem of establishing (network) deamon processes. In the future, we will also discuss Using Remote Procedure Call (RPC) and using CORBA/distributed objects for development.
I. Introduction to TCP/IP
The TCP/IP protocol family allows two programs running on the same computer or two computers connected by the network to communicate. This protocol family is designed for communication on unreliable networks. TCP/IP allows two basic operation modes: connection-oriented reliable transmission (TCP) and connectionless transmission (UDP ).
TCP provides a sequential, reliable, bidirectional (bi-directional), and connection-based byte transmission stream with a relay function transparent to upper-layer protocols. TCP splits your information into a datagram (not greater than 64 KB) and ensures that all the datagram entries arrive at the destination in order. Because it is based on connections, a virtual connection must be established before communication between one network entity and the other. On the contrary, UDP provides a (very fast) connectionless and unreliable message transmission (the message size is a definite maximum length ).
In order for programs to communicate with each other, no matter whether they are on the same machine (through the loopback interface) or different hosts, each program must have an independent address.
A TCP/IP address consists of two parts: the IP address used to identify the machine and the port address used to identify the specific program on that machine.
The address can be in the dotted-quad format (for example, 127.0.0.1) or host name format (for example, www.csdn.net ). The system can use/etc/hosts or DNS Domain Name Service (if possible) to convert the host name to the point-to-point signed address (that is, the IP address.
Port Number starts from 1. 1 and IPP0RT_RESERVED (in/usr/include/netinet/in. the CIDR Block in h, usually 1024) is reserved for the system (that is, you must create a network service as root to bind this part of the port ).
Most of the simplest network programs use the customer-server model. A service process waits for a client process to connect to it. When a connection is established, the server executes a specific task on behalf of the customer, which usually interrupts the connection.
Ii. Use the BSD Interface
The most common TCP/IP programming method is to use BSD interface programming. Through it, network endpoints (IP addresses and port addresses) appear in the form of a set of interfaces (sockets.
This set of interface IPC (interprocess communication) Facilities (introduced from 4.2BSD) is designed to allow the design of network programs to be independent of different underlying communication facilities.
1. Create a server program
To create a server program on the BSD interface, follow these steps:
(1) Establish a set of interfaces through function socket ()
(2) bind an address (IP address and port address) through the function bind ). In this step, the server location is determined so that the client can know how to access the server.
(3) Use the listem () function to listen to new connection requests from the listen port.
(4) use the accept () function to accept new connections.
Generally, maintenance means that the customer's request may take a long time. When processing a request, it is also efficient to receive and process new requests. The most common way to achieve this is to allow the server to copy its own process through the fork () function to accept new connections.
The following example shows how the server is implemented in C:
/*
* Simple "Hello, World! "Server
* Ivan Griffin in
*/
/* Hellwolf Misty translated */
# Include /**/
# Include/* exit ()*/
# Include/* memset (), memcpy ()*/
# Include/* uname ()*/
# Include
# Include/* socket (), bind (),
Listen (), accept ()*/
# Include
# Include
# Include
# Include/* fork (), write (), close ()*/
/*
* Constants
*/
Const char MESSAGE [] = "Hello, World! \ N ";
Const int BACK_LOG = 5;
/*
* The program requires a command line parameter: the port number to be bound
*/
Int main (int argc, char * argv [])
{
Int serverSocket = 0,
On = 0,
Port = 0,
Status = 0,
ChildPid = 0;
Struct hostent * hostPtr = NULL;
Char hostname [80] = "";
Struct sockaddr_in serverName = {0 };
If (2! = Argc)
{
Fprintf (stderr, "Usage: % s \ n ",
Argv [0]);
Exit (1 );
}
Port = atoi (argv [1]);
/*
* Socket () system call with three parameters:
* 1. Specify the communication domain, such as PF_UNIX (unix domain), PF_INET (IPv4 ),
* PF_INET6 (IPv6)
* 2. type indicates the communication type, for example, SOCK_STREAM (for connection reliability,
* Such as TCP) and SOCK_DGRAM (non-connection-oriented non-reliable mode, such as UDP.
* 3. The protocol parameter specifies the protocol to be used. Although the same protocol
* The protocol family (or domain) specifies different protocols.
* Parameter, but usually only one. For TCP Parameters, you can specify IPPROTO_TCP.
* IPPROTO_UDP can be used for UDP. You do not need to explicitly set this parameter. If you use 0
* The two parameters use the default protocol.
*/
ServerSocket = socket (PF_INET, SOCK_STREAM,
IPPROTO_TCP );
If (-1 = serverSocket)
{
Perror ("socket ()");
Exit (1 );
}
/*
* Once a set of interfaces is established, its operation mechanism can be modified through the set of Interface Options (socket option.
*/
/*
* Set the SO_REUSEADDR option to re-use the old address (IP address and port number) without waiting.
* Note: in Linux, if a socket is bound to a port, the socket is normally closed or the program exits,
* The port remains bound for a period of time. Other programs (or the original program that restarts) cannot bind the port.
*
* In the following call: SOL_SOCKET indicates the operation on the SOCKET layer.
*/
On = 1;
Status = setsockopt (serverSocket, SOL_SOCKET,
SO_REUSEADDR,
(Const char *) & on, sizeof (on ));
If (-1 = status)
{
Perror ("setsockopt (..., SO_REUSEADDR ,...)");
}
/* When the connection is interrupted, You need to delay the linger to ensure that all data is
* Is transmitted, so you need to enable the SO_LINGER option.
* The linger structure is defined in/usr/include/linux/socket. h:
* Struct linger
*{
* Int l_onoff;/* Linger active */
* Int l_linger;/* How long to linger */
*};
* If l_onoff is 0, the feature will be canceled when the delay is disabled. If the value is not zero, the interface delay is allowed to be disabled.
* The l_linger field indicates the time when the delay is disabled.
*/
{
Struct linger = {0 };
Linger. l_onoff = 1;
Linger. l_linger = 30;
Status = setsockopt (serverSocket,
SOL_SOCKET, SO_LINGER,
(Const char *) & linger,
Sizeof (linger ));
If (-1 = status)
{
Perror ("setsockopt (..., SO_LINGER ,...)");
}
}
/*
* Find out who I am
*/
Status = gethostname (hostname,
Sizeof (hostname ));
If (-1 = status)
{
Perror ("gethostname ()");
Exit (1 );
}
HostPtr = gethostbyname (hostname );
If (NULL = hostPtr)
{
Perror ("gethostbyname ()");
Exit (1 );
}
(Void) memset (& serverName, 0,
Sizeof (serverName ));
(Void) memcpy (& serverName. sin_addr,
HostPtr-> h_addr,
HostPtr-> h_length );
/*
* H_addr is a synonym for h_addr_list [0,
* H_addr_list is an array of addresses.
* 4 (byte) represents the length of an IP address.
*/
/*
* To bind the server to all the IP addresses of the local machine,
* The above line of code should be replaced by the following code
* ServerName. sin_addr.s_addr = htonl (INADDR_ANY );
*/
ServerName. sin_family = AF_INET;
/* Htons: h (host byteorder, host byteorder)
* To n (network byteorder, network byte order
* S (short type)
*/
ServerName. sin_port = htons (port );
/* After an address (serverSocket in this example) is created
* It should be bound to the set interface we obtain.
*/
Status = bind (serverSocket,
(Struct sockaddr *) & serverName,
Sizeof (serverName ));
If (-1 = status)
{
Perror ("bind ()");
Exit (1 );
}
/* Now the set of interfaces can be used to listen for new connections.
* BACK_LOG specifies the listen queue for pending connections)
. When a new connection arrives and the queue is full, the customer will receive a connection rejection error.
* (This is the basis for dos attacks ).
*/
Status = listen (serverSocket, BACK_LOG );
If (-1 = status)
{
Perror ("listen ()");
Exit (1 );
}
/* Starting from here, the set of interfaces will begin to accept requests and serve them.
* This example uses a for loop to achieve this goal. Once the connection is accepted (accpepted ),
* The server can obtain the customer's address through a pointer to record the customer's login.
* Task.
For (;;)
{
Struct sockaddr_in clientName = {0 };
Int slaveSocket, clientLength =
Sizeof (clientName );
(Void) memset (& clientName, 0,
Sizeof (clientName ));
SlaveSocket = accept (serverSocket,
(Struct sockaddr *) & clientName,
& ClientLength );
If (-1 = slaveSocket)
{
Perror ("accept ()");
Exit (1 );
}
ChildPid = fork ();
Switch (childPid)
{
Case-1:/* ERROR */
Perror ("fork ()");
Exit (1 );
Case 0:/* child process */
Close (serverSocket );
If (-1 = getpeername (slaveSocket,
(Struct sockaddr *) & clientName,
& ClientLength ))
{
Perror ("getpeername ()");
}
Else
{
Printf ("Connection request from % s \ n ",
Inet_ntoa (clientName. sin_addr ));
}
/*
* Server application specific code
* Goes here, e.g. perform some
* Action, respond to client etc.
*/
Write (slaveSocket, MESSAGE,
Strlen (MESSAGE ));
/* You can also use the cached ANSI function fprint,
* Use fflush to refresh the cache if necessary.
*/
Close (slaveSocket );
Exit (0 );
Default:/* parent process */
Close (slaveSocket);/* This is a good habit
* The parent process disables the child process's interface descriptor.
* As the preceding sub-process disables the set interface descriptor of the parent process.
*/
}
}
Return 0;
}