【TCP/IP Network Programming】The most complete actual combat essence notes 3 -UDP server/client and socket options

UDP based server and client


Implement UDP-based server-side and client-side

Important features of UDP

  1. UDP does not need to call listen and accept to establish a connection
  2. UDP only has socket creation process and data exchange process
  3. Both server and client only need 1 socket
  4. UDP is stateless, so the destination address must be added to each transmission

UDP send and receive function

#include <sys/socket.h>

// 参数:sock:UDP 套接字文件描述符; buff:用户保存接收的数据; nbytes:可接收的最大字节数;
// flags:可选项参数,没有则为 0; to:包含目标地址信息; addrlen:包含目标地址信息的结构体变量的长度
// 返回值:成功时返回接收的字节数,失败时返回 -1。
ssize_t sendto(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen);

#include <sys/socket.h>

// 参数:sock:UDP 套接字文件描述符; buff:待传输的数据; nbytes:待传输的数据长度(单位是字节);
// flags:可选项参数,没有则为 0; from:用来存储发送端的地址信息; addrlen:包含发送端地址信息的结构体变量的长度
// 返回值:成功时返回传输的字节数,失败时返回 -1。
ssize_t recvfrom(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* from, socklen_t *addrlen);

UDP If no address information is found when calling the sendto function, the socket will be automatically assigned an IP and port when the sendto function is called for the first time


UDP transmission characteristics and connect function

UDP sockets with data boundaries

UDP is a protocol with data boundaries;
that is, it emphasizes that the number of calls to the input function must be equal to the number of calls to the output function!

Only then can data integrity be ensured


Connected and unconnected sockets

The process of transmitting data through sendto can be divided into:

  1. Register IP and port with UDP socket
  2. transfer data
  3. Delete the destination address information registered in the UDP socket

When a long-term connection is required, turning UDP into a connected socket will save the time consumed in steps 1 and 3


Create a connected UDP socket

// adr为目标地址信息
connect(sock, (struct sockaddr*)&adr, sizeof(adr));

Gracefully disconnect sockets

What is the half-closed state?

For example, two hosts communicate in two directions. When host A is transmitting data, it immediately executes the close function to close the connection, and host B stops receiving any data immediately, so the data still being transmitted on the road will be discarded! ! !

Half-closed refers to: data can be transmitted but not received, not transmitted but acceptable, two main states


Use the shutdown function to disconnect

#include <sys/socket.h>

// shutdown函数只断开其中一个流
// sock:需要断开的套接字,howto:断开的方式
int shutdown(int sock, int howto);

The second parameter, the way of disconnection, can choose one of the following three types:

  1. SHUT_RD: Disconnect the input stream, after which the socket cannot receive data;
  2. SHUT_WR: Disconnect the output stream, after which the socket cannot send data;
  3. SHUT_RDWR: Simultaneously disconnect the I/O stream.

Server and client code based on half-closed state

First look at the server code

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    
    
    int serv_sd, clnt_sd;
    FILE *fp;
    char buf[BUF_SIZE];
    int read_cnt;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if (argc != 2)
    {
    
    
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    fp = fopen("file_server.c", "rb");
    serv_sd = socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    bind(serv_sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
    listen(serv_sd, 5);

    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sd = accept(serv_sd, (struct sockaddr *)&clnt_adr, &clnt_adr_sz); // 这里 accept 函数只调用了一次,此程序一次运行期间实际上只能接受一次连接。

    while (1)
    {
    
    
        read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
        if (read_cnt < BUF_SIZE)
        {
    
    
            write(clnt_sd, buf, read_cnt);
            break;
        }
        write(clnt_sd, buf, BUF_SIZE);
    }

    shutdown(clnt_sd, SHUT_WR);   // 关闭了输出流
    read(clnt_sd, buf, BUF_SIZE); // 还可以继续接收数据
    printf("Message from client: %s \n", buf);

    fclose(fp);
    close(clnt_sd);
    close(serv_sd);
    return 0;
}

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

Look at the client

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    
    
    int sd;
    FILE *fp;

    char buf[BUF_SIZE];
    int read_cnt;
    struct sockaddr_in serv_adr;
    if (argc != 3)
    {
    
    
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    fp = fopen("receive.dat", "wb");
    sd = socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    connect(sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));

    while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
        fwrite((void *)buf, 1, read_cnt, fp);

    puts("Received file data");
    write(sd, "Thank you", 10);
    fclose(fp);
    close(sd);
    return 0;
}

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

Domain name and network address


domain name system

DNS domain name system, which can convert between IP and domain name

When the current level DNS server cannot recognize the current domain name or IP, it will automatically request the upper level DNS server


Convert between IP and domain name

Use domain name to obtain IP

#include <netdb.h>

// 返回包含IP地址详细信息的指针
struct hostent* gethostbyname(const char* hostname);

Various options for sockets


Socket options and IO buffer size

There are three main levels of IO options:

  1. IPPROTO_IP: Matters related to IP protocol;
  2. IPPROTO_TCP: Matters related to the TCP protocol;
  3. SOL_SOCKET: Common options related to sockets.

Read and set socket optionals (but some optionals are read-only, be careful here!)

#include <sys/socket.h>
// sock 套接字
// level 可选项的协议层
// optname 可选项名
// optval 保存查看结果的缓冲地址值
// optlen 向第四个参数传递缓冲大小
int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);

// 对应参数和getsockopt一致
int setsockopt(int sock, int level, int optname, void* optval, socklen_t optlen);

SO_SNDBUFThe optional item indicates the information about the output buffer size
SO_RCVBUFThe optional item indicates the information about the input buffer size.

The buffer size can be changed, but there will still be some discrepancies


SO_REUSEADDR

Time-wait state

A state in the TCP protocol refers to a state that waits for a period of time after the TCP connection is closed to ensure that the remote port receives the ACK confirmation segment and then enters a state

In the TCP protocol, once the connection is closed, a FIN segment will be generated to send a close request to the remote host. At this time, if the remote host sends an ACK confirmation message segment, but the ACK is lost, the local host will wait until the timeout time (the timeout time is usually twice the maximum segment life time (Maximum Segment Lifetime, MSL), the typical value of MSL is 2 minutes) before leaving time-waitthe state


Address allocation error (binding error)

After the server uses ctrl+c to forcibly terminate the program, it will fall into the time-wait state. If you immediately restart the server with the same port number, a Binding error will occur, so you can only wait for a few minutes before restarting the
server

Hard termination by the client will have no effect


address reassignment

Set SO_REUSEADDRto 1, which means that the port number in the time-wait state is allowed to be reassigned

The default SO_REUSEADDRvalue is 0, it is not allowed


TCP_NODELAY

Nagle algorithm

NagleThe algorithm is an algorithm to improve the transmission performance of the TCP network. Its main function is to reduce the number of small packets transmitted by the network, thereby improving the transmission efficiency of the network.

NagleThe basic implementation idea of ​​the algorithm: When the sender sends a data packet, if there is still data in the current buffer area that has not been sent, then put the data packet into the buffer area and wait for a period of time before sending it.

The waiting time is TCP_NODELAYset through the option, and it is generally enabled by default, that is, it will be sent immediately without waiting.
When the Nagle algorithm needs to be enabled, the TCP_NODELAY option needs to be set to off.

Advantages: Avoid large traffic
Disadvantages: Generate N multiple small-sized packets, increase network load; reduce transmission speed, because you need to wait for ACK messages, especially when transferring large files;


Guess you like

Origin blog.csdn.net/delete_you/article/details/132032431