Linux High Performance Server Programming Chapter 5 Linux network programming basic API

Posted by IwnfuM on Mon, 17 Jan 2022 15:54:24 +0100

5.1 socket address API

  • The accumulator of modern CPU can load (at least) 4 bytes at a time (32-bit machine is considered here, the same below), that is, an integer
    Number. Then the order in which these 4 bytes are arranged in memory will affect the value of the integer it is loaded into by the accumulator. This is byte order
    Question. Byte order is divided into big endian and little endian o big endian refers to
    The high byte (23 ~ 31 b it) of an integer is stored at the low address of the memory, and the low byte (0 ~ 7 b it) is stored at the high address of the memory. Small endian byte order means that the high-order byte of an integer is stored at the high address of memory, while the low-order byte is stored at the low address
    Stored at a low address in memory.
  • Modern PC s mostly use small endian byte order, so small endian byte order is also known as host byte order.
  • When formatted data (such as 32-bit integers and 16 bit short integers) are directly transmitted between two hosts using different byte order, the receiver must interpret it incorrectly. The solution to the problem is that the sender always sends
    The data is converted into large end byte order data and then sent, and the receiving end knows that the data transmitted by the other party always adopts large end word
    Node order, so the receiving end can decide whether to convert the received data according to its own byte order (small end machine conversion)
    Replace, the big terminal does not convert). Therefore, the large end byte order is also known as the network byte order, which provides data to all hosts receiving data
    A guarantee of correct interpretation of received formatted data

5.1.2 general socket address

  • The TCP/IP protocol family has sockaddr_in and sockaddr_in6 two dedicated socket address structures, which are used for IPv4 and IPv6 respectively

  • All special socket address (and sockaddr storage) variables need to be converted to general socket address type sockaddr (forced conversion) in actual use, because the address parameters used by all socket programming interfaces
    All types are sockaddr

  •  inet_ When ntop succeeds, it returns the address of the target storage unit. If it fails, it returns NULL and sets ermo

5.2 creating socket

  • The domain parameter tells the system which underlying protocol family to use. For the TCP/IP protocol family, this parameter should be set to PF_INET (Protocol Family of In tern et, for IPv4) or PF_INET6 (for IPv6); For the UNIX local domain protocol family, this parameter should be set to PF_UNIX. For all protocol families supported by socket system call, please refer to its man manual. The type parameter specifies the service type. Service types mainly include SOCK_STREAM service and SOCK_ UGRAM (datagram) service. For the TCP/IP protocol family, the value is SOCK_STREAM means that the transport layer uses the TCP protocol, which is taken as SOCK_DGRAM indicates that the transport layer uses UDP protocol.
  • It is worth noting that since Linux kernel version 2.6.17, the type parameter can accept the value of the above service type and the following two important flags: SOCK_NONBLOCK and SOCK_CLOEXEC, respectively, means that the newly created socket is set to non blocking, and the socket is closed in the child process when the child process is created with fork call. In Linux before kernel version 2.6.17, these two attributes of file descriptor need to be set by additional system calls (such as fcntl).
  • The protocol parameter is to select a specific protocol under the protocol set composed of the first two parameters. However, this value is usually unique (the first two parameters have completely determined its value). In almost all cases, we should set it to 0, indicating that the default protocol is used.
  • When the socket system call succeeds, a socket file descriptor is returned. If it fails, it returns - 1 and sets ermo

5.3 naming socket

  • When creating a socket, we assigned it an address family, but did not specify which specific socket address in the address family to use. Binding a socket to the socket address is called naming the socket In the server program, we usually name the socket, because only after naming can the client know how to connect to it. The client usually does not need to name the socket, but uses the anonymous method, that is, the socket address automatically assigned by the operating system. The system call named socket is bind, which is defined as follows:

  • bind will my_ The socket address referred to by addr is assigned to the unnamed sockfd file descriptor. The addrlen parameter indicates the length of the socket address.
  • bind returns 0 when it succeeds and - 1 when it fails, and sets ermo. Two common ermos are EACCES and eaddinuse, which mean
  • EACCES, the bound address is a protected address that can only be accessed by super users. For example, ordinary users will
    When the socket is bound to a well-known service port (port number 0 ~ 1023), bind will return EACCES error
  • Eaddinuse, the bound address is in use. For example, bind a socket to a time_ Socket address of wait status.

5.4 monitoring socket

