Socket create, connect and close functions

    The following figure is a timeline of some typical events that occur between a pair of TCP client and server processes.

    To perform network I/O, the first thing a process must do is call the socket function, specifying the desired communication protocol type.
#include <sys/socket.h>
int socket(int family, int type, int protocol);
                                   /* return: non-negative descriptor if successful; otherwise -1 */

    Among them, the family parameter specifies the protocol family, the type parameter specifies the socket type, and the protocol parameter is a constant value of a protocol type, or is set to 0 to select the system default value for the given combination of family and type.
    The following tables give the values ​​of the parameters family, type, protocol, and the combination of family and type, respectively.



    A TCP client uses the connect function to establish a connection with a TCP server.
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
                           /* Return: 0 if successful; otherwise -1 */

    sockfd is the socket descriptor returned by the socket function. The second and third parameters respectively point to a socket address structure containing the server's IP address and port number and the size of the structure.
    The client does not have to call the bind function before calling connect because the kernel determines the source IP address and selects an ephemeral port as the source port if needed. In the case of a TCP socket, calling the connect function will trigger the TCP three-way handshake process and return only if the establishment is successful or an error occurs. The error return may have the following situations.
    1. If the TCP client does not receive the response of the SYN subsection within a certain period of time, it will return an ETIMEDOUT error.
    2. If the response to the client's SYN is RST (representing reset), it indicates that the server host has no process waiting to connect to it on the specified port (for example, the server process may not be running). This is a hard error, and the client returns an ECONNREFUSED error as soon as the RST is received. Generally, the three conditions for generating RST are: a SYN destined for a port arrives, but there is no server listening on the port; TCP wants to cancel an existing connection; TCP receives a segment on a connection that does not exist at all.
    3. If the SYN sent by the client causes an ICMP error of "destination unreachable" on a router in the middle, it is considered to be a soft error. The interval continues to send SYNs. If no response is received after a specified time, the saved message is returned to the process as an EHOSTUNREACH or ENETUNREACH error. In addition, these two situations are also possible: one is that there is no path to the remote system at all according to the forwarding table of the local system; the other is that the connect call returns without waiting at all.
    According to the TCP state transition diagram , the connect function causes the current socket to transition from the CLOSED state to the SYN_SENT state. If connect fails, the socket is no longer available and must be closed, and the connect function cannot be called again on such a socket. When this function is called in a loop to try each IP address for a given host until one succeeds, after each connect failure, the current socket descriptor must be closed and socket re-called.

    The bind function assigns a local protocol address to a socket.
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
                                       /* Return value: 0 if successful; otherwise -1 */

    where the second parameter is a pointer to a protocol-specific address structure. For TCP, the bind function can be called to specify a port number, or an IP address, or both, or neither. The following table summarizes how to set the values ​​of sin_addr/sin6_addr and sin_port/sin6_port according to the expected result.

    If the specified port number is 0, the kernel selects an ephemeral port when bind is called, and bind cannot return the port value. To get the ephemeral port value, only the function getsockname can be called to return the protocol address.
    If the specified IP address is a wildcard address, the kernel will wait until the socket is connected (TCP) or when a datagram has been sent on the socket (UDP) before choosing a local IP address. Letting the kernel choose an ephemeral port is normal for TCP clients unless a reserved port is required, but extremely rare for TCP servers, since servers are known through their well-known ports (the exception to this rule is remote Procedure call (Remote Procedure Call, RPC) servers, they usually have an ephemeral port selected by the kernel for their listening socket, and this port is then registered with the RPC port mapper. Before calling connect to these servers, the client must communicate with port mappers to get their ephemeral ports, this also applies to UDP RPC servers).
    For IPv4 addresses, the wildcard address is specified by the constant value INADDR_ANY, whose value is usually 0, which tells the kernel to choose an IP address. Typically it is used like this:
            struct sockaddr_in servaddr;
            servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Although the value of INADDR_ANY is the same whether it is network byte order or host byte order, htonl does not need to be used, but since all the "INADDR_" constant values ​​in the header file <netinet/in.h> are in accordance with the host byte order Defined, so in order to unify the format, it is also recommended to use htonl.
    For IPv6, because the 128-bit IPv6 address is stored in a structure (the right side of the assignment statement in C language cannot represent a constant value structure), the wildcard address is generally used like this:
            struct sockaddr_in6 serv;
            serv.sin6_addr = in6addr_any ;
    The header file <netinet/in.h> contains the extern declaration of in6addr_any, the system pre-allocates the in6addr_any variable and initializes it to the constant value IN6ADDR_ANY_INIT.
    A common error returned from the bind function is EADDRINUSE ("Address already in use").

    The listen function is only called by the TCP server, and should be called after socket and bind are called, and before the accept function is called.
