[Linux Development—Network Programming]

  • Network programming, writing programs to exchange data between two or more networked computers.

1. Network—Introduction

ctontrol + C: force the process to end

1. Network-physical structure

1. Ordinary network

Insert image description here

2. Fiber optic Gigabit network

  • There are many Gigabit networks on the market, but the maximum throughput of ordinary network cables is only 100 Mbits. If you want to reach Gigabit, you need to replace the Gigabit fiber optic network cable, and you also need a Gigabit fiber optic adapter.
  • The Gigabit network cable is flat and has a small interface similar to USB.
    Insert image description here
    Insert image description here
    Insert image description here

2. Network—IP address

并不是所有的地址都是可以用的

  • 1. The network address is composed of 4 bytes and 32 bits.

  • 2. Addresses starting with 0 cannot be used.

  • 3. Addresses ending in 0 represent network segments, not specific addresses.

  • 4. Addresses starting from 224 to 239 are 组播addresses and cannot be used for point-to-point transmission.

    • Multicast: understands broadcast over TCP/UDP
    • Advantages: Multicast can greatly save bandwidth
    • Disadvantages: It is easy to form a network storm
  • 5. Addresses starting from 240 to 255 are reserved for experiments and are generally not used as server or terminal addresses.

    • 127.0.0.1 is a reserved loopback network address. Any data sent to this network will be sent back. It is generally used for learning and testing.
    • 0.0.0.0 is reserved, the entire network segment address, generally used for server monitoring
  • Category A (large network), B (medium network), and C (small network) are used for networks of different sizes.

  • Class D is used for multicast (similar to broadcast)

  • Class E reserved for experimental, private networks
    Insert image description here

3. Network-port

The port is not only used by the server, but also by the client.

  • Well Known Ports : Also known as commonly used ports, from 0 to 1024, tightly bound to some specific services. Usually the communication of these ports clearly indicates the protocol of a certain service. This kind of port cannot redefine its role.netstat -an
    Insert image description here

    • 443: https service
    • 80: http communication
    • 23: Telnet service
  • Registered Ports : Port numbers range from 1025 to 49151. Most of these ports do not clearly define service objects. Different programs can be customized according to actual needs. Remote control software and Trojan programs will be defined by these ports.

  • Dynamic and/or Private Ports : Port numbers range from 49152 to 65535 (do not easily use it as the server’s listening port). In particular, some Trojan programs like to use these ports. These ports are often unnoticed and easy to hide.

4. Network-Protocol

The agreement is, 一种网络交互中数据格式和交互流程的约定. Through the protocol, we can interact with remote devices for data, request services or complete the other party's services.

1. TCP protocol header structure:

48 bytes
Insert image description here

2. The header structure of the HTTP protocol:

Text stream
GET request, version 1.1, Host: send data to the website; UA: client browser information;
Accept: receive information, format, language type
Insert image description here

3. SSH packet header structure

6 bytes, byte stream
Insert image description here

5. TCP protocol

Transmission Control Protocol (TCP), a connection-oriented, reliable, byte stream (in bytes) transport layer communication protocol. Safe and reliable but sacrifices performance.

  • Interaction process:
    Insert image description here

    • A timeout is required to resolve physical connection interruptions, and the default timeout may be as long as two hours.
    • Solution to timeout: heartbeat packet mechanism. Forced to ask whether both parties are online, if the abnormality continues for more than a certain number of times, and generally if 3 heartbeats cannot be received or sent, the connection is forced to be disconnected.

2. Sockets—Introduction

  • Similar to the network part of Windows network programming : Windows network programming
  • Socket: A software device used for network data transmission. The operating system provides us with socket support.

1. Socket understanding

Insert image description here

2. Socket creation

There are many types of sockets, the most commonly used are TCP and UDP sockets.

#include <sys/socket.h>

/* socket创建
- domain:使用的协议族(Protocol Family)信息
- type:数据传输类型信息
- protocol:计算机之间通信种使用的协议信息
*/
int socket(int domain, int type, int protocol);

3. socket function

  • Connection-oriented socket TCP : It is a reliable, in-order delivery, byte-based connection-oriented data transmission method.
  • Message-oriented socket UDP : unreliable, out-of-order delivery, and designed for high-speed data transmission.

Insert image description here
Insert image description here
Insert image description hereInsert image description here

4. bind function

#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
/* socket绑定
- sockfd:socket套接字文件描述符
- myaddr:结构体,存放地址信息的结构体变量地址值,IPv4,IPV6
- addrlen:
*/
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);//绑定成功返回0,失败返回-1

ntohs can be understood as: converting short data from network byte order to host byte order.

struct sockaddr_in addr;
char *serv_ip = "221.214.168.13";//声明IP地址字符串
char *serv_port = "9190";//声明端口号
memset(&add, e,sizeof(addr));//结构体变量addr的所有成员初始化为0

