Android system principle problem analysis - C/S model in the case of single channel

statement

  • In the Android system, we often encounter some system principle problems, which will be discussed in this column.
  • Many places in the Android system use the I/O multiplexing mechanism. In order to introduce the I/O multiplexing mechanism, first analyze the C/S model in the case of multi-channel concurrency.
  • This article refers to some blogs and books. The code is based on Android 7.1.1. It is not convenient to list them one by one. It is only for learning and knowledge sharing.
  • Download this code: CS model tcp-socket udp-socket ud-socket in the case of single channel

1 Overview

In a typical client/server scenario, applications communicate using sockets as follows:

  • Each application creates a socket. A socket is a "device" that allows communication, and both applications need to use it.
  • The server binds its socket to a well-known address (name) so that clients can locate its location.

A socket can be created using the socket() system call, which returns a file descriptor used to refer to the socket in subsequent system calls.

#include <sys/socket.h>
int socket(int domain, int type, int protocol);		//returns file descriptor on success, or -1 on error

Regardless of the parameter protocol, the protocol parameter in this article is set to 0. If you are interested in this parameter, you can search and learn by yourself. Mainly analyze the first two parameters: domain and type.

1.1 Communication domains

The socket exists in a communication domain, which determines:

  • Identify the method of a socket (that is, the format of the socket "address");
  • The scope of communication (between applications located on the same host or between applications located on different hosts connected using a network), that is, the concept of IPC or RPC.

Modern operating systems support at least the following domains:

  • UNIX(AF_UNIX) domain : Allows communication between applications on the same host.
  • IPv4(AF INET) domain : Allows communication between applications on hosts connected using the Internet Protocol IP4 network.
  • IPV6 (AF INET6) domain : Allows communication between applications on hosts connected using Internet Protocol IPV6 networks.
Domain domain communication performed communication between applications address format address structure
Unix Domain OF_UNIX in the kernel same host path name sockaddr un
Internet Domain OF_INET via IPv4 hosts connected via an IPv4 network 32-bit IPV4 address + 16-bit port number sockaddrin
Internet Domain AF_INET6 via IPv6 Hosts linked up over an IPv6 network 128-bit IPv6 address + 16-bit port number sockaddr in6

1.2 socket type

  Every socket implementation provides at least two sockets: stream and datagram. Both socket types are supported in Unix domain and Internet domain. The following table summarizes the properties of the two socket types:

Attributes socket type
Stream (SOCK_STREAM) Datagram (SOCK_DGRAM)
Reliable transmission? Yes No
message boundaries preserved? No Yes
link-oriented? Yes No

  Stream socket (SOCK_STREAM) provides a reliable bidirectional byte stream communication channel. The terms used in this description have the following meanings:

  • Reliable: Indicates that the data transmitted by the sender can be guaranteed to reach the receiving application intact (assuming that neither the network link nor the receiver will crash) or receive a notification that the transmission failed.
  • Bidirectional: Indicates that data can be transmitted in any direction between two sockets.
  • Byte stream: Represents the concept that there is no message boundary like a pipeline.

  A stream socket is similar to using a pair of pipes that allow two-way communication between two applications, the difference between them is that (Internet domain) sockets allow communication over the network.
  The normal operation of stream socket requires a pair of interconnected sockets, so stream socket is often called connection-oriented. The term "peer socket" refers to the socket at the other end of the connection, "peer address" means the address of the socket, and "peer application" means the application program utilizing the peer socket. Sometimes the term "remote" (or external) is used synonymously as an equivalent. Similarly, the term "local" is sometimes used to refer to the application, socket, or address on this end of the connection. A streaming socket can only be connected to one peer socket.

  Datagram sockets (SOCK_DGRAM) allow data to be exchanged in the form of messages called datagrams. In datagram sockets, message boundaries are preserved, but data transmission is unreliable. Messages may arrive out of order, duplicated, or not arrive at all.
  Datagram sockets are an example of the more general concept of connectionless sockets. Unlike stream sockets, a datagram socket does not need to be connected to another socket to use it.
In the Internet domain, datagram sockets use the User Datagram Protocol (UDP), while stream sockets (usually) use the Transmission Control Protocol (TCP). Generally speaking, the terms "Internet domain datagram socket" and "Internet domain stream socket" are not used when referring to these two sockets, but the terms "UDP socket" and "TCP socket" are used respectively.

