[LINUX] Introduction to Socket API Learned in C Part 4 UDP Server / Client # 1

This is the 4th introduction to the socket API to learn in C language, which I personally have made into a series. This time, we will check the mechanism of data transmission / reception for UDP and the difference from TCP.

With the following movements regarding UDP, it can be said that it is a communication protocol that is drawing attention again. Google's QUIC protocol: Migrate the Web from TCP to UDP

UDP features

UDP is an application-level extension of IP, a host-to-host protocol. UDP uses port numbers for addressing and, unlike TCP, maintains the boundaries of each message.

In addition, UDP discards datagrams as they are when data is corrupted, but unlike TCP, it is a protocol for providing best-effort datagram services, and it is reliable, such as data retransmission processing. Does not perform any related processing.

At first glance, UDP seems to be inferior to TCP, but there are many good points such as faster operation and easier reception of messages than TCP. The above article also touches on the characteristics and goodness of UDP, so it is a good idea to read it.

And unlike TCP, UDP is a connectionless protocol that can be used without establishing a connection. This is also shown in the source code below.

Execution environment

Client software Mac OS X 10.10.5 gcc Server software x86_64 CentOs 6.8 gcc

I will introduce both server software and client software, but since server software is simpler, I will introduce it first.

Server software source code

udps.c


#include <stdio.h> //printf(), fprintf(), perror(), getc()
#include <sys/socket.h> //socket(), bind(), sendto(), recvfrom()
#include <arpa/inet.h> // struct sockaddr_in, struct sockaddr, inet_ntoa(), inet_aton()
#include <stdlib.h> //atoi(), exit(), EXIT_FAILURE, EXIT_SUCCESS
#include <string.h> //memset(), strcmp()
#include <unistd.h> //close()

#define MSG_FAILURE -1

#define MAX_MSGSIZE 1024
#define MAX_BUFSIZE (MAX_MSGSIZE + 1)

int  get_socket(const char *);
void sockaddr_init (const char *, unsigned short, struct sockaddr *);
int  udp_send(int, const char *, int, struct sockaddr *);
int  udp_receive(int, char *, int, struct sockaddr *);
void socket_close(int);

int main(int argc, char* argv[]) {

	const char *address = "";
	unsigned short port = (unsigned short)atoi(argv[1]);
	struct sockaddr servSockAddr, clitSockAddr;
	char recvBuffer[MAX_BUFSIZE];

	int server_sock = get_socket("udp");
	sockaddr_init(address, port, &servSockAddr);

	if (bind(server_sock, &servSockAddr, sizeof(servSockAddr)) < 0) {
		perror("bind() failed.");
		exit(EXIT_FAILURE);
	}
	
	while(1) {
		int recvMsgSize = udp_receive(server_sock, recvBuffer, MAX_BUFSIZE, &clitSockAddr);
		if (recvMsgSize == MSG_FAILURE) continue;

		printf("message received from %s.\n", inet_ntoa(((struct sockaddr_in *)&clitSockAddr)->sin_addr));

		int sendMsgSize = udp_send(server_sock, recvBuffer, recvMsgSize, &clitSockAddr);
		if (sendMsgSize == MSG_FAILURE) continue;
	}
}

int get_socket(const char *type) {
    int sock;

	if (strcmp(type, "udp") == 0) {
		sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	} else if(strcmp(type, "tcp") == 0) {
		sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	}

    if (sock < 0){
        perror("socket() failed.");
        exit(EXIT_FAILURE);
    }

    return sock;
}