addr.sin_fimily = AF_INET;//指定地址族
/*
每次创建服务器端套接字都要输入IP地址,会很繁琐,此时可初始化地址信息为INADDRANY。
addr.sin_addr.s_addr = htonl(INADDRANY);
*/
add.sin_addr.s_addr = inet_addr(serv_ip);//基于字符串的IP地址初始化
add.sin_port=htons(atoi(serv_port));//基于字符串的端口号初始化

5. listen function

#include <sys/socket.h>

/*
- sock:套接字文件描述符
- backlog:连接请求等待队列的长度,若为5,则队列长度为5,最多使5个连接请求进入队列
*/
int listen(int sock, int backlog);//成功返回0,失败返回-1.

4. accept function

#include <sys/socket.h>

/*
- sock:套接字文件描述符
- addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后传递来的地址变量参数天才客户端地址信息
- addrlen: addr结构体参数的长度。
*/
int accept(int sock, struct sockaddr*addr, socklen_t *addrlen);//成功返回0,失败返回-1.

3. TCP Programming—Echo Server

  • Echo server : Returns the data received from the client to the client as it is, that is, "echo"

1. TCP/IP protocol stack

Sockets based on the TCP/IP network protocol are divided into: TCP and UDP sockets.

2. TCP server:

void server_func()
{
    
    
    printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);

    int server_sock, client_sock;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    socklen_t client_addr_size;

    //此处省略了一些,socket的配置认证,eg:版本号等
    server_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (server_sock < 0) {
    
    
        //error_handling("create socket error!");
        std::cout << "create socket error!\n";
        return;
    }

    memset(&server_addr, 0 , sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");// htonl(INADDR_ANY);  0.0.0.0 表示监听全网段,包括内外网
    server_addr.sin_port = htons(9444);

    int ret = bind(server_sock, (struct  sockaddr*)&server_addr, sizeof(server_addr));

    if (ret == -1) {
    
    
        //error_handling("bind socket error!");
        std::cout << "server bind socket error!\n";

        close(server_sock);
        return;
    };
    client_addr_size = sizeof(client_addr);
    ret = listen(server_sock, 3);
    if (ret == -1) {
    
    
        //error_handling("listen socket error!");
        std::cout << "server listen socket error!\n";

        close(server_sock);
        return;
    };

    //回声服务器-原理:服务端循环接收客户端的消息
    char buffer[1024];
    while (1)
    {
    
    
        memset(buffer, 0, sizeof(buffer));

        client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
        if (client_sock == -1) {
    
    
            //error_handling("accept server socket error!");
            std::cout << "server accept socket error!\n";

            close(server_sock);
            return;
        }

        ssize_t len = 0;
        while ((len = read(client_sock, buffer, sizeof(buffer))) > 0)
        {
    
    
            len = write(client_sock, buffer, strlen(buffer));
            if (len != (ssize_t)strlen(buffer)) {
    
    
                //error_handling("write message failed!");
                std::cout << "server write message failed!\n";

                close(server_sock);
                return;
            }

            std::cout << "server read & write success!, buffer:" << buffer <<"__len:"<< len << std::endl;

            memset(buffer, 0, len);//清理
        }

        close(client_sock);//服务端关闭的时候,客户端会自动关闭
    };

  
    close(server_sock);
}

3. TCP client:

void client_func()
{
    
    
    int client = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(9444);
    int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if (ret == -1) {
    
    
        std::cout << "client connect failed!\n";
        close(client);
        return;
    }
    std::cout << "client connect server is success!\n";

    char buffer[256] = "";
    while (1)
    {
    
    
        fputs("Input message(Q to quit):", stdout);//提示语句,输入Q结束
        fgets(buffer, sizeof(buffer), stdin);//对文件的标准输入流操作 读取buffer的256字节
        if (strcmp(buffer, "q\n") == 0 || (strcmp(buffer, "Q\n") == 0)) {
    
    
            break;
        }

        size_t len = strlen(buffer);
        size_t send_len = 0;

        //当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
        while (send_len < len)
        {
    
    
            ssize_t ret = write(client, buffer + send_len, len - send_len);//send_len 记录分包的标记
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed,make client write failed!\n", stdout);
                close(client);
                return;
            }
            send_len += (size_t)ret;

            std::cout << "client write success, msg:" << buffer << std::endl;

        }
        memset(buffer, 0, sizeof(buffer));

        //当数据量很大时,并不能一次把所有数据全部读取完,因此需要分包读取
        size_t read_len = 0;
        while (read_len < len)
        {
    
    
            size_t ret = read(client, buffer + read_len, len - read_len);
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed, make client read failed!\n", stdout);
                close(client);
                return;
            }
            read_len += (size_t)ret;
        }
        std::cout << "from server:" << buffer << std::endl;
    };
    
    close(client);
    std::cout << "client done!" << std::endl;
}