1.3 Socket-related system calls

The key socket system calls include the following:

  • The socket() system call creates a new socket.
  • The bind() system call binds a socket to an address. Usually, the server needs to use this call to bind its socket to a well-known address so that the client can locate the socket.
  • The listen() system call allows a streaming socket to accept incoming connections from other sockets.
  • The accept() system call accepts a connection from a peer application on a listening stream socket, and optionally returns the address of the peer socket.
  • The connect() system call establishes a connection with another socket.

2 C/S model of Internet DOMAIN

2.1 C/S model of TCP socket (SOCK_STREAM)

insert image description here
1. The process of establishing a connection :
  After the server calls socket(), bind(), listen() to complete the initialization, it calls accept() to block and wait, and is in the state of listening to the port. After the client calls socket() to initialize, call connect() to send a SYN segment and block waiting for the server to reply. The server responds with a SYN-ACK segment, and the client returns from connect() after receiving it, and at the same time responds with an ACK segment, and the server receives Then return from accept().

2. The process of data transmission :
  After the connection is established, the TCP protocol provides full-duplex communication services, but the general C/S program flow is that the client initiates a request actively, and the server passively processes the request, in a question-and-answer manner. Therefore, the server calls read() immediately after returning from accept(). Reading the socket is like reading a pipeline. If no data arrives, it will block and wait. At this time, the client calls write() to send the request to the server, and the server receives it from the read () returns to process the client's request. During this period, the client calls read() to block and wait for the server's response. The server calls write() to send the processing result back to the client, and calls read() again to block and wait for the next request. , the client returns from read() after receiving it, and sends the next request, and so on.

3. Disconnection process :
  If the client has no more requests, call close() to close the connection, just like the pipeline closed by the write end, the server's read() returns 0, so the server knows that the client is closed connection, also call close() to close the connection.

Note : After either party calls close(), both transmission directions of the connection are closed, and no more data can be sent. If one party calls shutdown(), the connection is in a half-closed state, and the data sent by the other party can still be received.

2.1.1 Server code

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>

#include "wrap.h"

#define SERV_PORT 6666

int main(void)
{
    
    
    int sfd, cfd;
    int len, i;
    char buf[BUFSIZ], clie_IP[BUFSIZ];

    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;

    sfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serv_addr, sizeof(serv_addr));           
    serv_addr.sin_family = AF_INET;                 
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    serv_addr.sin_port = htons(SERV_PORT);          

    Bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    Listen(sfd, 2);                                

    printf("wait for client connect ...\n");

    clie_addr_len = sizeof(clie_addr_len);
    cfd = Accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
    printf("cfd = ----%d\n", cfd);

    printf("client IP: %s  port:%d\n", 
            inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), 
            ntohs(clie_addr.sin_port));

    while (1) {
    
    
        len = Read(cfd, buf, sizeof(buf));
        Write(STDOUT_FILENO, buf, len);

        for (i = 0; i < len; i++)
            buf[i] = toupper(buf[i]);
        Write(cfd, buf, len); 
    }

    Close(sfd);
    Close(cfd);

    return 0;
}

2.1.2 Client code

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "wrap.h"

#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666

int main(void)
{
    
    
    int sfd, len;
    struct sockaddr_in serv_addr;
    char buf[BUFSIZ]; 

    sfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serv_addr, sizeof(serv_addr));                       
    serv_addr.sin_family = AF_INET;                             
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);    
    serv_addr.sin_port = htons(SERV_PORT);                      

    Connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    while (1) {
    
    
        fgets(buf, sizeof(buf), stdin);
        int r = Write(sfd, buf, strlen(buf));       
        printf("Write r ======== %d\n", r);
        len = Read(sfd, buf, sizeof(buf));
        printf("Read len ========= %d\n", len);
        Write(STDOUT_FILENO, buf, len);
    }

    Close(sfd);

    return 0;
}

  Since the client does not need a fixed port number, there is no need to call bind(), and the client's port number is automatically assigned by the kernel. Note that the client is not allowed to call bind(), but it is not necessary to call bind() to fix a port number, and the server does not have to call bind(), but if the server does not call bind(), the kernel will automatically assign a listening port to the server , the port number is different every time the server is started, and it is very troublesome for the client to connect to the server .