#include <sys/socket.h>
int listen(int sockfd, int backlog); /* Return value: 0 if successful, -1 otherwise */

    The second parameter of this function specifies the maximum number of connections that the kernel should queue for the corresponding socket. To understand this parameter, one must realize that the kernel maintains two queues for each given listening socket:
    (1) The outstanding connection queue. Each such SYN section corresponds to one of: has been sent by a client and arrived at the server, which is waiting for the corresponding TCP three-way handshake to complete. These sockets are in SYN_RCVD state.
    (2) The connection queue has been completed. For each client that has completed the TCP three-way handshake, these sockets are in the ESTABLISHED state.
    The figure below depicts these two queues listening on sockets.

    When the SYN from the client arrives, TCP creates a new entry in the queue of outstanding connections (if the queue is full, TCP ignores the subsection), and does not move the entry until the three-way handshake completes normally. The tail of the completed connection queue. When the process calls accept, the head of the queue entry in the completed connection queue will be returned to the process, or if the queue is empty, the process will be put to sleep and won't wake up until TCP puts an entry in the queue (assuming the default the blocking socket).
    Data arriving after the three-way handshake, but before the server calls accept, shall be queued by the server TCP up to the size of the receive buffer for the corresponding connected socket.

    The accept function is also called by the TCP server to return the next completed connection from the head of the completed connection queue.
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
                                    /* Return value: non-negative descriptor if successful, -1 otherwise */

    The parameters cliaddr and addrlen are used to return the protocol address of the connected peer process. If you are not interested in returning the client's protocol address, you can set them as null pointers. addrlen is a value-result parameter: before calling, set the integer value referenced by addrlen to the length of the socket address structure valued by cliaddr, and when returning, the integer value is the value stored in the socket address by the kernel The exact number of bytes inside the structure. If accept is successful, a brand new descriptor automatically generated by the kernel representing the TCP connection to the returned client is returned. In contrast to the listening socket descriptor, we call it the connected socket descriptor.

    Sockets are closed using the usual file descriptor close function close.
#include <unistd.h>
int close(int sockfd); /* Return value: 0 if successful; otherwise -1 */

    After the socket is closed, the socket descriptor can no longer be used by the calling process, and TCP sends the normal TCP connection termination sequence after attempting to send any data that has been queued to be sent to the peer. But in fact, the close function just decrements the reference count of the socket descriptor by 1. If the reference count of the descriptor is still greater than 0, the close call does not trigger the TCP quad-packet connection termination sequence (if you really want to A FIN is sent on a TCP connection, and the shutdown function can be called). This is also what is expected for a concurrent server where the parent process shares the connected socket with the child process. So in a concurrent server, the connected descriptor in the parent process must be explicitly closed, otherwise only closing (explicitly or implicitly) in the child process will just reduce its reference count from 2 to 1, the description Descriptors will continue to occupy resources in the parent process, and eventually as more and more connections are established, the parent process will run out of available descriptors. Therefore, the program outline of a typical concurrent server is generally as follows (in order to avoid bloated code, no judgment is made on the execution results of bind and so on).
#include <unistd.h> // fork function header file

pid_t pid;
int	listenfd, connfd;
listenfd = socket(...);

/* Fill in the socket address and port part */

bind(listenfd, ...);
listen(listenfd, LISTENQ);
// Here you should also call the signal function to catch the SIGCHLD signal before entering the loop to avoid spawning a zombie process.
// Also, on most systems the signal received may cause the parent process to die with an EINTR error (interrupted system call)
// stop, so for portability, you should also do some processing on the signal function of the standard library, such as setting the sigaction structure
// The sa_flags flag is SA_RESTART or SA_INTERRUPT, so that the kernel can automatically restart the interrupted system
// System calls, continue slow system calls such as accept that were previously blocked, because the basic rules that apply to slow system calls are:
// When a process blocked on a slow system call catches a signal and the corresponding signal handler returns, the system call
// May return an EINTR error.
for(;;){
	connfd = accept(listenfd, ...);
	if((pid=fork()) == 0){ // child process
		close(listenfd); // Display close shared listening socket, can be omitted
		/* process the request */
		close(connfd); // Displays closed shared connected sockets, can be omitted
		exit(0);` // The child process exits to prevent it from continuing to execute
	}
	close(connfd); // parent process must explicitly close the connected socket descriptor
}

    The usual way to terminate a network connection is to call the close function, but close has two limitations that can be avoided by using the shutdown function.
    (1) close decrements the reference count of the descriptor by 1, and closes the socket only when the count becomes 0, and shutdown can trigger the normal connection termination sequence of TCP regardless of the reference count.
    (2) close will terminate the data transfer in both directions of reading and writing at one time, while shutdown can only close half of the TCP connection.
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
                         /* Return value: if successful, return 0; otherwise, return -1 */

    The value of the parameter howto determines the shutdown behavior of the shutdown function.
    * SHUT_RD: Indicates the read half of the closed connection - there is no more data to read in the socket, and any existing data in the socket's receive buffer is discarded. The process can no longer call any read functions on such a socket, and any data received by the socket from the peer is acknowledged and then silently discarded.
    * SHUT_WR: Close the write half of the connection - for TCP sockets, this is called a half-close. Data currently remaining in the socket send buffer will be sent, followed by TCP's normal connection termination sequence. Such write-half closes are performed regardless of whether the socket descriptor's reference count is 0 or not. A process can no longer call any write functions on such a socket.
    * SHUT_RDWR: Both the read and write halves of the connection are closed.

    The getsockname function can be used to obtain the local protocol address associated with a socket, and getpeername can be used to obtain the foreign protocol address associated with a socket.
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
                               /* Return value: if successful, return 0; otherwise, return -1 */

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326203015&siteId=291194637