4. Interaction between different processes between the server and the client

#include <sys/wait.h>
void server_client_connect()
{
    
    
    //创建进程
    pid_t pid = fork();
    if (pid == 0) {
    
    //为0时表示在子进程
        sleep(1);//为了让服务端先执行

        client_func();
        
    }
    else if (pid > 0) {
    
    //主进程为服务端进程
        server_func();

        int status = 0;
        wait(&status);
    }
    else
    {
    
    
        std::cout << "fork failed!" << pid << std::endl;
    }

}

5. Echo Server Practical Project (Network Calculator)—Cloud Computing

  • If you need the source code, please leave a message.
  • Project requirements :
/*
* 云计算器-需求:
1.客户端连接到服务器端后以1字节整数形式传递待算数字个数。      (个数是1字节)
2.客户端向服务器端传递的每个整数型数据占用4字节。             (每个数是4字节)
3.传递整数型数据后接着传递运算符。运算符信息占用1字节。       (运算符信息1字节)
4.选择字符+、-、*之一传递。
5. 服务器端以4字节整数型向客户端传回运算结果。
6. 客户端得到运算结果后终止与服务器端的连接。
*/
  • Final output:
    Insert image description here

4. The underlying principles of TCP

1. TCP socket I/O buffering

  • We know that TCP socket data sending and receiving has no boundaries . Even if the server calls the write function once to transfer 40 bytes of data, the client may read 10 bytes each time through four read function calls. But there are also some doubts here. The server transmits 40 bytes at once, but the client can receive it slowly in batches. After the client receives 10 bytes, where are the remaining 30 bytes waiting? Is it like an airplane hovering in the sky waiting to land, with the remaining 30 bytes also wandering in the network waiting to be received?

  • In fact, the data is not transmitted immediately after the write function is called, and the data is not received immediately after the read function is called . More precisely, as shown in the figure below, the moment the write function is called, the data will be moved to the output buffer ; the moment the read function is called, the data is read from the input buffer .
    Insert image description here

    • 1. From sending data to receiving, there are physical cables and input and output buffers in between, so it will be slower.
    • 2. When the server sends data, it will first check back with the client to see if there is a buffer (3-way handshake protocol). If there is, it can continue to send.
  • When the write function is called, the data will be moved to the output buffer and transferred to the other party's input buffer at the appropriate time (whether transmitted separately or all at once). At this time, the other party will call the read function to read data from the input buffer. These I/O buffering characteristics can be summarized as follows.
    A: I/O buffering exists individually within each TCP socket.
    B: I/O buffering is automatically generated when the socket is created.
    C: Data left in the output buffer will continue to be delivered even if the socket is closed .
    D: Closing the socket will lose the data in the input buffer .
    So, what will happen in the following situation? After understanding the I/O buffering, the process:
    "The client input buffer is 50 bytes, and the server transmits 100 bytes."
    This is indeed a problem. The input buffer is only 50 bytes, but 100 bytes of data were received. The following solution can be proposed:
    quickly call the read function to read the data before filling up the input buffer. This will free up some space and the problem will be solved .

  • In fact, such problems will not occur at all, because TCP will control the data flow.
    There is a sliding window (Sliding Window) protocol in TCP, which is presented as follows in conversational mode.
    Socket A: "Hello, you can send me up to 50 bytes."
    Socket B: "OK!"
    Socket A: "I have freed up 20 bytes of space, and you can receive up to 70 bytes."
    .Socket B: "OK!" The same is true for data sending and receiving, so data will not be lost due to buffer overflow in TCP. However, the transmission efficiency will be affected due to buffering .

2. Internal principles of TCP

  • Three major steps in TCP communication:
    • 1. Three-way handshake to establish connection;
    • 2. Start communication and exchange data;
    • 3. Wave four times to disconnect;

1. Three-way handshake

(Client initiates - server responds)

  • [First handshake] Socket A: "Hello, socket B. I have data to send to you, please establish a connection."
    [Second handshake] Socket B: "Okay, I This side is ready."
    [Third handshake] Socket A: "Thank you for accepting my request."

Insert image description here
First , host A requesting a connection transmits the following information to host B:
[SYN] SEQ:1000, ACK: -The
SEQ in this message is 1000, the ACK is empty, and the meaning of SEQ 1000 is as follows:
"The serial number of the data packet being transmitted now is 1000. If the reception is correct, please notify me to deliver data packet No. 1001 to you." This is the message used when requesting a connection for the first time, also known as SYN. SYN is the abbreviation of Synchronization, which represents the synchronization message transmitted before sending and receiving data .

Next, host B transmits the following message to A:
[SYN+ACK]SEQ:2000, ACK:1001.
At this time, SEQ is 2000, ACK is 1001, and the meaning of SEQ 2000 is as follows:
" The serial number of the data packet being transmitted is 2000. If Received correctly, please notify me to deliver data packet No. 2001 to you ."

