UDP socket programming basics

    The following diagram shows the general flow when writing a client/server program using UDP sockets.

    Clients in UDP do not need to establish a connection with the server, but use the sendto and recvfrom functions to send and receive data.
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
                 struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
               const struct sockaddr *to, socklen_t addrlen);
                    /* Return value of both functions: the number of bytes read or written if successful; otherwise -1 */

    The first three parameters of these two functions are equivalent to the three parameters of read and write: descriptor, buffer, and number of read and write bytes. The flag parameter will be introduced later when we discuss functions such as recv and send. Set to 0.
    The to parameter of sendto represents the address of the recipient of the datagram, the size of which is specified by the addrlen parameter. The from parameter of recvfrom represents the address of the sender of the datagram, and its size is placed in the location pointed to by the addrlen parameter (note that if from is a null pointer, addrlen must also be a null pointer, indicating that the sender does not care. Doing so exists One risk: any process that knows the client's address and ephemeral port number can send a datagram to the client posing as the server's reply). The last two parameters of these two functions are similar to the last two parameters of connect and accept respectively. Although these two functions can also be used with TCP, they are usually not necessary.
    In UDP, writing a datagram of length 0 results in an IP datagram containing only an IP header (usually 20 bytes for IPv4 and 40 bytes for IPv6) and an 8-byte UDP header with no data. newspaper. This also means that it is acceptable for recvfrom to return a value of 0, rather than a return of 0 from read on a TCP socket indicating that the connection has been closed by the peer.
    Below is an example of a simple echo server written using UDP sockets (checking the return value of the function is omitted).
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define SERV_PORT	9877
typedef struct sockaddr SA;

void dgram_echo(int);

int main(void){
	struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	int sockfd = socket (AF_INET, SOCK_DGRAM, 0);
	bind(sockfd, (SA *)&servaddr, sizeof(servaddr));

	dgram_echo(sockfd);
}

#define MAXLINE	2048

void dgram_echo(int sockfd){
	struct sockaddr_in	cliaddr;
	socklen_t addrlen = sizeof(cliaddr);
	char msg[MAXLINE];
	for(;;){
		socklen_t len ​​= addrlen;
		int n = recvfrom(sockfd, msg, MAXLINE, 0, (SA *)&cliaddr, &len);
		sendto(sockfd, msg, n, 0, (SA *)&cliaddr, len);
	}
}

    Since no connections are required, most UDP servers provide an iterative server rather than a concurrent server like TCP servers.
    Every UDP socket has a receive buffer into which every datagram that arrives at the socket goes. When a process calls recvfrom, the next datagram in the buffer is delivered to the process in FIFO order.
    A code example for the client is also given next.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERV_PORT	9877
#define MAXLINE 2048
typedef struct sockaddr	SA;

void dgram_cli(FILE *fp, int sockfd, const SA *paddr, socklen_t addrlen){
	char buf[MAXLINE+1];
	struct sockaddr *preply_addr = malloc(addrlen);
	socklen_t len ​​= addrlen;
	while(fgets(buf, MAXLINE, fp) != NULL){
		sendto(sockfd, buf, strlen(buf), 0, paddr, addrlen);
		int n = recvfrom(sockfd, buf, MAXLINE, 0, prepy_addr, &len);
		if (len! = addrlen || memcmp (paddr, preply_addr, len)! = 0)
			continue; // filter data that may come from non-requesting servers
		buf[n] = 0;		// null terminate
		fputs(buf, stdout);
	}
}

int main(int argc, char *argv[]){
	if(argc != 2){
		printf("Usage: %s <IPAddress>\n", argv[0]);
		exit(1);
	}
	struct sockaddr_in	servaddr;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton (AF_INET, argv [1], & servaddr.sin_addr);

	int sockfd = socket (AF_INET, SOCK_DGRAM, 0);
	dgram_cli(stdin, sockfd, (SA *)&servaddr, sizeof(servaddr));
	exit(0);
}

    Note that the UDP client/server example here is unreliable. Because if a client datagram or the server's reply is lost, the client will block forever on the recvfrom call. The general way to prevent such permanent blocking is to set a timeout for the client's recvfrom, but this is not a complete solution, because it makes it impossible to determine whether the client's datagram did not reach the server, or the server's response did not return. client. We will continue to discuss how to add reliability to UDP client/server programs later.
    In addition, in order to avoid receiving datagrams from unsolicited servers, the client program filters the address of the datagram sender returned by recvfrom (or uses connect directly, see below). However, if the server host is multihomed, this approach may cause some of the server's reply datagrams to be lost. Because most IP implementations accept datagrams whose destination address is any IP address of the host, regardless of the interface to which the datagram arrives (RFC calls it the weak-end system model. Conversely, the strong-end system model only accepts the arriving interface and the destination address. consistent datagram), so when the server binds to its socket with a wildcard IP address, the kernel will automatically select a source address for the IP datagram encapsulating the reply as the primary IP address of the outgoing interface, which may It is not the address requested by the client, so it is mistakenly filtered out by the client program. One solution to this problem is for the client, after getting the IP address returned by recvfrom, to verify the host's domain name rather than the IP address by looking up the server host's name in DNS. Another solution: the UDP server creates a socket for each IP address configured on the server host, and binds each IP address to its own socket. Then use select to wait for any of them to become readable before giving a reply from the readable socket.
    In UDP sockets, for asynchronous errors caused by functions such as sendto (such as sending a datagram to a server program that is not running, a "port unreachable" ICMP error message is generated, the error is caused by sendto, but sendto itself returns successfully) and not returned to it unless it is connected. Although UDP does not require a connection, it is indeed possible to call connect on a UDP socket, but it does not have a three-way handshake process. The kernel just checks whether there is an immediately visible error, and records the IP address and port of the peer, and then just return immediately.
    Connected UDP sockets have the following three changes compared to the default unconnected UDP sockets.
    (1) The destination address and port number can no longer be specified for the output operation, that is, instead of using sendto (or setting the fifth and sixth parameters to a null pointer and 0 respectively), use write or send instead. Anything written to a connected UDP socket is automatically sent to the address specified by connect.
    (2) Similarly, it is not necessary to use recvfrom to learn the sender of the datagram, but use read, recv or recvmsg instead, because the kernel only returns those datagrams from the destination address specified by connect.
    (3) An asynchronous error will be returned.
    It is usually the UDP client that calls connect, but some UDP servers (such as TFTP) communicate with a single client for a long time, in which case both the client and the server may call connect.
    The DNS in the figure below is an example of calling connect.

    A DNS client can be configured to use one or more DNS servers, usually by listing the IP address of the server host in /etc/resolv.conf. If a single server host is listed (the first client in the figure), the client process can call connect, but if multiple server hosts are listed (the second client in the figure), the client process cannot call connect connect. In addition, the DNS server process usually handles multiple client requests, so connect cannot be called.
    In addition, a process with a connected UDP socket can call connect again for one of two purposes:
    (1) Specify a new IP address and port number. In this case for a TCP socket, connect can only be called once.
    (2) Disconnect the socket. To disconnect a connected UDP socket, set the address family member sin_family or sin6_family of the socket address structure to AF_UNSPEC when calling connect again, which may return an EAFNOSUPPORT error, but that's okay. It is the process that calls connect on the connected UDP socket that disconnects the socket.
    When the application process knows that it is going to send multiple datagrams to the same destination address, it is more efficient to display the connection socket. Because when a datagram is sent on an unconnected UDP socket, the Berkeley-originated kernel will temporarily connect the socket, send the datagram, and then disconnect the connection, even if the next datagram is sent to the same address is also like this.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326069649&siteId=291194637