2.2 C/S model of UDP socket (SOCK_DGRAM)

insert image description here
  Since UDP does not need to maintain connections, the program logic is much simpler, but the UDP protocol is unreliable, and the mechanism to ensure communication reliability needs to be implemented at the application layer .

2.2.1 Server code

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>

#define SERV_PORT 8000

int main(void)
{
    
    
    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;
    int sockfd;
    char buf[BUFSIZ];
    char str[INET_ADDRSTRLEN];
    int i, n;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);

    bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    printf("Accepting connections ...\n");
    while (1) {
    
    
        clie_addr_len = sizeof(clie_addr);
        n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
        if (n == -1)
            perror("recvfrom error");

        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
                ntohs(clie_addr.sin_port));

        for (i = 0; i < n; i++)
            buf[i] = toupper(buf[i]);

        n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
        if (n == -1)
            perror("sendto error");
    }
    close(sockfd);

    return 0;
}

2.2.2 Client code

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>

#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    
    
    struct sockaddr_in servaddr;
    int sockfd, n;
    char buf[BUFSIZ];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    while (fgets(buf, BUFSIZ, stdin) != NULL) {
    
    
        n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
        if (n == -1)
            perror("sendto error");

        n = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, 0);         //NULL:不关心对端信息
        if (n == -1)
            perror("recvfrom error");

        write(STDOUT_FILENO, buf, n);
    }

    close(sockfd);

    return 0;
}

2.3 The difference between TCP socket and UDP socket

  The protocol model mainly used by the transport layer in Internet DOMAIN: TCP protocol and UDP protocol. The TCP protocol plays a dominant role in network communication, and the vast majority of network communication uses the TCP protocol to complete data transmission. But UDP is also an indispensable means of communication in network communication.
  Compared with TCP, the form of UDP communication is more like sending text messages. There is no need to establish and maintain a connection before data transmission. Just focus on getting the data. The process of three-way handshake is omitted, and the communication speed can be greatly improved , but the stability and accuracy of the accompanying communication cannot be guaranteed. Therefore, UDP is called "connectionless unreliable message delivery".
  Compared with TCP, what are the advantages and disadvantages of UDP? Since there is no need to create a connection, the UDP overhead is small, the data transmission speed is fast, and the real-time performance is strong. It is mostly used in communication occasions that require high real-time performance, such as video conferencing, teleconferencing, etc. But it is also accompanied by unreliable data transmission, and the accuracy, transmission sequence and flow of transmitted data cannot be controlled and guaranteed. Therefore, under normal circumstances, the UDP protocol is used for data transmission. In order to ensure the correctness of the data, it is necessary to add an auxiliary verification protocol at the application layer to make up for the shortcomings of UDP, so as to achieve the purpose of reliable data transmission .
  Similar to TCP, UDP may also lose packets when receiving data after the buffer is filled . Since it does not have a TCP sliding window mechanism, the following two methods are usually used to solve it:

  1. The server application layer designs flow control to control the speed of sending data.
  2. Change the receive buffer size with the setsockopt function. like:
    #include <sys/socket.h>
    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    
    int n = 220x1024
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
    

3 C/S Model of Unix DOMAIN

  The socket API was originally designed for network communication, and later developed an IPC mechanism based on the socket framework, which is Unix Domain Socket . Although the network socket can also be used for inter-process communication on the same host (through the loopback address 127.0.0.1), the Unix Domain Socket is more efficient for IPC: it does not need to go through the network protocol stack, pack and unpack, and calculate the checksum , maintenance sequence number and response, etc., just copy the application layer data from one process to another . This is because the IPC mechanism is essentially reliable communication, while the network protocol is designed for unreliable communication. Unix Domain Socket also provides stream-oriented and datagram-oriented API interfaces, similar to TCP and UDP, but message-oriented Unix Domain Socket is also reliable, and messages will neither be lost nor out of sequence .