The meaning of ACK1001 is as follows:
" The data packet with SEQ 1000 that was just transmitted was received correctly. Now please pass the data packet with SEQ 1001. "

The acknowledgment message (ACK1001) for the data packet transmitted for the first time by host A and the synchronization message (SEQ2000) preparing for the data transmission by host B are sent in a bundle. Therefore, this type of message is also called SYN+ACK.

Before sending and receiving data, assign a sequence number to the data packet and notify the other party of the sequence number. These are preparations to prevent data loss . By assigning sequence numbers to packets and acknowledging them, lost packets can be immediately viewed and retransmitted in the event of data loss. Therefore, TCP can guarantee reliable data transmission. Finally, observe the message transmitted by host A to host B:
[ACK]SEQ:1001, ACK:2001

Sequence numbers need to be assigned when sending data packets during a TCP connection.
Add 1 to the previous serial number 1000, that is, assign 1001. At this time, the data packet delivers the following message:
"The transmitted data packet with SEQ 2000 has been correctly received, and the data packet with SEQ 2001 can now be transmitted."

This transmits the ACK message with ACK2001 added. At this point, Host A and Host B have confirmed that each other is ready.

  • Damn, the text is too complicated, let me give you an easy-to-understand explanation.
    The TCP three-way handshake is like walking alone in the community on a dark and windy night. Not far away, you see a beautiful girl in the community coming towards you. However, you cannot be 100% sure because the street lights are a bit dim, so you have to Use waving to determine if the other person recognizes you.
    You first wave to the girl (syn), and after the girl sees you waving to her, she nods to you and squeezes out a smile (ack). After you see the girl smile, you confirm that the girl has successfully identified you (entering the established state).
    But the girl was a little embarrassed and looked around to see if you were looking at someone else. She also needed to confirm. The girl also waved to you (syn). When you saw the girl waving to you, you knew that the other party was seeking your confirmation, so you nodded and smiled (ack). The girl confirmed after seeing the other party's smile. You are greeting yourself (entering the established state).

Attack using TCP three-way handshake :
The client keeps doing only the first handshake, so the server keeps creating connections and assigning sequence numbers based on this handshake, but these created connections require a timeout of about two hours, so It takes up more server resources, and excessive data volume blocks the server.

2. Data transmission

  • Pay attention to data transmission: retransmission and deduplication. The network kernel module of the operating system has already taken care of these two tasks for us.
    Insert image description here
  • TCP data transmission means that two people communicate across the air. There is a certain distance, and the other party needs to repeatedly confirm that they heard what they said.
    You shout a sentence (seq), and after the girl hears it, she will reply to you that she heard it (ack).
    If you shout and don't hear a reply from the girl for a long time, you will feel very depressed. Just like when you are in love, you are full of enthusiasm, but the girl is hot and cold, so you persevere. If it doesn't work once, just do it twice. If it doesn't work twice, just do it twice. Three times, this is tcp retransmission. Since it will be retransmitted, the girl may have heard the same sentence twice. This is the elimination of duplication.
    The network kernel module of the operating system has already taken care of the two tasks of retransmission and deduplication for us.
    After it's over, your sister and you will reluctantly separate.
    Then we have to break up.
    That is to say, we have come to the four waves of TCP communication.

3. Wave four times

Insert image description here

5. UDP programming

UDP is the main form of DDOS attack (network distributed denial of service attack) . The sender address can be forged, and the data cannot be rejected. Data packets need to be received, but because the sender address is forged, the sender cannot be found and the sender address cannot be rejected. Member people

1. Basic principles of UDP

We can illustrate how UDP works through letters . This is the traditional example used when explaining UDP, and it is fully consistent with the characteristics of UDP. Before sending a letter, you should fill in the sender's and recipient's addresses on the envelope, then affix a stamp and put it in the mailbox. Of course, the nature of the letter makes it impossible to confirm whether the other party has received it. In addition, letters may be lost during the mailing process. That said, letters are an unreliable method of transmission. Similarly, UDP also provides unreliable data transmission services.
"In this case, TCP should be a better protocol, right?"

If you only consider reliability, TCP is indeed better than UDP . But UDP is structurally simpler than TCP . UDP does not send a response message like ACK, nor does it assign sequence numbers to data packets like SEQ. Therefore, the performance of UDP is sometimes much higher than that of TCP. Implementing UDP in programming is also simpler than TCP. In addition, although UDP is not as reliable as TCP, data corruption does not occur as frequently as imagined. Therefore, UDP is a good choice when performance is more important than reliability.

