Linux network development tutorial 13_ Extended usage of data transceiver

Both write() and send() can send and receive data. What's the difference?

  • send can use flags to specify optional information, where 0 indicates the default sending behavior
  • send when the flags are 0, it will wait for the data in the transmission buffer to be cleared before putting the data into the transmission buffer and then returning
  • write cannot specify optional information and will not block

Both read() and recv() can receive data. What's the difference?

  • recv can use flags to specify optional information, where 0 indicates the default receiving behavior
  • recv when the flags are 0, it will wait for the data in the receiving buffer before taking the data out of the receiving buffer and returning
  • read cannot specify optional information and will not block

Data transceiver options

#include <sys/socket.h>

ssize_t send(int socketfd, const void *buf, size_t nbytes, int flags);
ssize_t recv(int socketfd, void *buf, size_t nbytes, int flags);

flags - specify optional information when sending and receiving data, where 0 is the default sending and receiving behavior

flags option information (part)

Optionalmeaningsendrecv
MSG_OOBIt is used to transmit Out Of Band Data, i.e. emergency data (priority transmission)
MSG_PEEKVerify whether there is data in the receive buffer (what data is there)
MSG_DONTROUTEThe data transmission process does not go through the routing table to find the destination in the local LAN
MSG_DONTWAITNon blocking mode, return immediately when sending and receiving data
MSG_WAITALLDo not return in advance before receiving all the requested data
MSG_MOREMore data needs to be sent, indicating that the kernel is waiting for data
......

Note: different operating systems support the above options differently. During actual project development, it is necessary to investigate the options supported in the target system in advance

MSG_OOB (out of band data, emergency data)

Native definition

  • Data transmitted independently using channels different from ordinary data
  • The priority of out of band data is higher than that of ordinary data (priority transmission, priority reception at the opposite end)

Out of band data in TCP

  • Due to the limitation of native design, TCP cannot provide out of band data in the real sense
  • In TCP, emergency data can only be transmitted through the mark in the transmission protocol message header, and the length is only 1 byte

Implementation principle of TCP out of band data

The URG pointer points to the next position of the emergency message, that is, the previous byte of the position pointed by the URG pointer stores the emergency message

The receiving end receives emergency data first, stores it in a special buffer, and then receives ordinary data

Emergency data: 0 x03
 General data: 0 x01,0x02

TCP out of band data processing strategy

  • Because TCP is designed as streaming data, it is impossible to achieve real out of band data
  • The marked emergency data can be received in advance and enter the special buffer (only one byte)

    • Each TCP packet has at most one emergency data
    • The special buffer only stores the latest emergency data (it will be lost if it is not received in time)

What happens when you send and receive data in the following way

Send ordinary data and receive in ordinary way: normal, data arrive in order
Send ordinary data and receive in emergency mode: error return
Send emergency data and receive it in normal mode: normal data is blocked when recv
Send emergency data and receive in emergency mode: normal, receive emergency data

Programming experiment: sending and receiving TCP emergency data

client.c

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

int main()
{
    int sock = {0};
    struct sockaddr_in addr = {0};
    int len = 0;
    char *test = "Delpin-Tang";

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if (sock == -1) {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        printf("connect error\n");
        return -1;
    }

    printf("connect success\n");

    len = send(sock, test, strlen(test), MSG_OOB);

    getchar();

    close(sock);

    return 0;
}

server.c

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

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if (server == -1) {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
        printf("server bind error\n");
        return -1;
    }

    if (listen(server, 1) == -1) {
        printf("server listen error\n");
        return -1;
    }

    printf("server start success\n");

    while (1) {
        asize = sizeof(caddr);

        client = accept(server, (struct sockaddr*)&caddr, &asize);

        if (client == -1) {
            printf("client accept error");
            return -1;
        }

        printf("client: %d\n", client);

        do {
            r = recv(client, buf, sizeof(buf), MSG_OOB);

            if (r > 0) {
                buf[r] = 0;
                printf("OOB: %s\n", buf);
            }

            r = recv(client, buf, sizeof(buf), 0);

            if (r > 0) {
                buf[r] = 0;
                printf("NORMAL: %s\n", buf);
            }
        }while (r > 0);

        close(client);
    }

    close(server);

    return 0;
}