insert image description here
  UNIX Domain Socket is full-duplex, and the API interface has rich semantics. Compared with other IPC mechanisms, it has obvious advantages and has become the most widely used IPC mechanism. The process of using Unix Domain Socket is similar to that of network socket. You must first call socket() to create a socket file descriptor. Address_family is specified as AF_UNIX, type can be selected as SOCK_DGRAM/SOCK_STREAM, and the protocol parameter is still specified as 0.
  The most obvious difference between Unix Domain Socket and network socket programming is that the address format is different. It is represented by the structure sockaddr_un. The socket address of network programming is IP address + port number, while the address of Unix Domain Socket is a socket type file in the file system. path of, the socket file was created by a bind() call, and if the file already exists when bind() is called, bind() returns an error .

Compare the network socket address structure with the local socket address structure:

struct sockaddr_in {
    
    
__kernel_sa_family_t sin_family; 			/* Address family */  	地址结构类型
__be16 sin_port;					 	/* Port number */		端口号
struct in_addr sin_addr;					/* Internet address */	IP地址
};

struct sockaddr_un {
    
    
__kernel_sa_family_t sun_family; 		/* AF_UNIX */			地址结构类型
char sun_path[UNIX_PATH_MAX]; 		/* pathname */		socket文件名(含路径)
};

3.1 C/S data model (SOCK_STREAM)

3.1.1 Server code

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>

#include "wrap.h"

#define SERV_ADDR  "serv.socket"

int main(void)
{
    
    
    int lfd, cfd, len, size, i;
    struct sockaddr_un servaddr, cliaddr;
    char buf[4096];

    lfd = Socket(AF_UNIX, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path,SERV_ADDR);

    len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);     /* servaddr total len */

    unlink(SERV_ADDR);                              /* 确保bind之前serv.sock文件不存在,bind会创建该文件 */
    Bind(lfd, (struct sockaddr *)&servaddr, len);           /* 参3不能是sizeof(servaddr) */

    Listen(lfd, 20);

    printf("Accept ...\n");
    while (1) {
    
    
        len = sizeof(cliaddr);
        cfd = Accept(lfd, (struct sockaddr *)&cliaddr, (socklen_t *)&len);

        len -= offsetof(struct sockaddr_un, sun_path);      /* 得到文件名的长度 */
        cliaddr.sun_path[len] = '\0';                       /* 确保打印时,没有乱码出现 */

        printf("client bind filename %s\n", cliaddr.sun_path);

        while ((size = read(cfd, buf, sizeof(buf))) > 0) {
    
    
            for (i = 0; i < size; i++)
                buf[i] = toupper(buf[i]);
            write(cfd, buf, size);
        }
        close(cfd);
    }
    close(lfd);

    return 0;
}

3.1.2 Client code

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>

#include "wrap.h"

#define SERV_ADDR "serv.socket"
#define CLIE_ADDR "clie.socket"

int main(void)
{
    
    
    int  cfd, len;
    struct sockaddr_un servaddr, cliaddr;
    char buf[4096];

    cfd = Socket(AF_UNIX, SOCK_STREAM, 0);

    bzero(&cliaddr, sizeof(cliaddr));
    cliaddr.sun_family = AF_UNIX;
    strcpy(cliaddr.sun_path,CLIE_ADDR);

    len = offsetof(struct sockaddr_un, sun_path) + strlen(cliaddr.sun_path);     /* 计算客户端地址结构有效长度 */

    unlink(CLIE_ADDR);
    Bind(cfd, (struct sockaddr *)&cliaddr, len);                                 /* 客户端也需要bind, 不能依赖自动绑定*/

    
    bzero(&servaddr, sizeof(servaddr));                                          /* 构造server 地址 */
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path,SERV_ADDR);

    len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);   /* 计算服务器端地址结构有效长度 */

    Connect(cfd, (struct sockaddr *)&servaddr, len);

    while (fgets(buf, sizeof(buf), stdin) != NULL) {
    
    
        write(cfd, buf, strlen(buf));
        len = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
    }

    close(cfd);

    return 0;
}

3.2 C/S data model (SOCK_DGRAM)

  Because the Unix Domain Socket is used for IPC, it is enough to know the SOCK_STREAM type, and the code of the example will not be written here.

Guess you like

Origin blog.csdn.net/Xiaoma_Pedro/article/details/130875599