In this case, what is the role of UDP? In order to provide reliable data transmission services, TCP performs flow control at the unreliable IP layer, and UDP lacks this flow control mechanism.
Flow control is the most important symbol that distinguishes UDP and TCP . But if you remove flow control from TCP, there is only a handful of content left. In other words, the life of TCP lies in flow control .

If TCP is compared to a phone call, UDP is compared to a letter. But this only describes how the protocol works and does not include the data exchange rate. Please do not mistakenly think that "phone calls are faster than letters, so TCP's data sending and receiving rate is also faster than UDP." Quite the opposite actually. TCP cannot be faster than UDP, but it may be close to UDP when sending and receiving certain types of data. For example, the larger the amount of data exchanged each time, the closer the TCP transmission rate is to the UDP transmission rate.

TCP is used in combination with UDP: TCP is used for flow control and UDP is used for data transmission.

Due to the disordered nature of UDP packet data transmission, due to the status change of the router, packets may be sent first and arrive later.

Insert image description here

  • As can be seen from the above figure, the role of IP is to accurately deliver the UDP data packet leaving host B to host A. But the process of finally delivering the UDP packet to a certain UDP socket of host A is completed by UDP. The most important role of UDP is to deliver the data packets transmitted to the host to the final UDP socket according to the port number . In fact, in actual application scenarios, UDP also has a certain degree of reliability. Network transmission characteristics lead to frequent information loss, but if you want to transmit compressed files (when sending 10,000 data packets, only one loss will cause problems), you must use TCP, because as long as a part of the compressed file is lost, it is difficult to decompress it. But the situation is different when transmitting video or audio in real time over a network. For multimedia data, it is not a big problem to lose part of it, which will only cause short-term picture jitter or subtle noise. However, because real-time services need to be provided, speed becomes a very important factor, and UDP needs to be considered at this time. But UDP is not always faster than TCP. The reasons why TCP is slower than UDP are usually due to the following two points.
    • 1 The connection setting and clearing process before and after sending and receiving data.
    • 2. Flow control added to ensure reliability during the process of sending and receiving data.
      Especially when the amount of data sent and received is small but frequent connections are required, UDP is more efficient than TCP.

2. UDP socket

  • The difference between UDP and TCP:

    • 1. UDP does not have listen and accept
    • 2. UDP sends and receives data on a socket, which is distinguished according to the sender address and recipient address of from and recvfrom.
  • The server and client in UDP are not connected.
    UDP server/client does not exchange data in the connected state like TCP, so unlike TCP, there is no need to go through the connection process. In other words, there is no need to call the listen function and accept function called during the TCP connection process. There are only the process of creating a socket and the process of data exchange in UDP.

  • Both the UDP server and the client only need 1 socket
    in TCP, and there should be a one-to-one relationship between the sockets. To serve 10 clients, you need 10 server-side sockets in addition to the gatekeeper's server socket. But in UDP, both the server and the client only need 1 socket . When I explained the principle of UDP before, I gave the example of a letter. The mailbox used when sending and receiving letters can be compared to a UDP socket. As long as there is a mailbox nearby, you can send letters to any address through it. Likewise, only 1 UDP socket is needed to transmit data to any host

Insert image description here

  • The above figure shows the process of exchanging data between a UDP socket and two different hosts. In other words, only one UDP socket can communicate with multiple hosts .
  • After the TCP socket is created, there is no need to add address information when transmitting data. Because the TCP socket will remain connected to the other party's socket. In other words, the TCP socket knows the destination address information. However, UDP sockets do not maintain the connection state (UDP sockets only have a simple mailbox function), so target address information must be added every time data is transmitted. This is equivalent to filling in the address on the letter before sending it . The following are: UDP-related functions called when filling in the address and transmitting data.

1. sendto() function—sender

#include<sys/socket.h>
→成功时返回传输的字节数,失败时返回-1。
ssize_t sendto(int sock,void*buff,size_t nbytes,int flags,struct sockaddr *to, socklen_t addrlen);

● sock
is the UDP socket file descriptor used to transmit data.
● buff
saves the buffer address value of the data to be transmitted.
● nbytes
The length of data to be transmitted, in bytes.
● flags
optional parameter, if not available, pass 0.

The address value of the sockaddr structure variable that stores the target address information.
● addrlen
is the length of the address value structure variable passed to parameter to.

The biggest difference between the sendto function and the previous TCP output function is that this function needs to pass the target address information to it . Next, we introduce the function for receiving UDP data. The sender of UDP data is not fixed , so this function is defined in a form that can receive the sender information, that is, the sender information in the UDP data packet will be returned at the same time .

2. recvfrom() function—recipient

#include<sys/socket.h>
//→成功时返回接收的字节数,失败时返回-1。
ssize_t recvfrom(int sock,  void *buff,size_t nbytes,  int flags,
struct sockaddr*from,   socklen_t*addrlen);