After the socket is named, it cannot accept client connections immediately. We need to use the following system call to create a listening queue to store pending client connections:

  • The sockfd parameter specifies the maximum length of the monitored socketo backlog parameter, prompting the kernel to listen to the queue. If the length of the listening queue exceeds the backlog, the server will not accept new client connections, and the client will also receive the econnreused error message. In Linux before kernel version 2.2, the backlog parameter refers to the upper limit of all sockets in semi connected state (SYN_RCVD) and fully connected state (ESTABLISHED). However, since kernel version 2.2, it only represents the upper limit of sockets in fully connected state, and the upper limit of sockets in semi connected state is determined by / proc/sys/net/ipv4/tcp_max_syn_backlog kernel parameter definition. The typical value of the backlog parameter is 5.  
  • If listen succeeds, it returns 0. If it fails, it returns - 1 and sets ermo
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <csignal>
#include <cassert>
#include <unistd.h>

#include "include/algorithm_text.h"

static bool stop = false;
static void handle_term(int sig){
    stop = true;
}
int main(int argc,char* argv[]) {
    //Reference link https://blog.51cto.com/u_15284125/2988992
    //The first parameter of signal function refers to the signal to be processed, and the second parameter refers to the processing method (system default / ignore / capture)
    //SIGTERM requests to abort the process, and the kill command is sent to handle by default_ Term function
    signal(SIGTERM,handle_term);
    if (argc < 3){
        //basename reference link https://blog.csdn.net/Draven_Liu/article/details/38235585
        //Suppose the path is nihao / nihao / jhhh / TXT c
        //The basename function does not care whether the path is correct or whether the file exists, but just removes the last TXT from the path C this file name and other things are deleted, and then return to TXT Just C
        std::cout << "usage:" <<basename(argv[0]) << "ip_address port_number backlog\n"<<std::endl;
    }
    //argv[1] ip address
    //argv[2] port number
    //argv[3] log level
    const char* ip = argv[1];
    //atoi converts a string to an integer
    //Reference link https://www.runoob.com/cprogramming/c-function-atoi.html
    int port = atoi(argv[2]);
    int backlog = atoi(argv[3]);
    //The first parameter of socket programming indicates which underlying protocol family to use. For TCP/IP protocol family, this parameter should be set to PF_INET
    //For the TCP/IP protocol family, the value is SOCK_STREAM means that the transport layer uses the TCP protocol
    //The third parameter is to select a specific protocol under the protocol set composed of the first two parameters and set it to 0, indicating that the default protocol is used
    int sock = socket(PF_INET,SOCK_STREAM,0);
    //If the assertion is incorrect, it will not continue to execute
    assert(sock>=0);
    //Create an IPv4 socket address
    //TCP/IP protocol family sockaddr_in indicates the IPv4 dedicated socket address structure
    struct sockaddr_in address;
    // bzero() will clear the first n bytes of the memory block (string);
    // s is the memory (string) pointer, and n is the number of bytes to be cleared.
    // It is often used in network programming.
    bzero(&address,sizeof (address));
    address.sin_family = AF_INET;
    //int inet_pton(int af,const char* src,void* dst)
    //af specifies the address family AF_INET or AF_INET6
    //inet_ The Pton function successfully returns 1, fails to return 0, and sets errno
    //errno indicates various errors
    // inet_pton converts the IP address SRC represented by string (IPv4 address represented by dotted decimal and IPv6 represented by hexadecimal) into IP address represented by network byte order integer, and stores the conversion result in the memory pointed by dst
    inet_pton(AF_INET,ip,&address.sin_addr);
    //const char* inet_ntop(int af,const char* src,void* dst,socklen_t cnt)
    //inet_tpon function and INET_ The first three parameters have the same meaning, and the last parameter cnt specifies the size of the target storage unit
    //The address of the target unit is returned successfully. If it fails, NULL is returned and errno is set
    address.sin_port = htons(port);
    //bind will my_ The socket address (2) referred to by addr is assigned to the unnamed sockfd(1) file descriptor, and the addrlen parameter (3) indicates the length of the socket address.
    int ret = bind(sock,(struct sockaddr*)&address,sizeof (address));
    assert(ret != -1);
    ret = listen(sock,backlog);
    //Cycle and wait for the connection until a SIGTERM signal interrupts it
    while(!stop){
        sleep(1);
    }
    //Close socket
    close(sock);
    return 0;
}

  • When we change the third parameter of the server program and re run it, we can find the same rule, that is, there are at most (backlog+1) complete connections. On different systems, the running results will be somewhat different, but the upper limit of full connections in the listening queue is usually slightly larger than the backlog value.