Output:

server start success
client: 4
NORMAL: Delpin-Tan   // Note that normal data is output first (because when flags are MSG_OOB, it will not block, but when flags are 0, it will block until data is received)
OOB: g               // Note that only the last character is output!!

Small question: how to efficiently receive TCP emergency data in actual development?

Use select to receive emergency data

When ordinary data and emergency data are received on the socket, the select will be returned immediately

  • Ordinary data: socket is in data readable state (ordinary data can be read)
  • Emergency data: socket handles abnormal state (emergency data can be read)

Example of emergency data reception

num = select(max + 1, &temp, 0, &except, &timeout);

if (num > 0) {
    for (i=1; i<=max; ++i) {
        if (FD_ISSET(i, &except)) {
            if (i != server) {
                char buf[32] = {0};
                int r = recv(i, buf, sizeof(buf), MSG_OOB);
                if (r > 0) {
                    buf[r] = 0;
                    printf("OOB: %s\n", buf);
                }
            }
        }

        if (FD_ISSET(I, &temp)) {
            // ...
        }
    }
}

Programming experiment: use select to receive emergency data

client.c

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

int main()
{
    int sock = {0};
    struct sockaddr_in addr = {0};
    int len = 0;
    char *test = "Delpin-Tang";

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if (sock == -1) {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        printf("connect error\n");
        return -1;
    }

    printf("connect success\n");

    len = send(sock, test, strlen(test), MSG_OOB);

    getchar();

    close(sock);

    return 0;
}

select-server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int server_handler(int server) 
{
    struct sockaddr_in addr = {0};
    socklen_t asize = sizeof(addr);
    return accept(server, (struct sockaddr*)&addr, &asize);
}

int client_handler(int client) 
{
    char buf[32] = {0};

    int ret = recv(client, buf, sizeof(buf) - 1, 0);

    if (ret > 0) {
        buf[ret] = 0;

        printf("Recv: %s\n", buf);
    }

    return ret;
}

int clint_except_handler(int client) 
{
    char buf[2] = {0};
    int r = recv(client, buf, sizeof(buf), MSG_OOB);

    if (r > 0) {
        buf[r] = 0;
        printf("OOB: %s\n", buf);
    }

    return r;
}

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int max = 0;
    int num = 0;
    fd_set reads = {0};
    fd_set temps = {0};
    fd_set except = {0};
    struct timeval timeout = {0};

    server = socket(PF_INET, SOCK_STREAM, 0);

    if (server == -1) {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
        printf("server bind error\n");
        return -1;
    }

    if (listen(server, 1) == -1) {
        printf("server listeb error\n");
        return -1;
    }

    printf("server start success\n");

    FD_ZERO(&reads);
    FD_SET(server, &reads);

    max = server;

    while (1) {
        temps = reads;
        except = reads;

        timeout.tv_sec = 0;
        timeout.tv_usec = 10000;

        num = select(max + 1, &temps, 0, &except, &timeout);

        if (num > 0) {
            int i = 0;

            for (i=0; i<=max; ++i) {
                if (FD_ISSET(i, &except)) {
                    if (i != server) {
                        clint_except_handler(i);
                    }
                }

                if (FD_ISSET(i, &temps)) {
                    if (i == server) {
                        int client = server_handler(server);

                        if (client > -1) {
                            FD_SET(client, &reads);
                            max = (client > max) ? client : max;
                            printf("accept client: %d\n", client);
                        }
                    } else {
                        int r = client_handler(i);

                        if (r == -1) {
                            FD_CLR(i, &reads);
                            close(i);
                        }
                    }
                }
            }

            int client = server_handler(server);
        }
    }
}

Output:

server start success
accept client: 4
OOB: g
Recv: Delpin-Tan

Summary

  • read() / write() can be used to send and receive ordinary data (without extension function)
  • send() / recv() can expand more functions through option information
  • TCP emergency data can identify 256 kinds of emergency events (abnormal events)
  • Through select, emergency data can be processed in time and ordinary data can be distinguished

Tags: C Linux socket

Posted by php_blob on Sat, 07 May 2022 05:21:29 +0300