●sock is the UDP socket file descriptor used to receive data.
●buff saves the cache address value of received data
●nbytes The maximum number of bytes that can be received, so it cannot exceed the buffer size pointed by parameter buf.
●flags optional parameter, if not available, pass in 0.
●fromThe address value of the sockaddr structure variable that stores the sender's address information.
●addrlen holds the variable address value of the structure variable length of the parameter from.
The core part when writing a UDP program lies in the above two functions, which also illustrates their status in UDP data transmission.

3. UDP programming—echo server

1. Server

  • 1. Create socket
  • 2. Configure the address family (ip, port)
  • 3. Bind bind
  • 4. Receive data recvfrom and send data sendto
  • 5. Close the socket

2. Client

  • 1. Create socket
  • 2. Configure the address family (ip, port)
  • 3. Send data sendto and receive data recvfrom
  • 4. Close the socket

3. Core source code

//UDP回声服务器
#define BUF_SIZE 30 //buffer字节大小

//需要控制台 输入端口
void udp_server(int argc, char* argv[])
{
    
    
    if (argc != 2) {
    
    //校验端口参数,
        printf("Usage : %s <port>\n", argv[0]);
        error_handling("argement is error: 端口");
    }

    int serv_sock;
    char message[BUF_SIZE];
    socklen_t clnt_adr_sz;
    struct sockaddr_in serv_adr, clnt_adr;
    
    //创建socket
    serv_sock = socket(PF_INET, SOCK_DGRAM, 0);//UDP的socket SOCK_DGRAM: 报文类型
    if (serv_sock == -1)
        error_handling("UDP socket creation error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    //配置socket的地址族
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY->0.0.0.0 htonl:主机字节序 转网络字节序 l:long型 4或8字节
    serv_adr.sin_port = htons((uint16_t)atoi(argv[1]));//需要输入端口 htons:主机字节序 转网络字节序 s:short型,2字节

    //绑定
    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error");
        
    //收发数据
    ssize_t str_len;
    while (1)
    {
    
    
        clnt_adr_sz = sizeof(clnt_adr);
        str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);//利用分配的地址接收数据。不限制数据传输对象
        sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);//函数调用同时获取数据传输端的地址。正是利用该地址将接收的数据逆向重传。
    }
    close(serv_sock);
}

void udp_client(int argc, char* argv[])
{
    
    
    if (argc != 3) {
    
    
        printf("Usage : %s <IP> <port>\n", argv[0]);
        error_handling("argement is error: IP");
    }

    int sock;
    char message[BUF_SIZE];
    socklen_t adr_sz;

    struct sockaddr_in serv_adr, from_adr;
    
    //创建socket
    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));//清理,防止默认值

    //配置socket的地址族
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);//控制台输入ip地址,eg:127.0.0.1
    serv_adr.sin_port = htons((uint16_t)atoi(argv[2]));//控制台输入端口

    //收发数据
    ssize_t str_len;
    while (1)
    {
    
    
        fputs("Insert message(q to quit): ", stdout);
        fgets(message, sizeof(message), stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))//字符串比较
            break;

        ssize_t len = sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
        memset(message, 0, len);
        adr_sz = sizeof(from_adr);
        str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz);

        printf("Message from server: %s", message);
        message[str_len] = 0;
    }
    close(sock);
}


//主进程开启客户端,再开启服务端,是为了在主进程调试方便
void udp_server_client_connect(int argc, char* argv[])
{
    
    

    char* argv0 = "";
    pid_t pid = fork();
    if (pid < 0)
        std::cout << "fork failed!" << pid << std::endl;

    if (pid == 0)//开启新进程,子进程(服务端进程)
    {
    
    

        int argc = 2;
        char* argv[] = {
    
     argv0, (char*)"9555" };
        udp_server(argc, argv);
        
    }
    else//pid > 0 , 父进程(客户端进程) 
    {
    
    
        int argc = 3;
        char* argv[] = {
    
     argv0, (char*)"127.0.0.1", (char*)"9555" };
        udp_client(argc, argv);

        int status = 0;
        wait(&status);
    }

}

3. UDP transmission characteristics and calls

  • The implementation method of UDP server/client was explained earlier. But if you look closely at the UDP client, you will find that it lacks the process of assigning IPs and ports to sockets.
  • The TCP client calls the connect function to automatically complete this process, while UDP does not even have a function call statement that can assume the same function. When exactly are IP and port numbers assigned?
  • In a UDP program, the socket address allocation should be completed before calling the sendto function to transmit data , so the bind function is called.
  • Of course, the bind function has appeared in TCP programs, but the bind function does not distinguish between TCP and UDP . That is to say, it can also be called in UDP programs.
  • In addition, if it is found that the address information has not been assigned when calling the sendto function, the IP and port will be automatically assigned to the corresponding socket when the sendto function is called for the first time.
  • Moreover, the address assigned at this time is retained until the end of the program (before closing the socket) , so it can also be used to exchange data with other UDP sockets.
  • Of course, use the host IP as the IP and select any unused port number as the port number.