5 . 5 accept connection

  • The sockfd parameter is the listening socket that has executed the listen system call. The addr parameter is used to obtain the remote socket address of the accepted connection. The length of the socket address is indicated by the addrlen parameter. When accept succeeds, a new connection socket is returned, which uniquely identifies the accepted connection. The server can communicate with the client corresponding to the accepted connection by reading and writing the socket. When accept fails, return - 1 and set ermo
  • We call the socket that has executed the listen call and is in the listen state as the listening socket, and all the sockets that are in the ESTABLISHED state as the connection socket
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <csignal>
#include <cassert>
#include <unistd.h>

#include "include/algorithm_text.h"

int main(int argc,char* argv[]) {
    if (argc <= 2){
        printf("usage:%s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in address{};
    bzero(&address,sizeof (address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    //htons converts an unsigned short integer value to network byte order, that is, big endian mode
    address.sin_port = htons(port);
    int sock = socket(PF_INET,SOCK_STREAM,0);
    assert(sock >= 0);
    int ret = bind(sock,(struct sockaddr*)&address,sizeof (address));
    assert(ret != -1);
    ret = listen(sock,5);
    //Wait 20 seconds for the client connection and related operations (drop / exit) to complete
    sleep(20);
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof (client);
    int connfd = accept(sock,(struct sockaddr*)&client,&client_addrlength);
    if (connfd < 0){
        printf("errno is:%d\n",errno);
    } else{
        //If the connection is accepted successfully, the IP address and port number of the client will be printed
        char remote[INET_ADDRSTRLEN];
        printf("connected with ip:%s and port:%d\n",
               inet_ntop(AF_INET,&client.sin_addr,remote,INET_ADDRSTRLEN),
               // Converts an unsigned short integer from network byte order to host byte order
               ntohs(client.sin_port));
        close(connfd);
    }
    close(sock);
    return 0;
}
  • accept takes the connection from the listening queue, regardless of the status of the connection (such as the ESTABLISHED status and CLOSE_WAIT status above), and does not care about any changes in network conditions

5.6 initiate connection

  • If the server passively accepts connections through the listen call, the client needs to actively establish connections with the server through the following system calls:

  • The sockfd parameter returns a socket from the socket system call. The serv addr parameter is the socket that the server listens to
    The addrlen parameter specifies the length of the address.  
  • 0 is returned when connect is successful. Once the connection is successfully established, sockfd uniquely identifies the connection, and the client can communicate with the server by reading and writing sockfd. If connect fails, return - 1 and set e rm s, two of which are common
    ermo is econnreused and ETIMEDOUT, which have the following meanings:
  • Econnreused, the target port does not exist, and the connection is rejected
  • ETIMEDOUT, connection timeout

5.7 closing the connection

  • Closing a connection is actually closing the socket corresponding to the connection, which can be completed by the following system call to close the ordinary file descriptor

  • The fd parameter is the socket to be closed. However, the close system call does not always close a connection immediately, but reduces the fd reference count by 1. Only if the reference count of fd is. The connection is really closed. In a multi process program, a fork system call will increase the reference count of the socket opened in the parent process by 1 by default. Therefore, we must perform a close call on the socket in both the parent process and the child process to close the connection.  
  • If you want to terminate the connection immediately anyway (instead of subtracting the reference count of the socket by 1), you can use the following shutdown system call (compared with close, it is specially designed for network programming)

  • The sockfd parameter is the socketo howto parameter to be closed, which determines the shutdown behavior. It can be taken from table 5 ・ 3
    A value.

  • It can be seen that shutdown can turn off the read or write on the socket respectively, or both. When closing the connection, close can only turn off the read and write on the socket at the same time o
  • When shutdown succeeds, it returns 0. If it fails, it returns - 1 and sets ermoo

5 . 8 data reading and writing
5.8.1 TCP data reading and writing

  • Read and write operations on files are also applicable to sockets, but the socket programming interface provides several system calls dedicated to socket data reading and writing, which increase the control of data reading and writing. The system calls used to read and write TCP stream data are:

  • Recv reads the data on sockfd. buf and Ien} parameters specify the location and size of the read buffer respectively. The meaning of flags parameter is shown later, which is usually set to 0. That is, when recv succeeds, it returns the length of the actually read data, which may be less than the expected length len. Therefore, we may have to call recv many times to read the complete data. Recv may return 0, which means that the communication partner has closed the connection. In case of recv error, - 1 is returned and ermo is set
  • Send writes data to sockfd, and the buf and len parameters specify the location and size of the write buffer respectively. When send succeeds, it returns the length of the actually written data. If it fails, it returns and sets errno
  • The flags parameter provides additional control for data sending and receiving. It can take the logical or of one or more of the options shown in the table

Sender code

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>

#include "include/algorithm_text.h"


int main(int argc,char* argv[]) {
    if (argc <= 2){
        printf("usage:%s ip_server_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in server_address{};
    bzero(&server_address,sizeof (server_address));
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    //htons converts an unsigned short integer value to network byte order, that is, big endian mode
    server_address.sin_port = htons(port);
    int sock_fd = socket(PF_INET,SOCK_STREAM,0);
    assert(sock_fd >= 0);
    if (connect(sock_fd,(struct sockaddr*)&server_address,sizeof (server_address))<0){
        printf("connected failed.\n");
    } else{
        const char* oob_data = "abc";
        const char* normal_data = "123";
        send(sock_fd,normal_data, strlen(normal_data),0);
        send(sock_fd,oob_data, strlen(oob_data),MSG_OOB);
        send(sock_fd,normal_data, strlen(normal_data),0);
    }
    
    close(sock_fd);
    return 0;
}

Receiver code

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>

#include "include/algorithm_text.h"
#define BUF_SIZE 1024


int main(int argc,char* argv[]) {
    if (argc <= 2){
        printf("usage:%s ip_server_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in server_address{};
    bzero(&server_address,sizeof (server_address));
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    //htons converts an unsigned short integer value to network byte order, that is, big endian mode
    server_address.sin_port = htons(port);
    int sock_fd = socket(PF_INET,SOCK_STREAM,0);
    assert(sock_fd >= 0);
    int ret = bind(sock_fd,(struct sockaddr*)&server_address,sizeof (server_address));
    assert(ret != -1);

    ret = listen(sock_fd,5);
    assert(ret != -1);

    struct sockaddr_in client{};
    socklen_t client_addrlength = sizeof (client);
    int connfd = accept(sock_fd,(struct sockaddr*)&client,&client_addrlength);
    if (connfd < 0){
        printf("errno is %d\n",errno);
    } else{
        char buffer[BUF_SIZE];
        memset(buffer,'\0',BUF_SIZE);
        ret = recv(connfd,buffer,BUF_SIZE-1,0);
        printf("got %d bytes of normal data '%s' \n",ret,buffer);

        memset(buffer,'\0',BUF_SIZE);
        ret = recv(connfd,buffer,BUF_SIZE-1,MSG_OOB);
        printf("got %d bytes of oob data '%s' \n",ret,buffer);

        memset(buffer,'\0',BUF_SIZE);
        ret = recv(connfd,buffer,BUF_SIZE-1,0);
        printf("got %d bytes of normal data '%s' \n",ret,buffer);

        close(connfd);
    }
    close(sock_fd);
    return 0;
}

5.8.2 UDP data reading and writing

  • recvfrom reads the data on sockfd. The buf and len parameters specify the location and size of the read buffer respectively. Because UDP communication does not have the concept of connection, we need to obtain the socket address of the sender every time we read data, that is, the parameter Src_ The addr parameter specifies the length of the address.
  • sendto writes data to sockfd, and the buf and len parameters specify the location and size of the write buffer respectively. dest_addr
    Parameter specifies the socket address of the receiving end, and addrlen parameter specifies the length of the address.
  • The meanings of the flags parameter and return value of these two system calls are the same as those of the flags parameter and return value of the send/recv system call.
  • It is worth mentioning that the recvGom/sendto system call can also be used for connection oriented socket
    For data reading and writing, you only need to set the last two parameters to NULL to ignore the socket address of the sender / receiver (because we have established a connection with the other party, we already know its socket address).

5.8.3 general data reading and writing function

  • socket programming interface also provides a pair of general data reading and writing system calls. They can be used not only for TCP stream data, but also for UDP datagrams:

  • The sockfd parameter specifies the target socket to be operated on. The msg parameter is a pointer to the msghdr structure type. The definition of msghdr structure is as follows

  • The msg name member points to a socket address structure variable. It specifies the socket address of the communication partner. For connection oriented TCP protocol, this member is meaningless and must be set to NULL because the other party's address is already known for the data stream socket. msg namelen member specifies MSG_ The length of the socket address indicated by name.
  • msg iov member is the pointer of iovec structure type. Iovec structure is defined as follows:

  • As can be seen from the above, the iovec structure encapsulates the starting position and length of a block of memory.
  • msg_iovlen specifies how many iovec structure objects there are.
  • For recvmsg, the data will be read and stored in msg_iovlen blocks are scattered in memory, and the location and length of these memories are specified by the array pointed to by msgiov, which is called scatter read;
  • For sendmsg, the data in msg iovlen block distributed memory will be sent together, which is called centralized write
  • msg contro 1 and MSG_ The controllen member is used to assist in the transmission of data. We will not discuss them in detail, but only describe how to use them to pass file descriptors between processes in Chapter 13.
  • msg_ The flags member does not need to be set. It will copy the contents of the flags parameter of recvmsg/sendmsg to affect the data reading and writing process. Recvmsg also sets some updated flags to msg flags before the call ends.
  • The meanings of the flags parameter and return value of recvmsg/sendmsg are the same as those of the flags parameter and return value of send/recv
    Same.

5.9 out of band marking

  • In practical applications, we usually can't predict when out of band data will arrive. Fortunately, when the Linux kernel detects the TCP emergency flag, it will notify the application that there is out of band data to receive. Two common ways for the kernel to notify applications of the arrival of out of band data are: abnormal events generated by I/O multiplexing and SIGURG signals. However, even if the application is notified that there is out of band data to receive, it also needs to know the specific location of out of band data in the data stream in order to accurately receive out of band data. This can be achieved through the following system calls:

  • Sockatmark determines whether sockfd is in the out of band flag, that is, whether the next read data is out of band data. If yes, sockatmark returns 1. At this time, we can use the recv call with MSGJDOB flag to receive out of band
    data If not, sockatmark returns 0.

5.10 address information function

  • In some cases, we want to know the local socket address of a connected socket and the remote socket address. The following two functions are used to solve this problem:

  • getsockname obtains the local socket address corresponding to sockfd and stores it in the address specified by the address parameter
    The length of the socket address is stored in the variable pointed to by the addressjen parameter. If the length of the actual socket address is greater than the size of the memory area indicated by address, the socket address will be truncated. Returned when getsockname succeeds
    Return 0, return - 1 if failed, and set errno
  • getpeername obtains the remote socket address corresponding to sockfd. The meaning of its parameters and return values is the same as that of getsockname
    The parameters and return values are the same

5.11 socket options

  • If fcntl system call is a general POSIX method for controlling file descriptor attributes, the following two system calls are specifically used to read and set socket file descriptor attributes:

  • The sockfd parameter specifies the target socket to be operated on. The level parameter specifies the options (i.e. properties) of which protocol to operate,
    For example, IPv4, IPv6, TCP, etc. option_ The iiame parameter specifies the name of the option. Table 5-5 lists several commonly used socket options in socket communication. The option value and option len parameters are the operated options respectively
    The value and length of the. Different options have different types of values, as shown in the data type column in the table.

  • getsockopt and setsockopt return 0 on success and - 1 on failure and set ermo
  • It is worth noting that for the server, some socket options can only be used for monitoring before calling the listen system call
    Listen to the socket settings. This is because the connection socket can only be returned by the accept call, and accept is returned from listen
    The connection accepted in the listen queue has completed at least the first two steps of the three TCP handshakes (because the connection in the listen listen queue has at least entered the SYN_RCVD state), which indicates that the server has sent a TCP synchronization message segment to the accepted connection. However, some socket options should be set in the TCP synchronization message segment, such as the TCP maximum message segment option (this option can only be sent by the synchronization message segment). In this case, the solution provided by Linux to developers is to set these socket options for the listening socket, and the connection socket returned by accept will automatically inherit these options. These socket options include: SO_DEBUG,SO_ DONTROUTE,SO_KEEPALIVE,SO_LINGER,SOJDOBINLINE,SO_ RCVBUF> SO_ RCVLOWAT,SO_SNDBUF,SO_ SNDLOWAT> TCP_ Maxseg and TCP_N0DELAYo for the client, these socket options should be set before calling the connect function, because after the connect call returns successfully, the TCP three handshakes have been completed.

5.11.1 SO_REUSEADDR option

  • Time of TCP connection_ Wait status and mention that the server program can set the socket option SO_REUSEADDR to force the use of time_ Socket address occupied by connection in wait state. The specific implementation method is shown in the code.
  • Reuse local IP address
    int sock = socket(PF_INET,SOCK_STREAM,0);
    assert(sock >= 0);
    int reuse = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    
    struct sockaddr_in address{};
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);
    int ret = bind(sock,(struct sockaddr*)&address,sizeof (address));
  • After setsockopt is set, even if the sock is at time_ In the wait state, the socket address bound to it can also be reused immediately. In addition, we can also modify the kernel parameter / proc/sys/net/ipv4/tcp_tw_recycle to quickly recycle the closed socket, so that the TCP connection does not enter time at all_ Wait state, which in turn allows applications to immediately reuse local socket addresses.

5.11.2 SO_RCVBUF and SO_SNDBUF option

  • SO_RCVBUF and so_ The sndbuf option indicates the size of TCP receive buffer and send buffer respectively. However, when we use setsockopt to set the size of TCP's receive buffer and send buffer, the system will double its value and must not be less than a minimum value. The minimum value of TCP receive buffer is 256 bytes, while the minimum value of transmit buffer is 2048 bytes (however, different systems may have different default minimum values). The purpose of the system is to ensure that a TCP connection has enough free buffer to deal with congestion (for example, the fast retransmission algorithm expects that the TCP receiving buffer can accommodate at least four TCP message segments with the size of SMSS). In addition, we can modify the kernel parameters directly
    /proc/sys/net/ipv4/tcp_rmem and / proc/sys/net/ipv4/tcp_wmem to force TCP to have no minimum limit on the size of the receive buffer and the send buffer. We will discuss these two kernel parameters in Chapter 16.

Client program for modifying TCP send buffer

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>

#include "include/algorithm_text.h"
#define BUF_SIZE 1024


int main(int argc,char* argv[]) {
    if (argc <= 2){
        printf("usage:%s ip_server_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in server_address{};
    bzero(&server_address,sizeof (server_address));
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    //htons converts an unsigned short integer value to network byte order, that is, big endian mode
    server_address.sin_port = htons(port);
    int sock_fd = socket(PF_INET,SOCK_STREAM,0);
    assert(sock_fd >= 0);

    int send_buf = atoi(argv[3]);
    int len = sizeof (send_buf);
    //First set the size of the TCP send buffer, and then read the data immediately
    setsockopt(sock_fd,SOL_SOCKET,SO_SNDBUF,&send_buf,len);
    getsockopt(sock_fd,SOL_SOCKET,SO_SNDBUF,&send_buf,(socklen_t*)&len);
    printf("the tcp send buffer size after setting is %d\n", send_buf);
    if (connect(sock_fd,(struct sockaddr*)&server_address,sizeof (server_address))!=-1){
            char buffer[BUF_SIZE];
            memset(buffer, 'a', BUF_SIZE);
            send(sock_fd, buffer, BUF_SIZE, 0);
    }
    close(sock_fd);
    return 0;
}

Server program for modifying TCP receive buffer

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>

#include "include/algorithm_text.h"
#define BUF_SIZE 1024


int main(int argc,char* argv[]) {
    if (argc <= 2){
        printf("usage:%s ip_server_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in server_address{};
    bzero(&server_address,sizeof (server_address));
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    //htons converts an unsigned short integer value to network byte order, that is, big endian mode
    server_address.sin_port = htons(port);
    int sock_fd = socket(PF_INET,SOCK_STREAM,0);
    assert(sock_fd >= 0);

    int recv_buf = atoi(argv[3]);
    int len = sizeof (recv_buf);
    //First set the size of the TCP receive buffer, and then read the data immediately
    setsockopt(sock_fd,SOL_SOCKET,SO_SNDBUF,&recv_buf,len);
    getsockopt(sock_fd,SOL_SOCKET,SO_SNDBUF,&recv_buf,(socklen_t*)&len);
    printf("the tcp receive buffer size after setting is %d\n", recv_buf);

    int ret = bind(sock_fd,(struct sockaddr*)&server_address,sizeof (server_address));
    assert(ret != -1);

    struct sockaddr_in client{};
    socklen_t client_addrlength = sizeof (client);
    int connfd = accept(sock_fd,(struct sockaddr*)&client,&client_addrlength);
    if (connfd < 0){
        printf("errno is:%d\n",errno);
    } else{
        char buffer[BUF_SIZE];
        memset(buffer,'\0',BUF_SIZE);
        while (recv(connfd,buffer,BUF_SIZE-1,0)>0){}
        close(connfd);

    }

    close(sock_fd);
    return 0;
}
  • From the output of the server, the minimum TCP receive buffer allowed by the system is 256 bytes. When we set up TCP
    When the size of the receive buffer is 50 bytes, our setting will be ignored. From the output of the client, we set
    The size of TCP send buffer has been doubled by the system. These two cases are consistent with what we discussed earlier.

5.11.3 SO_RCVLOWAT and SO_SNDLOWAT options

  • SO_RCVLOWAT and so_ The sndlowat option indicates the low water mark of TCP receive buffer and send buffer respectively. They are generally called by I/O multiplexing system (see Chapter 9) to determine whether the socket is readable or writable. When the total number of readable data in the TCP receive buffer is greater than its low water mark, the I/O multiplexing system call will notify the application that the data can be read from the corresponding socket; When the free space (the space where data can be written) in the TCP send buffer is greater than its low water mark, the I/O multiplexing system call will notify the application that data can be written to the corresponding socke.
  • By default, the low water mark of the TCP receive buffer and the low water mark of the TCP send buffer are both 1 byte.

5.11.4 SO_ Ringer options

  • SO_ The linker option controls the behavior of the close system call when closing a TCP connection. By default, when we use the close system call to close a socket, close will return immediately, and the TCP module is responsible for sending the remaining data in the TCP transmission buffer corresponding to the socket to the other party.
  • As shown in Table 5 ・ 5, set (get) so_ When selecting the value of the linker option, we need to pass a linker type structure to the setsockopt (getsockopt) system call, which is defined as follows:

  • According to the different values of the two member variables in the linker structure, the close system call may produce one of the following three behaviors:
  1. l_onoff equals 0 , so_ The linker option does not work. Close uses the default behavior to close the socket □
  2. l_onoff is not 0, l_ Linker equals 0. At this time, the close system call returns immediately, and the TCP module will discard the closed
    The TCP corresponding to the closed socket sends the remaining data in the buffer and sends a reset message segment to the other party (see section 3.5.2). Therefore, this situation provides the server with a way to terminate a connection abnormally.
  3. l onoff is not 0, l_ Linker is greater than 0. At this time, the behavior of close depends on two conditions: one is to be closed
    Whether there is residual data in the TCP transmission buffer corresponding to the socket of; The second is whether the socket is blocked or non blocked. For blocked sockets, close will wait for a long period of time until the TCP module sends all residual data and gets the other party's confirmation. If the TCP module fails to send the residual data and get the other party's confirmation during this period, the close system call will return - 1 and set ermo to EWOULDBLOCK; If the socket is non blocking, close will return immediately. At this time, we need to judge whether the residual data has been sent according to its return value and ermo. We will discuss blocking and non blocking in Chapter 8.

5.1.2 network information API

  • The two elements of socket address, namely IP address and port number, are expressed in numerical values. This is not easy to remember and expand (such as moving from IPv4 to IPv6). Therefore, in the previous chapter, we used the host name to access a machine and avoided using its IP address directly. Similarly, we use the service name instead of the port number. For example, the following two
  • The telnet command has exactly the same effect:
  • telnet 127.0.0.1 80
  • telnet localhost www
  • In the above example, the telnet client program realizes the conversion from host name to IP address and from service name to port number by calling some network information APIs. Next, we will discuss some important aspects of network information API

5.12.1 gethostbyname and gethostbyaddr

  • The gethostbyname function obtains the complete information of the host according to the host name, and the gethostbyaddr function obtains the complete information of the host according to the IP address.
  • The gethostbyname function usually finds the host in the local / etc/hosts configuration file first. If it is not found, then access the DNS server. These have been discussed in previous chapters. These two functions are defined as follows:

  • The name parameter specifies the host name of the target host, the addr parameter specifies the IP address of the target host, the len parameter specifies the length of the IP address indicated by addr, and the type parameter specifies the type of the IP address indicated by addi • whose legal values include AF_INET (for IPv4 addresses) and AF INET6 (for IPv6 addresses).
  • The two functions return pointers of the hostent structure type. The definition of the hostent structure is as follows:

5.12.2 getservbyname and getservbyport

  • The getservbyname function obtains the complete information of a service according to the name, and the getservbyport function obtains the complete information of a service according to the port number. They actually get service information by reading the / etc/services file. These two functions are defined as follows:

  • The name parameter specifies the name of the target service, and the port parameter specifies the port number corresponding to the target service. The proto parameter specifies the service type. Pass "tcp" to it to obtain the stream service, pass "udp" to it to obtain the datagram service, and pass NULL to it to obtain all types of services.
  • The two functions return pointers of the service structure type. The definition of the structure service is as follows:

  • Next, we access the daytime service on the target server through the host name and service name to obtain the system time of the machine, as shown in code listing 5-12.

Accessing the daytime service

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <netdb.h>

#include "include/algorithm_text.h"
#define BUF_SIZE 1024


int main(int argc,char* argv[]) {
    assert(argc == 2);
    char* host = argv[1];
    //Get destination host address information
    struct hostent* hostinfo = gethostbyname(host);
    assert(hostinfo);
    //Get daytime service information
    struct servent* servinfo = getservbyname("daytime","tcp");
    assert(servinfo);
    printf("daytime port is %d\n", ntohs(servinfo->s_port));

    struct sockaddr_in address{};
    address.sin_family = AF_INET;
    address.sin_port = servinfo->s_port;
    /* Pay attention to the code shown below because h_addr_list itself is an address list using network byte order, so when using the IP address in it,
     * There is no need to translate byte order to the target IP address*/
    address.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    int result = connect(sockfd,(struct sockaddr*)&address,sizeof (address));
    assert(result != -1);
    char buffer[128];
    result = read(sockfd,buffer,sizeof (buffer));
    assert(result > 0);
    buffer[result] = '\0';
    printf("the day tiem is: %s",buffer);
    close(sockfd);
    return 0;
}
  • It should be noted that the four functions discussed above are non reentrant, that is, non thread safe. But netdb H header files give their reentrant versions. As the naming rules for reentrant versions of all other functions under Linux, the function names of these functions are added at the end of the original function names_ r ( re-entrant) .

5.12.3 getaddrinfo

  • The getaddrinfo function can obtain the IP address through the host name (the gethostbyname function is used internally) and the port number through the service name (the getservbyname function is used internally). Whether it is reentrant depends on whether the gethostbyname and getservbyname functions called internally are their reentrant versions. This function is defined as follows:
  • #include <netdb.h>

  • The hostname parameter can receive either the host name or the IP address represented by a string (dotted decimal string for IPv4 and hexadecimal string for IPv6). Similarly, the service parameter can receive either the service name or the decimal port number represented by the string table. The hints parameter is a hint given to getaddrinfo by the application to control the output of getaddrinfo more accurately. The hints parameter can be set to n u l, indicating that getaddrinfo is allowed to feed back any available results. The result parameter points to a linked list that is used to store the results fed back by getaddrinfo.
  • Each result of getaddrinfo feedback is an object of addrinfo structure type. The definition of structure addrinfo is as follows:

 

  • In this structure, ai_protocol member refers to a specific network protocol. Its meaning is the same as the third parameter of socket system call. It is usually set to 0. ai_ The flags member can take the bitwise or of the flags in table 5-6.

  • When we use the hints parameter, we can set its ai_flags, ai_family, ai_socktype and ai_protocol has four fields, and the other fields must be set to NULL. For example, listing 13 uses the hints parameter to obtain the "daytime" stream service information on the host's EST laptop

 5.12.4 getnameinfo

  • The getnameinfo function can simultaneously obtain the host name (internally using the gethostbyaddr function) and the service name (internally using the getservbyport function) represented by a string through the socket address. Whether it is reentrant depends on whether the internally called gethostbyaddr and getservimport functions are their reentrant versions. This function is defined as follows:

  • Getnameinfo stores the returned host name in the cache pointed to by the host parameter and the service name in the cache pointed to by the serv parameter. The hostlen and servlen parameters specify the length of the two caches respectively. The Hags parameter controls the behavior of getnameinfo, which can receive the options in table 5-7.

  • The getaddrinfo and getnameinfo functions return 0 on success and error codes on failure. The possible error codes are shown in the table

  • The strerror function under Linux can convert the numeric error code ermo into a readable string form. Similarly, the following function can convert the error code in table 5-8 into its string form:

​​​​​​​

Reference link

Topics: C++ Linux