void sockaddr_init (const char *address, unsigned short port, struct sockaddr *sockaddr) {

    struct sockaddr_in sockaddr_in;
    sockaddr_in.sin_family = AF_INET;

    if (inet_aton(address, &sockaddr_in.sin_addr) == 0) {
		if (strcmp(address, "") == 0 ) {
			sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			fprintf(stderr, "Invalid IP Address.\n");
			exit(EXIT_FAILURE);
		}
    }

    if (port == 0) {
        fprintf(stderr, "invalid port number.\n");
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_port = htons(port);

    *sockaddr = *((struct sockaddr *)&sockaddr_in);
}

int udp_send(int sock, const char *data, int size, struct sockaddr *sockaddr) {
	int sendSize;
    sendSize = sendto(sock, data, size, 0, sockaddr, sizeof(*sockaddr));
    if (sendSize != size) {
        perror("sendto() failed.");
		return MSG_FAILURE;
    }
	return sendSize;
}

int udp_receive(int sock, char *buffer, int size, struct sockaddr *sockaddr) {
    unsigned int sockaddrLen = sizeof(*sockaddr);
    int receivedSize = recvfrom(sock, buffer, MAX_BUFSIZE, 0, sockaddr, &sockaddrLen);
	if (receivedSize < 0) {
        perror("recvfrom() failed.");
		return MSG_FAILURE;
	}
	
	return receivedSize;
}

void socket_close(int server) {
    if (close(server) < 0) {
        perror("close() failed.");
        exit(EXIT_FAILURE);
    }
}

Lines 26-27

The socket creation process and the address structure initialization process are separated into functions get_socket () and sockaddr_init (), respectively.

Regarding sockaddr_init (), we just separated the previous address setting process from the main routine.

In get_socket (), the socket () system call is used as in TCP. The difference from TCP is that the second argument specifies SOCK_DGRAM for creating a datagram type socket, and the third argument specifies IPPROTO_UDP, which is a datagram type end-to-end protocol. Again, do not specify 0 and specify it.

29th to 32nd lines

As with TCP, the bind () system call is used to link the address to the socket, but unlike TCP, messages are sent and received as they are without calling listen () or accept ().

In this state, as in the example, if you check with netstat, you can see that ʻudp 0 0 0.0.0.0:8080 0.0.0.0:*` is displayed.

In TCP, in order to establish a connection for each client, a new socket descriptor is acquired by the process of accept () and messages are sent and received, but in the case of UDP, since the connection is not established, bind () was performed. Send and receive messages using the socket itself.

Also, as for the client, we don't normally use the connect () system call. I don't normally use it because it can be used.

From the name connect (), you might think that it is a TCP-only system call that performs processing to establish a connection, but since connect () plays a role in determining the address of the connection destination when sending and receiving messages, It can also be used for UDP.

Of course, in the case of TCP, the packet of the SYN connection request is actually sent to the other server, so the process of determining the address of the connection destination and the process of establishing the connection are performed, but in the case of UDP, Packets for such connection connections will not be sent, and communication using that socket will be limited to communication with the socket associated with the specific port number of the host with a specific IP address.

35th to 36th lines

It is separated into a function called udp_receive (), but it is the process of receiving the message from the client by the recvfrom () system call.

In this case, like recv (), it blocks the execution of the program until the datagram arrives. The default behavior is as described during the recv () system call.

The argument is almost the same as the recv () system call, but since the information of the client that is the source is obtained only when the datagram is received, the pointer to the sockaddr structure that stores the source information is used as the argument in the 5th argument. Pass to.

Then, I pass a pointer that specifies the length to the 6th argument, but when I pass it, the size of the sockaddr structure is stored, and when it returns from the control of recvfrom (), the size actually stored Note that is stored.

As for the reception process to the user process, data is transferred from the receive queue of the socket module in the same way as TCP, but since the boundaries are preserved and the messages are grouped one by one, it is mistaken. You will not receive multiple messages.

Also, with recvfrom (), if a payload larger than the specified receive buffer size is sent, the byte string data after the specified size will be discarded.

Therefore, in UDP, it is necessary to make the size of the receive buffer secured by recevfrom () sufficiently large in advance according to the application requirements. You need at least a size larger than the size sent by sendto ().

By the way, the maximum size of the payload of the UDP datagram is 65507 (65535 (IP packet length) -20 (IP header length) -8 (UDP header length)) bytes due to the restriction of the lower protocol IP. However, when it is sent over the network, the IP datagram will be divided and flow through the network due to the restrictions of the transmittable payload size of the data link (1500 bytes for MTU * Ethernet). ..

When copying a message to the receive buffer, if no error occurs, the sequential processing is continued as it is.

Line 38

Displays the source IP address from the source information stored in clitSockAddr.

40th to 41st lines

It is separated into a function called udp_send (), but the sendto () system call sends a message from the client.

From recvfrom (), sendto () has a similar specification to the similar send () system call, and is supposed to additionally pass the address information of the destination host together. The address information of the destination host uses the structure obtained by recvfrom ().

And unlike TCP, it does not perform retransmission processing due to an error, so there is no need to keep the data to be sent in the buffer. Therefore, when control is returned from sendto (), the data has already been passed to the lower module.

In addition, the maximum value of the UDP send buffer is often determined by the system, so if you want to send a large buffer, you need to use a system call that changes the socket settings separately. I will introduce this system call again.

There is no need to repeatedly send and receive data until the connection is closed, as in TCP, and the datagram messages are bounded, so sendto () and recvfrom () will be sent once each if not discarded. It is possible to get the corresponding message by executing it one by one. However, the order is not guaranteed.

By the way, if you use connect (), you can use the send () and recv () system calls as you did with TCP. Of course, the transmission / reception principle is different from TCP.

And since UDP is a service on the best effort side, it is not always possible to receive messages. Therefore, the following client code adds a simple retransmission process at the application level.

Source code of client software

udpc.c


#include <stdio.h> //printf(), fprintf(), perror(), getc()
#include <sys/socket.h> //socket(), sendto(), recvfrom()
#include <arpa/inet.h> // struct sockaddr_in, struct sockaddr, inet_ntoa(), inet_aton()
#include <stdlib.h> //atoi(), exit(), EXIT_FAILURE, EXIT_SUCCESS
#include <string.h> //memset(), strcmp()
#include <unistd.h> //close()
#include <signal.h> //sigatcion()
#include <errno.h> //erron, EINTR

#define MSG_FAILURE -1
#define MAX_MSGSIZE 1024
#define MAX_BUFSIZE (MAX_MSGSIZE + 1)
#define MAX_TRIES 5
#define TIMEOUT_SECOND 2

int  get_socket(const char *);
void sockaddr_init (const char *, unsigned short, struct sockaddr *);
int  udp_send(int, const char *, int, struct sockaddr *);
int  udp_receive(int, char *, int, struct sockaddr *);
void socket_close(int);
int  input(char *, int);
void remove_lf(char *, int);
void sigaction_init(struct sigaction *, void (*)(int));
void catchAlarm(int);
int  udp_try_receive (int, struct sockaddr *, struct sockaddr *, char *, int, char *);
int  check_correct_server (struct sockaddr *, struct sockaddr *);

int intTries = 0;

int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(stderr, "argument count mismatch error.\n");
        exit(EXIT_FAILURE);
    }

    const char *address = argv[1];
    unsigned short port = (unsigned short)atoi(argv[2]);
    struct sockaddr servSockAddr, clitSockAddr;
	struct sigaction action;

    int server_sock = get_socket("udp");
	sockaddr_init(address, port, &servSockAddr);

	sigaction_init(&action, catchAlarm);
	if (sigaction(SIGALRM, &action, NULL) < 0) {
		perror("sigaction() failure");
		exit(EXIT_FAILURE);
	}

    while(1){
        char sendBuffer[MAX_BUFSIZE];
        char receiveBuffer[MAX_BUFSIZE];
        int inputSize = input(sendBuffer, MAX_BUFSIZE);

        if (strcmp(sendBuffer, "quit\n") == 0) {
            socket_close(server_sock);
			break;
        }

		int receivedSize = udp_try_receive(server_sock, &servSockAddr, &clitSockAddr, sendBuffer, inputSize, receiveBuffer);

		if (check_correct_server(&servSockAddr, &clitSockAddr) == -1) {

			continue;
		}

		remove_lf(receiveBuffer, receivedSize);

        printf("server return: %s\n", receiveBuffer);
    }

	return EXIT_SUCCESS;
}

int get_socket(const char *type) {
    int sock;

	if (strcmp(type, "udp") == 0) {
		sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	} else if(strcmp(type, "tcp") == 0) {
		sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	}

    if (sock < 0){
        perror("socket() failed.");
        exit(EXIT_FAILURE);
    }

    return sock;
}

void sockaddr_init (const char *address, unsigned short port, struct sockaddr *sockaddr) {

    struct sockaddr_in sockaddr_in;
    sockaddr_in.sin_family = AF_INET;

    if (inet_aton(address, &sockaddr_in.sin_addr) == 0) {
		if (strcmp(address, "") == 0 ) {
			sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			fprintf(stderr, "Invalid IP Address.\n");
			exit(EXIT_FAILURE);
		}
    }

    if (port == 0) {
        fprintf(stderr, "invalid port number.\n");
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_port = htons(port);

    *sockaddr = *((struct sockaddr *)&sockaddr_in);
}

int udp_send(int sock, const char *data, int size, struct sockaddr *sockaddr) {
	int sendSize;
    sendSize = sendto(sock, data, size, 0, sockaddr, sizeof(*sockaddr));
    if (sendSize != size) {
        perror("sendto() failed.");
		return MSG_FAILURE;
    }
	return sendSize;
}

int udp_receive(int sock, char *buffer, int size, struct sockaddr *sockaddr) {
    unsigned int sockaddrLen = sizeof(*sockaddr);
    int receivedSize = recvfrom(sock, buffer, MAX_BUFSIZE, 0, sockaddr, &sockaddrLen);
	if (receivedSize < 0) {
        perror("recvfrom() failed.");
		return MSG_FAILURE;
	}
	
	return receivedSize;
}

void socket_close(int server) {
    if (close(server) < 0) {
        perror("close() failed.");
        exit(EXIT_FAILURE);
    }
}

int input(char *buffer, int size) {
    printf("please enter the characters:");

    if (fgets(buffer, size, stdin) == NULL){
        fprintf(stderr, "invalid input string.\n");
        exit(EXIT_FAILURE);
    }

	//flush the stdin buffer
	if (buffer[strlen(buffer)-1] != '\n') {
		int c;
		while((c = getc(stdin) != '\n') && (c != EOF)){}
	}
	
    return strlen(buffer);
}

void remove_lf(char *buffer, int bufferSize) {
	buffer[bufferSize-1] = '\0';
}

void catchAlarm (int ignored) {
	intTries += 1;
}

void sigaction_init(struct sigaction *action, void (*handler)(int) ) {
	action->sa_handler = handler;
	if (sigfillset(&(action->sa_mask)) < 0) {
		perror("sigfillset() failure");
		exit(EXIT_FAILURE);
	}
	action->sa_flags = 0;
}

int udp_try_receive (int sock, struct sockaddr *servSockAddr, struct sockaddr *clitSockAddr,  char *sendBuffer, int sendSize, char *receiveBuffer) {

    int sendedSize = udp_send(sock, sendBuffer, sendSize, servSockAddr);
	int receivedSize;
	while (1) {
		alarm(TIMEOUT_SECOND);
		receivedSize = udp_receive(sock, receiveBuffer, MAX_BUFSIZE, clitSockAddr);
		if (receivedSize == MSG_FAILURE) {
			if (errno == EINTR) {
				if (intTries <= MAX_TRIES) {
					printf("timed out %d.\n", intTries);
					sendedSize = udp_send(sock, sendBuffer, sendSize, servSockAddr);
					if (sendedSize == MSG_FAILURE) break;
					alarm(TIMEOUT_SECOND);
					continue;
				} else {
					printf("total timed out %d.\n", MAX_TRIES);
					exit(EXIT_FAILURE);
				}
			} else {
				exit(EXIT_FAILURE);
			}
		}
		break;
	}
	alarm(0);

	return receivedSize;
}

int check_correct_server (struct sockaddr *sockaddr_1, struct sockaddr *sockaddr_2) {
	if( ((struct sockaddr_in *)sockaddr_1)->sin_addr.s_addr != ((struct sockaddr_in *)sockaddr_2)->sin_addr.s_addr ) {
		fprintf(stderr, "reveiceid from unknown server.\n");
	} else if (ntohs(((struct sockaddr_in *)sockaddr_1)->sin_port) != ntohs(((struct sockaddr_in *)sockaddr_2)->sin_port)) {
		fprintf(stderr, "reveiceid from unknown port.\n");
	} else {
		return EXIT_SUCCESS;
	}

	return MSG_FAILURE;
}

Lines 41-42

As with the server, create a socket and initialize the address structure. You usually don't need to call bind () for the same reasons as for TCP clients.

Lines 44-47

This is one of the points of this UDP program, and we are preparing to detect the UDP transmission timeout by the signal. The process is separated into a function called sigaction_init (), but the process is to create a sigaction structure.

Pass a pointer to this structure to the sigaction () system call to change the default action when SIGALRM is notified to the process. If this change process is omitted and the default process is executed, in the case of this program, the program will end the moment SIGALRM is notified to the process.

To briefly explain what happens when SIGALRM is notified to a process by the OS, the catchAlarm () function is executed and blocks other signals during that time. In fact, while the catchAlarm () function is running, pressing Ctrl + C (SIGINT), for example, does not terminate the process.

Although signals are closely related to network programming, I would like to post another article about signals themselves.

Lines 50 to 70

I wrote about sendto () and recvfrom () for the client earlier, but the function udp_try_receive () on the 60th line is a series of subroutines for sending and receiving processing that detects timeout processing.

First, use the alarm () system call to have the OS send a SIGALRM after 2 seconds. If recvfrom () cannot receive the message, it will wait for the program to process by default, but this time SIGALRM is sent to the process after about 2 seconds and returns -1 which means an error. When using the socket API by combining signals, it seems necessary to deepen the understanding of the series of processes.

And since EINTR is set as errno which means an error code, processing is performed based on that condition, and retransmission processing is performed until MAX_TRIES is reached. We do not guarantee the reliability, but the application is doing something like TCP retransmission processing.

If that doesn't work, the program will be terminated once, assuming that something is wrong. This will prevent the program from being blocked endlessly.

If you can receive the message without waiting, specify 0 for the argument of alarm () to turn off the alarm.

Line 62

We have added a routine to check if the source of the message check_correct_server () function is correct.

This ensures that the client responds from the host and application that sent the packet. It is unlikely that packets will come from other hosts, but it should be included as long as it is practically possible.

Line 67

Since the end of the message should be a line break, we have added a process to convert the line break to a null character.

Next time, I would like to look at a method that combines multi-process processing in addition to TCP sockets, but rather than socket programming, the concept of multi-process becomes an important part, so multi-process. Let's take a look at the process.

Reference book

-Network construction by TCP / IP <Vol.1> Principle / Protocol / Architecture -[TCP / IP network experiment programming understood from the basics-Linux / FreeBSD compatible](https://www.amazon.co.jp/%E5%9F%BA%E7%A4%8E%E3%81%8B%E3 % 82% 89% E3% 82% 8F% E3% 81% 8B% E3% 82% 8BTCP-IP-% E3% 83% 8D% E3% 83% 83% E3% 83% 88% E3% 83% AF% E3% 83% BC% E3% 82% AF% E5% AE% 9F% E9% A8% 93% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0% E2% 80% 95Linux-FreeBSD% E5% AF% BE% E5% BF% 9C-% E6% 9D% 91% E5% B1% B1 / dp / 4274065847 / ref = sr_1_4? s = books & ie = UTF8 & qid = 1471270171 & sr = 1-4 & keywords =% E5% 9F% BA% E7% A4% 8E% E3% 81% 8B% E3% 82% 89% E3% 82% 8F% E3% 81% 8B% E3% 82% 8Btcp% 2Fip) -[TCP / IP Socket Programming C Language](https://www.amazon.co.jp/TCP-IP%E3%82%BD%E3%82%B1%E3%83%83%E3%83% 88% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0-C% E8% A8% 80% E8% AA% 9E% E7% B7% A8-Michael-Donahoo / dp / 4274065197) -[Detailed Linux Kernel 3rd Edition](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3-Linux%E3%82%AB%E3%83%BC%E3 % 83% 8D% E3% 83% AB-% E7% AC% AC3% E7% 89% 88-Daniel-Bovet / dp / 4873111313X)

Recommended Posts

Introduction to Socket API Learned in C Part 4 UDP Server / Client # 1
Introduction to Socket API Learned in C Part 3 TCP Server / Client # 1
Introduction to Socket API Learned in C Part 2 Client Edition
Introduction to Socket API Learned in C Language Part 1 Server Edition
Set up a UDP server in C language
Sample API server to receive JSON in Golang
C API in Python 3
[Introduction to cx_Oracle] (Part 13) Connection using connection pool (client side)
Go language to see and remember Part 7 C language in GO language
Summary of Chapter 2 of Introduction to Design Patterns Learned in Java Language
Overview of how to create a server socket and how to establish a client socket
Chapter 4 Summary of Introduction to Design Patterns Learned in Java Language
Summary of Chapter 3 of Introduction to Design Patterns Learned in Java Language
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part2-
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part4-
Hit the New Relic API in Python to get the server status
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part3-
Introduction to Python Hands On Part 1
Introduction to Protobuf-c (C language ⇔ Python)
How to wrap C in Python
Optimization learned with OR-Tools Part0 [Introduction]
Introduction to Ansible Part 1'Hello World !!'