In summary, the IP and port number are automatically assigned when the sendto function is called, so there is usually no need for additional address allocation processes in the UDP client .

6. Various options for sockets

1. I/O buffer size

1. Understanding:

Insert image description here

2,getsockopt & setsockopt 函数

We can read (Get) and set (Set) almost all the options in the table above (of course, some options can only perform one operation). The reading and setting of options are completed through the following two functions.

#include<sys/socket.h>
//→成功时返回0,失败时返回-1。
int getsockopt(int sock, int level,int optname, void *optval, socklen_t *optlen);

●sock: used to view option socket file descriptors.
●levelThe optional protocol layer to be viewed.
●optname The name of the optional option to be viewed.
●optval saves the buffer address value of the viewing result.
●The buffer size passed by optlen to the fourth parameter optval. After calling the function, this variable stores the number of bytes of optional information returned through the fourth parameter.

#include<sys/socket.h>
//→成功时返回0,失败时返回-1。
int setsockopt(int sock, int level, int optname, const void*optval, socklen_t optlen);

●sock is used to change the optional socket file descriptor.
●levelThe optional protocol layer to be changed.
●optname The optional name to be changed.
●optval holds the buffer address value of the option information to be changed.
●optlen The number of bytes of optional information passed to the fourth parameter optval.

3,SO_SNDBUF &SO_RCVBUF

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
    
    
	int sock;
	int snd_buf=1024*3, rcv_buf=1024*3;
	int state;
	socklen_t len;
	
	sock=socket(PF_INET, SOCK_STREAM, 0);
	state=setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
	if(state)
		error_handling("setsockopt() error!");
	
	state=setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
	if(state)
		error_handling("setsockopt() error!");
	
	len=sizeof(snd_buf);
	state=getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
	if(state)
		error_handling("getsockopt() error!");
	
	len=sizeof(rcv_buf);
	state=getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
	if(state)
		error_handling("getsockopt() error!");
	
	printf("Input buffer size: %d \n", rcv_buf);
	printf("Output buffer size: %d \n", snd_buf);
	return 0;
}

void error_handling(char *message)
{
    
    
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

2,SO_REUSEADDR

  • Address allocation error (Binding Error) address reallocation occurred

The server is in a Time-wait waiting state due to a delay in getting a response.

  • The Time-wait solution is to change the status of SO_REUSEADDR in the socket options. By adjusting this parameter appropriately, the socket port number in the Time-wait state can be reassigned to a new socket . The default value of SO_REUSEADDR is 0 (false), which means that a socket port number in the Time-wait state cannot be allocated. Therefore you need to change this value to 1 (true).
	//解决Time-wait的问题
    getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
    printf("SO_REUSEADDR = %d\n", optval);
    //设置optval
    if (optval == 0)
        optval = 1;

    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, addrlen);
  • demo project:
//------------------------ Time-wait--------------------------
#define TRUE 1
#define FALSE 0

void tw_tcp_server()
{
    
    
    int sock, client, optval = 0;
    struct sockaddr_in addr, cliAddr;
    socklen_t addrlen = sizeof(addr);
    char buffer[256] = "";

    sock = socket(PF_INET, SOCK_STREAM, 0);

    //解决Time-wait的问题
    getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
    printf("SO_REUSEADDR = %d\n", optval);
    //设置optval
    if (optval == 0)
        optval = 1;

    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, addrlen);
    //查看新的optval
    getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
    printf("SO_REUSEADDR = %d\n", optval);

    memset(&addr, 0, addrlen);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(9526);

    addrlen = sizeof(addr);
    if (bind(sock, (struct sockaddr*) & addr, addrlen) == -1)
    {
    
    
        error_handling("tw_tcp_server bind failed");
    }

    listen(sock, 3);
    client = accept(sock, (struct sockaddr*)&cliAddr, &addrlen);
    read(client, buffer, sizeof(buffer));
    close(client);
    close(sock);
}

