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.
Basic Introduction
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.
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.
Create a server program
To create a server program on the BSD interface, follow these steps:
1) Establish an interface 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 (ivan.griffin@ul.ie) * // * Hellwolf Misty translated */# include/**/# include/* exit () */# include/* memset (), memcpy () */# include/* uname () */# include/* socket (), bind (), listen (), accept () */# 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. The parameter domain indicates the communication domain, such as PF_UNIX (unix domain), PF_INET (IPv4), and * PF_INET6 (IPv6) 2. type indicates the communication type, such as SOCK_STREAM (for connection reliability mode, * for TCP) and SOCK_DGRAM (for non-connection-oriented non-reliability mode, such as UDP. * 3. The protocol parameter specifies the protocol to be used. Although you can specify different protocol * parameters for the same protocol * family (or communication domain), there is usually only one. For TCP Parameters, you can specify IPPROTO_TCP. For * UDP, you can use IPPROTO_UDP. You do not need to explicitly set this parameter. If 0 is used, the default protocol is used based on the first * two parameters. */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 set interface to re-use the old address (IP address and port number) without waiting * Note: in Linux, if a socket is bound to a port, after the socket is normally closed or the program exits, * the port remains bound for a period of time, other programs (or the original program restarted) 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, the linger must be delayed to ensure that all data is transmitted, therefore, you need to enable the SO_LINGER option. * The linger structure is in/usr/include/linux/socket. defined in 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 it is delayed. 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. rochelle 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], and * h_addr_list is an array of addresses * with a length of 4 (byte) represents the length of an IP address * // ** in order to bind the server to all the IP addresses of the Local Machine, * the preceding code must replace * serverName with the following code. sin_addr.s_addr = htonl (INADDR_ANY); */serverName. sin_family = AF_INET;/* htons: H (host byteorder, host byte order) * 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 of interfaces 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 maximum length of 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 perform some tasks such as recording Customer Login. 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 (serve RSocket); 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, * as long as you remember to use fflush to refresh the cache */close (SlaveSocket); exit (0); default:/* parent process */close (slaveSocket ); /* This is a good habit. * The parent process disables the set interface descriptor of the child process * just as the parent process disables the set interface descriptor of the parent process. */} Return 0 ;}
|