A typical function Call of UDP C/s
UDP does not have a connection like TCP, the client directly sendto to a server to send data, the server side has been recvfrom blocked to receive any client-sent data.
Second, sendto and Recvfrom functions
int sendto (int sockfd, const void* buff, size_t nbytes, int flag, const struct sockaddr* to, socklen_taddrlen); int Recvfro m (int sockfd, void* buff, size_t nbytes, int flag, struct sockaddr* from, socklen_t* Addrlen);
1. Successful return of bytes, failure return-1
2. The two functions have three more parameters compared to read and write.
(1) Flag said, this place is first set to 0
(2) The address structure of the SendTo indicates the socket address of the sending destination. ADDRLEN Specifies the address length, which is an integer type. Corresponds to a socket address in TCP for connect.
(3) The address structure of the recvfrom indicates the socket address of the sending end of the datagram. Addrlen is an integer address for this socket address. Corresponds to the socket address in TCP's accept.
3. Writing a 0-length datagram is possible, forming an IP datagram that contains only the IP header (20 bytes) and the UDP header (8 bytes). So recvfrom returns 0, which is acceptable. Instead of read returns 0 as TCP, the connection is closed.
4. The socket address parameter of the recvfrom can be null, indicating that it does not care who sent the data. At this point the Addrlen must also be null.
Iii. writing a back-up server using UDP
#include "unp.h" #define MAXLINE 1024#define PORT 13void err_sys (const char* s) { fprintf (stderr, "%s\n", s); Exit (1);} void Str_echo (int sockfd, struct sockaddr* cliaddr,socklen_t len) { int nbyte;socklen_t n;char buff[maxline]; while (true) { n=len; Nbyte=recvfrom (SOCKFD, Buff, MAXLINE, 0, cliaddr, &n); if (nbyte<0) err_sys ("recv error"); SendTo (SOCKFD, Buff, nbyte, 0, CLIADDR, n); }} int main (int argc, char** argv) { int sockfd; struct sockaddr_in servaddr,cliaddr; Sockfd=socket (af_inet, SOCK_DGRAM, 0); Bzero (&servaddr,sizeof (SERVADDR)); servaddr.sin_family=af_inet; Servaddr.port=htona (port); Servaddr.sin_addr.s_addr=htonl (inaddr_any); Bind (SOCKFD, (struct sockaddr*) &servaddr, sizeof (SERVADDR)); Str_echo (SOCKFD, (struct aockaddr*) &cliaddr, sizeof (CLIADDR));}
1. The function will never terminate at first, under normal circumstances. It is not like a TCP connection, there are four times to terminate the connection.
2. Processing function Str_echo is an iterative function that is not as concurrent as TCP. Generally, TCP servers are concurrent, and UDP servers are iterative. Why? Below say
3. Each UDP socket will have a receive buffer, similar to a queue. More than one data report to the UDP server, it will queue, call the Recvfrom function, from this queue header out of the datagram to the process. While TCP is a sub-process for each client one connection fork, and each connection one socket, one receive buffer per socket, so we are going to listen to each receive buffer concurrently. UDP is sent by any customer to the datagram into a receive buffer, so there is no need for any concurrent server, and can not be made concurrent.
4. The Str_echo function is protocol Independent.
Iv. using UDP to rewrite the echo client
#include "unp.h" #define MAXLINE 1024#define PORT 13void err_sys (const char* s) {fprintf (stderr, "%s\n", s); Exit (1);} void str_cli (int sockfd, file* fd,const struct sockaddr* servaddr,socklen_t addrlen) {int nbytes; Charbuff[maxline],recvbuff[maxline]; while (Fgets (BUFF,MAXLINE,FD)!=null) {sendto (Sockfd,buff, strlen (Buff), 0, servaddr, Addrlen); Nbytes=recvfrom (Sockfd,recvbuff, MAXLINE, 0, NULL, NULL); if (nbytes<0) Err_sys ("Recvfrom error"); recvbuff[nbytes]=0; Fputs (recvbuff,stdout); }}int Main (int argc, char** argv) {int sockfd; struct SOCKADDR_INSERVADDR; Char buff[maxline+1]; if (argc!=2) err_sys ("input error"); if ((Sockfd=socket (af_inet,sock_dgram, 0)) <0) Err_sys ("Socket error"); Bzero (&servaddr, sizeof (SERVADDR)); Servaddr.sin_family=af_inet; Servaddr.sin_port=htons (port); if (Inet_pton (Af_inet, argv[1],&servaddr.sin_addr) <=0) err_sys ("IP address Error"); STR_CLI (Sockfd,stdin, (struct sockaddr*) &servaddr,sizeof (SERVADDR)); Exit (0);}
1. The local port is not specified for the client, and the kernel is automatically assigned when the client sendto the first time. 2. The recvfrom socket address here is a null pointer, and doing so is very dangerous.
Because it is possible for any host to send a message to this temporary port on this client, this client will assume that the message was sent from the server side. Cause the message to be confusing. So this is a problem, the following solution.
3. Loss of datagrams
This can cause the client to block permanently on the Recvfrom function if a client sends a datagram to the server is lost, or if the server-side packet is lost to the client.
We can set a timeout for recvfrom, but the timeout still doesn't solve the problem completely. Because if it times out, we don't know if the server-side datagram is missing or the server---client database is missing.
4. For the above 2 questions, we try to obtain the recvfrom socket address and whether the socket address sent by SendTo is consistent to determine whether this message is from the peer-to-peer server.
The STR_CLI function we modified is as follows:
void str_cli (int sockfd, file* fd, const struct sockaddr* servaddr, socklen_t addrlen) { int nbytes; Charbuff[maxline],recvbuff[maxline]; struct sockaddr * fromaddr=new sockaddr (); Socklen_tfromaddrlen=addrlen; while (Fgets (BUFF,MAXLINE,FD)!=null) { sendto (sockfd, Buff,strlen (Buff), 0, servaddr, Addrlen); Nbytes=recvfrom (Sockfd,recvbuff, maxline,0, fromaddr, &fromaddrlen); if (Fromaddrlen!=addrlen | | memcmp (SERVADDR, fromaddr, Addrlen)!=0) { fputs ("Not from server, ignored", stdout ); Continue; } if (nbytes<0) err_sys ("Recvfrom error"); recvbuff[nbytes]=0; Fputs (recvbuff,stdout); }}
As can be seen, we are comparing sendto, the server socket address, and recvfrom get the server socket address is equal. Here we compare the lengths of the two and then compare the bytes by byte.
There's still a problem here:
If the server is a multi-host, that is, two IP addresses, such as IP1,IP2. Since we are writing server-side programs, the bind function parameter is a wildcard IP, so when we sendto, is the use of IP1, and when the server back-firing, the kernel automatically selected IP2, this will let our client misjudged that the echo message is not from the server side.
Two solutions:
1> recvfrom Gets the IP, query DNS obtains the host's domain name to determine whether the message came from that host.
The 2> server side creates a socket for each IP, using bind to each IP address. Then use Select to listen to these sockets, waiting for one to become readable, indicating that the client is using this IP, then the server uses this IP socket to be able to shoot back.
5. The server is not running
What happens when we start the client first and do not start the server side:
We enter a row of data from the console, and the client will block forever on the Recvfrom function.
Underlying mechanism:
The data is sent to the server host, the destination port of the sending host is not turned on, so returns an ICMP message unreachable by the port, which is not returned to the client process (why?). )。 So the client is used to block on the recvfrom.
Async Error: The error in this example is caused by the SendTo function, but SendTo returns successfully, and ICMP returns an error later. This is an asynchronous error.
A basic rule: for a UDP socket, the asynchronous error thrown by it is not returned to it unless it is already connected. (Note that UDP also has the Connect function).
Why does the ICMP message not return to the client process? What is the rationale for UNIX design?
Suppose we use the client to send 3 messages continuously, the destination server of 2 messages is normal, the last server does not start, there will be an ICMP message, assuming that the message is Recvfrom fetched, RECVFROM returns a negative value to indicate the error, and then errno to the error type. But note that at this point the client does not know which destination server, which socket error, the kernel can not tell the process, because recvfrom at this time the information returned is only errno, so UNIX rules, do not return to the process.
6. Use Connect for UDP sockets
The above mentioned asynchronous errors that are not connected to a UDP socket and are not returned to the process, where we can connect to a UDP socket using connect.
The call to the Connect function, like TCP, specifies the socket address of the destination server. Note that there is no three handshake, just check to see if there is an immediate known error (such as the destination host unreachable).
The difference between connected UDP sockets and non-connected UDP sockets:
(1) Connected socket, use send directly, send datagram to connect destination server socket. Instead of using SendTo.
(2) Connected sockets, directly using write, receive datagrams from the Connect destination server socket, datagrams from other destination server sockets will not be submitted to the socket.
It also means that a connected socket can only communicate with one peer.
The non-connected sockets can obviously communicate with any number of peers.
(3) A connected socket occurs when an asynchronous message is returned to the process because the destination socket is already known. The non-connected socket is not returned to the process.
Note: The client's UDP sockets are generally connected, while the server side or SendTo and recvfrom,connect only affect local sockets.
Note: We can point to a connected UDP socket multiple times with connect, while TCP is not available. Connect multiple times in the following situations
(1) Specify a new IP address and port number
(2) Disconnect the socket. At this point, the address family of the socket address structure (IPV4 sin_family) is set to Af_unspec.
7. Performance
When we sendto a non-connected UDP socket for two consecutive times, take a look at the specific steps:
Connecting sockets
Send a first datagram
Disconnecting sockets
Connecting sockets
Send a second datagram
Disconnecting sockets
If two datagrams are the same destination socket, then we should use the obvious connect and then improve the efficiency. Because this only requires a connection and disconnection.
A connection in UNIX requires a one-third overhead of a UDP transfer.
8. We revise the above client str_cli function
void str_cli (int sockfd, file* fd,const struct sockaddr* servaddr, socklen_t addrlen) { int nbytes; Char Buff[maxline],recvbuff[maxline]; Conect (Sockfd,servaddr,addrlen); while (Fgets (BUFF,MAXLINE,FD)!=null) { write (sockfd, buff, strlen (buff)); Nbytes=read (Sockfd,recvbuff, MAXLINE); if (nbytes<0) err_sys ("read error"); recvbuff[nbytes]=0; Fputs (recvbuff,stdout); }}
At this point we run the client, enter a host IP address for a server program that is not started, and then enter a row of data from the console, and the result is readerror. The asynchronous error is returned to the process.
Note: At this point when we connect, there is no error, until we send a message to return the error. If TCP is at this point, an error will occur at connect. Reason?
Because the UDP connect does not trigger three handshakes, and TCP connect triggers three handshakes and the destination port is unreachable, the server returns the RST packet.
V. UDP lack of traffic control
Assuming that a client sends a large amount of data continuously, the server side uses the socket receive buffer to queue to receive the data, but when the sent data exceeds the socket receive buffer, the server side automatically discards the incoming datagram, at which point there will be no error on the client and server side.
Therefore, UDP is not flow control.
Vi. IP addresses and port numbers in UDP
1. Non-connected UDP sockets, if we do not bind,
When SendTo, the kernel chooses a local IP address and port number, so two consecutive sendto on the same host, the source IP address and the port number of two messages may be different.
Also, after the server receives recvfrom, the message is sendto, which may cause the source IP address and port number of the back-up message to be different from the destination IP address and port number of the Recvfrom message.
2. The UDP socket is connected, if there is no bind
The local IP and port number of the connected socket may be different when connecting to the same destination IP port number multiple times with the same client.
We can use GetSockName to get the local IP and port number of a connected UDP socket.
We use Recvmsg to get the local IP and port number of the non-connected UDP socket.
Vii. We use select TCP and UDP to rewrite the back-up server
#include "UNP" #define MAXLINE 1024#define PORT 13#define maxcon 5void err_sys (const char* s) {fprintf (stderr, "%s\n", s ); Exit (1);} void Str_echo (int sockfd) {int nbyte; Char Buff[maxline];again:while ((Nbyte=read (SOCKFD, Buff, MAXLINE) >0) {write (SOCKFD, buff, NB Yte); } if (nbyte<0 &&errno==eintr) goto again; else if (nbyte<0) Err_sys ("read error");} void sig_chld (int); int main (int argc, char** argv) {int listenfd, CONNFD, UDPFD; Pid_tchildpid; Char Recvmsg[maxline]; const int on=1; struct sockaddr_in servaddr,cliaddr; Socklen_t Len; Listenfd=socket (af_inet, sock_stream, 0); Bzero (&servaddr,sizeof (SERVADDR)); Servaddr.sin_family=af_inet; Servaddr.port=htons (port); Servaddr.sin_addr.s_addr=htonl (Inaddr_any); SetSockOpt (Listenfd,sol_socket, SO_REUSEADDR, &on,sizeof (on)); Bind (LISTENFD, (struct sockaddr*) &servaddr, sizeof (SERVADDR)); Listen (Listenfd,maxcon); Udpfd=socket (Af_inet,sock_dgram, 0); Bind (UDPFD, (struct sockaddr*) &servaddr, sizeof (SERVADDR)); Fd_set Fset; Fd_zero (&fset); int Maxfdp1=max (LISTENFD,UDPFD) +1; int nready; Signal (SIGCHLD,SIG_CHLD); while (true) {fd_set (listenfd,&fset); Fd_set (Udpfd,&fset); Nready=select (Maxfdp1,fset,null,null,null); if (nready<0) {if (ERRNO==EINTR) continue; else Err_sys ("select Error"); } if (Fd_isset (Listenfd,&fset)) {connfd=accept (listenfd,null,null); if ((Childpid=fork ()) ==0) {close (LISTENFD); Str_echo (CONNFD); Exit (0); } close (CONNFD); } if (Fd_isset (UDPFD, &fset)) {int nbytes; Len=siezof (CLIADDR); Nbytes=recvfrom (Udpfd,recvmsg, maxline,0, (struct sockaddr*) &cliaddr,&len); SendTo (udpfd,recvmsg,nbytes,0, (struct sockaddr*) &cliaddr,len); } }}
This involves the IO multiplexing of SELECT, signal processing, fork subprocess, TCP server, UDP server, socket options.
Here the Str_echo function and the fifth chapter of the same, signal processing function is not implemented, notice the function to call Waitpid.
Note the interesting part here: We just use Select to listen for TCP's listening sockets and UDP sockets. Use child processes for connected TCP sockets to process.
That is, TCP's parallel server and UDP's iterative server.
Before we were all using Select to listen for TCP listening sockets and connected sockets, making the program a complete single process. This is not the case, as it is too cumbersome, and this is just a demonstration of using Select to listen for both TCP and UDP connections. Very concise, very interesting, this piece of code carefully read. A lot of interesting places.
Note here: The same server, TCP sockets and UDP sockets use the same port, which is possible.
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
The 8th chapter of the
UNIX Network Programming learning note is based on UDP socket programming