void tw_tcp_client()
{
    
    
    int client = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(9526);
    int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if (ret == -1) {
    
    
        std::cout << "client connect failed!\n";
        close(client);
        return;
    }
    std::cout << "client connect server is success!\n";

    char buffer[256] = "";
    while (ret == 0)
    {
    
    
        fputs("Input message(Q to quit):", stdout);//提示语句,输入Q结束
        fgets(buffer, sizeof(buffer), stdin);//对文件的标准输入流操作 读取buffer的256字节
        if (strcmp(buffer, "q\n") == 0 || (strcmp(buffer, "Q\n") == 0)) {
    
    
            break;
        }

        size_t len = strlen(buffer);
        size_t send_len = 0;

        //当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
        while (send_len < len)
        {
    
    
            ssize_t ret = write(client, buffer + send_len, len - send_len);//send_len 记录分包的标记
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed,make client write failed!\n", stdout);
                close(client);
                return;
            }
            send_len += (size_t)ret;

            std::cout << "client write success, msg:" << buffer << std::endl;

        }
        memset(buffer, 0, sizeof(buffer));

        //当数据量很大时,并不能一次把所有数据全部读取完,因此需要分包读取
        size_t read_len = 0;
        while (read_len < len)
        {
    
    
            size_t ret = read(client, buffer + read_len, len - read_len);
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed, make client read failed!\n", stdout);
                close(client);
                return;
            }
            read_len += (size_t)ret;
        }
        std::cout << "from server:" << buffer << std::endl;
    };

    close(client);
    std::cout << "client done!" << std::endl;
}

void tw_func(char* option)
{
    
    
    if (strcmp(option, "1") == 0)
    {
    
    
        tw_tcp_server();
        tw_tcp_server();
    }
    else {
    
    
        tw_tcp_client();
    }
}
  • Called in main function:
 tw_func(argv[1]);//Time-wait超时等待
  • operation result:
    Insert image description here

3,TCP_NODELAY

NODELAY: No delay.

Nagle algorithm

Nagle's algorithm, named after its inventor John Nagle, is used to automatically concatenate many small buffer messages ; this process (called nagling) increases the efficiency of network software systems by reducing the number of packets that must be sent. .
Insert image description here
The following conclusions can be drawn from the above figure:

  • "The Nagle algorithm only sends the next data when an ACK message for the previous data is received."
    TCP sockets use the Nagle algorithm by default to exchange data, so buffering is maximized until an ACK is received. This is exactly the case on the left side of the picture above. To send the string "Nagle", pass it to the output buffer. At this time there is no other data before the header character "N" (no ACK to receive), so it is transmitted immediately. Then it starts waiting for the ACK message of the character "N". During the waiting process, the remaining "agle" is filled in the output buffer. Next, after receiving the ACK message of character "N", the output buffered "agle" is loaded into a data packet and sent. In other words, a total of 4 data packets need to be passed to transmit 1 string.
  • Next, analyze the process of sending the string "Nagle" when the Nagle algorithm is not used. Assume that the characters "N" to "e" are passed to the output buffer in order. The sending process at this time has nothing to do with whether ACK is received or not, so the data will be sent out immediately after reaching the output buffer. As you can see from the right side of the figure above, a total of 10 packets are needed to send the string "Nagle". It can be seen that not using the Nagle algorithm will have a negative impact on network traffic. Even if only 1 byte of data is transmitted, the header information may be dozens of bytes. Therefore, in order to improve network transmission efficiency, the Nagle algorithm must be used.
  • When the string is passed to the output buffer in the program, it is not passed verbatim, so the actual situation of sending the string "Nagle" is not as shown in the figure above. But if the characters that make up the string are transferred to the output buffer after a period of time (if such data transfer exists), a situation similar to the picture above may occur. In the picture above, the data to be sent is transferred to the output buffer at intervals.
  • But Nagle's algorithm is not applicable all the time. According to the characteristics of the transmitted data, when the network traffic is not greatly affected, the transmission speed is faster when the Nagle algorithm is not used than when it is used . The most typical one is " transmitting large file data ". Getting the file data into the output buffer doesn't take much time, so even without using Nagle's algorithm, packets will be transmitted when the output buffer is full. Not only will this not increase the number of data packets, but it will be continuously transmitted without waiting for ACK, so the transmission speed can be greatly improved.
  • In general, not applying the Nagle algorithm can improve the transmission speed. However, if you give up using the Nagle algorithm unconditionally, it will increase excessive network traffic and affect transmission . Therefore, the Nagle algorithm should not be disabled when the data characteristics are not accurately judged .
  • The Nagle algorithm should be disabled for the "large file data" just mentioned. In other words, the Nagle algorithm should be disabled if necessary. "There is little difference in network traffic whether the Nagle algorithm is used or not. The transmission speed using the Nagle algorithm is slower." The disabling method is very simple.
  • As can also be seen from the following code, just change the socket option TCP_NODELAY to 1 (true).
int opt_val=1;
setsockopt(sock, IPPROTO_TCP,TCP_NODELAY,(void*)&opt_val, sizeof(opt_val));
  • You can check the setting status of Nagle algorithm through the value of TCP_NODELAY .
int opt_val;socklen_t opt_len;
opt_len=sizeof(opt_val);
getsockopt(sock,IPPROTO_TCP,TCP_NODELAY,(void*)&opt_val,&opt_len);
  • If the Nagle algorithm is being used, 0 is saved in the opt val variable;
    if the Nagle algorithm is disabled, 1 is saved.

Guess you like

Origin blog.csdn.net/MOON_YZM/article/details/130818508