**
Chapter XIV multicast and broadcasting
**
Suppose we want to send the same data to 10000 users. If TCP is used to provide services, 10000 socket connections are required. Even if UDP sockets are used to provide services, 10000 data transmissions are required.
When the same data needs to be sent to a large number of clients, it will have a negative impact on the server and network traffic. At this time, multicast technology can be used.
14.1 Multicast
Multicast data transmission is based on UDP. The difference is that UDP data transmission is carried out with a single target (one-to-one connection), and multicast data is transmitted to a large number of hosts joining a specific group at the same time, that is, data can be transmitted to multiple hosts.
14.1. 1. Advantages of multicast data transmission mode and traffic
Multicast data transmission features:
The server sends data only once for a specific multicast group
Even if data is sent only once, all clients in the group will receive data.
The number of multicast groups can be increased arbitrarily within the IP range
Joining a specific group can receive data sent to the multicast group.
Multicast group is a class D address (224.0.0.0 ~ 239.225.225.225)
Join multicast group: "in class D IP address, I want to receive multicast data sent to target 239.234.218.234"
Multicast is based on UDP, but the difference is that when a multicast packet is delivered to the network, the router will copy the packet and deliver it to multiple hosts.
As shown in the figure below, multicast needs to be completed with the help of router.
As can be seen from the above figure, the data is transmitted step by step from the sender's main sentence to all members of the group through the router.
"But it seems that this approach is not conducive to network traffic"
As can be seen from the figure, the router copies the same file many times, but reduces the number of packets sent.
Using multicast, when the transmission paths are similar or the same, it can greatly reduce the transmission times of data packets. Only the parent-child files of the router and passed to the main sentence can be used.
Because of this characteristic, multicast is mainly used for "real-time transmission of multimedia data"
14.1.2 Routing, TTL (time to live) and joining groups
1)TTL
In order to propagate multicast packets, TTL must be set because TTL determines the transmission distance of packets.
TTL is expressed as an integer, and it is - 1 every time it passes through a router. When TTL becomes 0, the packet can no longer be delivered and can only be destroyed. (as shown in the figure below)
Therefore, if the TTL value is set too large, the network traffic will be affected, and if it is too small, it will not be delivered to the target.
So how to set TTL?
TTL settings are done through socket options in Chapter 9.
The protocol layer related to TTL is IPPROTO_IP, option named IP_MULTICAST_TTL. Therefore, the TTL can be set to the desired number by a code similar to the following.
int send_sock; int time_live = 1234; ... send_sock = socket(PF_INET, SOCK_DGRAM, 0); setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST, (viod*)& time_live, sizeof(time_live)); ...
How to join a multicast group?
This is also done by setting the socket option.
The protocol layer joining the multicast group is also IPPROTO_IP, option named IP_ADD_MEMBERSHIP. You can join a multicast group through the following code.
int recv_sock; struct ip_mreq join_adr; ... recv_sock = socket(PF_INET, SOCK_DGRAM, 0); ... join_adr.imr_multiaddr.s_addr = "Multicast group address information"; join_adr.imr_interface.s_addr = "Host address information for joining multicast group"; setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (viod*)&join_adr, sizeof(join_adr)); ...
It seems a little unclear, but if you study it carefully before, you will find that it is actually the information in the structure, which is used to store the information of the multicast group we want to join. Then call the setsockopt function, and set the information to the corresponding socket.
Let's take a look at IP_ Contents of mreq structure:
struct ip_mreq { struct in_addr imr_multiaddr; // Write the IP address of the group you want to join struct in_addr imr_interface; // The ip address of the host to which this set of sockets belongs. You can also use INADDR_ANY }
Where in_ I think you are familiar with the addr structure type, which has been used countless times when setting sockets
(the second parameter in bind is the address value, and its structure contains the in_addr structure variable)
14.1. 3. Implement multicast sender and receiver
In multicast, the "Sender" is called the Sender and the Receiver is called the Receiver. They replace the server and client.
Obviously, the Sender is the data sending subject and the Receiver is the data Receiver that needs to join the multicast group.
The following implementation:
Sender: broadcast the information saved in xxx file to AAA group.
Receiver: receive the information passed to AAA group.
First look at the Sender code. Remember to create a UDP socket~
news_sender.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define TTL 200 #define BUF_SIZE 30 void error_handling(char* message); int main(int argc, char *argv[]) { /* code */ if(argc!= 3){ printf("Usage : %s <GROUPIP> <port>\n", argv[0]); exit(1); } // Declare variable int send_sock; struct sockaddr_in mul_adr; int time_live = TTL; char buf[BUF_SIZE]; int count; FILE *fp; // Initialize UDP socket send_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&mul_adr, 0, sizeof(mul_adr)); mul_adr.sin_family = AF_INET; mul_adr.sin_addr.s_addr = inet_addr(argv[1]); // Multicast IP (destination IP) mul_adr.sin_port = htons(atoi(argv[2])); // Multicast port (destination port) // Set TTL of socket setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live)); // Open the file that saves the information to be sent if((fp = fopen("news.txt","r") )== NULL){ error_handling("fopen() error"); } // Start multicast cycle while(!feof(fp)) { fgets(buf, BUF_SIZE, fp); // read data printf("%s\n", buf); count = sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr)); printf("%d\n", count); sleep(2); } fclose(fp); close(send_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
news_receiver.c
#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[]) { /* code */ if(argc!= 3){ // Two parameters are still required here printf("Usage : %s <GROUPIP> <port>\n", argv[0]); exit(1); } // Declare socket related variables int recv_sock; int str_len; char buf[BUF_SIZE]; struct sockaddr_in adr; struct ip_mreq join_adr; // Initialize socket, server address and so on recv_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&adr,0,sizeof(adr)); adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(atoi(argv[2])); // Assign address information to the receiving socket if(bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1){ error_handling("bind() error"); } // Set join_ Multicast group information in ADR structure join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]); // The entered parameter is set to the IP address information of the multicast group join_adr.imr_interface.s_addr = htonl(INADDR_ANY); // Set the local address as: the IP address (new member) that you want to join the multicast group // Set the receiving options of the receiving socket and add a new multicast group member setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr)); // Let's start receiving information circularly while(1) { str_len = recvfrom(recv_sock, buf, BUF_SIZE -1, 0, NULL, 0); if(str_len < 0){ break; } buf[str_len] = 0; fputs(buf, stdout); } close(recv_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
To sum up:
After sending the socket to the UDP socket and modifying the socket property, call the sendto function (at the same time the IP and port number has been allocated), and send the data directly to the target address, that is, multicast group address.
The socket at the receiving end is set as UDP socket, and the address of the current host at the receiving end is assigned to the socket. After modifying the socket property, add the current host IP to the multicast group. In this way, you can receive data by calling recvfrom function.
14.2 broadcasting
Broadcast is similar to multicast in sending data to multiple hosts at one time, but the range of data transmission is different.
Even when multicast spans different networks, as long as you join the multicast group, you can receive data (continuously propagate through the router), while broadcasting can only transmit data to hosts in the same network.
14.2. 1 understanding and implementation of broadcasting
Similar to multicast, broadcasting is based on UDP. According to the form of IP address used in data transmission, broadcasting is divided into the following two types:
Direct broadcast
Local broadcasting
The difference between the two is mainly in the IP address.
In the IP address of direct broadcast, except the network ground, all other host addresses are set to 1
For example, the network address you want to send to is 192.12 All hosts in 34 can transmit data to 192.12 34.255 transmission. In other words, data can be transmitted to all hosts in a specific area by direct broadcasting. (192.12.34.255 represents all hosts under the network address 192.12.34)
The IP address used in local broadcasting is limited to 255.255 two hundred and fifty-five point two five five
For example: 192.32 24 the host in the network sends 255.255 255.255 when transmitting data, the data is passed to 192.32 24 all hosts in the network.
Implement sender and receiver
int send_sock; int bcast = 1; // Initialize variables to set so_ Change the broadcast option information to 1 ... send_sock = socket(PF_INET,SOCK_DGRAM, 0); ... setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&bcast, sizeof(bcast)); ...
Call the setsockopt function to set so_ The broadcast option is set to the value 1 in the bcast variable
It means that data broadcasting can be carried out. This process (modifying socket options) only needs to be modified in the Sender.
14.2. 2 realize the sender and receiver of broadcast data
news_sender_brd.c
#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[]) { /* code */ if(argc!= 3){ printf("Usage : %s <Boradcast IP> <port>\n", argv[0]); exit(1); } // Declare variable int send_sock; struct sockaddr_in broad_adr; char buf[BUF_SIZE]; int so_brd = 1; int count; FILE *fp; // Initialize UDP socket The address must be set to the address of the destination to be transmitted, that is, the multicast group address send_sock = socket(PF_INET, SOCK_DGRAM, 0); // Set TTL of socket setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_brd, sizeof(so_brd)); memset(&broad_adr, 0, sizeof(broad_adr)); broad_adr.sin_family = AF_INET; broad_adr.sin_addr.s_addr = inet_addr(argv[1]); // (target IP) local broadcasting and direct broadcasting are reflected here broad_adr.sin_port = htons(atoi(argv[2])); // (target port) // Open the file that saves the information to be sent if((fp = fopen("news.txt","r") )== NULL){ error_handling("fopen() error"); } // Start broadcast cycle while(!feof(fp)) { fgets(buf, BUF_SIZE, fp); // Read data first count = sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr)); printf("%d\n", count); if(count < 0){ error_handling("sendto error!"); } sleep(2); } fclose(fp); close(send_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
news_receiver_brd.c
#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[]) { /* code */ if(argc!= 2){ // Two parameters are still required here printf("Usage : %s <port>\n", argv[0]); exit(1); } // Declare socket related variables int recv_sock; int str_len; char buf[BUF_SIZE]; struct sockaddr_in adr; // Initialization of socket and receiver address recv_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&adr,0,sizeof(adr)); adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(atoi(argv[1])); // Assign address information to the receiving socket if(bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1){ error_handling("bind() error"); } // There is no need to set socket options for the receiver socket // Let's start receiving information circularly while(1) { str_len = recvfrom(recv_sock, buf, BUF_SIZE -1, 0, NULL, 0); if(str_len < 0){ break; } buf[str_len] = 0; fputs(buf, stdout); } close(recv_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
**
Chapter 15 sockets and standard IO functions
**
15.1 standard IO functions
Master some functions used in file operation
fopen feof fgetc fputs
Several functions are described below:
1 fopen fclose FILE *fopen(const char *path,const char *mode); Among them, path Is the stream we want to open, and mode This is the way we open the file, which determines how the file you open will be treated. There are several ways: "r":Open in read-only mode, the open file must exist. "r+" :Open in read-write mode, the file must exist. "w" : Open in write only mode, create if the file does not exist, and empty if the file exists. "w+" : Open in read-write mode, create if the file does not exist, and empty if the file exists. "a" : Open in write only mode, write to the end of the file in append mode, and create if the file does not exist. "a+": Open the read-write mode. The file does not exist. Create it. Read from the beginning and write from the end. //*********************************************************************** 2 fgetc fputc Function: read characters from files. Header file: #include <stdio.h> Function prototype: int fgetc(FILE *stream); Return value: returns a byte read. If it reads to the end of the file or there is an error in reading, it returns EOF Function: write characters to files. Header file: #include <stdio.h> Function prototype: int fputc(int c, FILE *stream); Return value: under normal call conditions, the function returns the number of characters written to the file ASCII Code value. When there is an error, it returns EOF(-1). When a character or a byte of data is correctly written, the internal write pointer of the file will automatically move back by one byte. EOF Is in the header file stdio.h Macros defined in. //******************************************************************* 3 fgets gputs fgets(); Function: read string from file Header file: #include <stdio.h> Function prototype: char *fgets(char *buf, int size, FILE *stream); Parameter Description: *s Character pointer to the address used to store the resulting data. size Integer data indicating the size of the stored data. *stream File structure pointer to the file stream to be read. Return value: 1.If successful, the first parameter is returned buf; 2.Encountered while reading characters end-of-file,be eof The indicator is set. If this happens before any characters have been read, then buf Keep the original content and return NULL; 3.If a read in error occurs, error Indicator set, return NULL,buf The value of may be changed. fputs(); Function: write a string to the specified file(Do not automatically write string end flags'\0') Header file: #include <stdio.h> Function prototype: int fputs(const char *s, FILE *stream); Parameter Description: *s s Is a character pointer, which can be a string constant or the first address of the array where the string is stored. Return value: the return value is a non negative integer; Otherwise return EOF(Symbolic Constant whose value is-1). //**************************************************************** 4 fread fwrite fread(); Function: read data from file stream, up to nmemb Items, each size Bytes Header file: #include <stdio.h> Function prototype: size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); Parameter Description: *ptr Memory address for receiving data size The number of bytes of each data item to be read, in bytes nmemb To read nmemb Data items, each data item size Bytes. *stream File stream Return value: Returns the number of items actually read, if greater than nmemb It means that an error has occurred. In addition, the file location indicator cannot be determined after an error occurs. If other stream or buffer Null pointer, or unicode The number of bytes written in the mode is odd, This function setting errno by EINVAL And return 0. fwrite(); Function: write a data block to a file Header file: #include <stdio.h> Function prototype: size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); Return value: returns the number of data blocks actually written
15.1. 1 two advantages of standard IO functions
Standard I/O functions have good Portability.
Standard I/O functions can take advantage of buffering to improve performance
About portability:
In fact, all standard functions have this property. In order to support all operating systems (compilers), these functions are defined according to ANSI C standard. This is not limited to network programming, but applicable to all programming fields.
About using buffering to improve performance:
Additional buffering support is available when using standard I/O functions. As mentioned earlier, when creating a socket, the operating system will generate a buffer for I/O for the socket. What is the relationship between the two? As shown in the figure below:
As can be seen from the above figure, when using standard I/O functions to transmit data, two buffers are passed.
For example, when the string "xxxxx" is transferred through the fputs function, the data is first transferred to the standard I/O function buffer. The data is then moved to the socket output buffer, and finally the string is sent to the other host.
The buffer in the socket is mainly set up to implement the TCP protocol. For example, if the data is not received in TCP transmission and needs to be transmitted again, it is to read the data from the output buffer that stores the data.
On the other hand, the main purpose of using standard I/O function buffering is to improve performance.
So how to improve performance? Can buffering improve performance? Why?
Generally, the more data needs to be transmitted, the greater the performance difference between buffering and non buffering
From another perspective, the improvement of performance can be described in the following two aspects:
Amount of data transmitted
The number of times data is moved to the output buffer
First, from the perspective of data volume:
Let me ask a question first: how many bytes are transmitted when a byte of data is sent 10 times (10 packets) and a total of 10 bytes are sent once?
The data packet used when sending data contains header information. The header information has nothing to do with the data size and is filled in according to a certain format. In other words, assume that a header occupies 40 bytes (actually larger):
The former: 10 + 40 * 10 = 410 bytes; The latter: 40 * 1 + 10 = 50 bytes. It can be seen that there is a great difference in the amount of data to be transmitted between the two methods.
Next, the number of times to move from to the output buffer:
The former takes nearly 10 times as long to move 10 times as the latter takes to move once.
15.1. 2 performance comparison between standard IO function and system function
Use actual cases to verify whether it will improve performance:
The first is an example of copying files using system functions:
syscpy.c
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #define BUF_SIZE 3 int main(int argc, char *argv[]) { int fd1, fd2, len; char buf[BUF_SIZE]; fd1=open("news.txt", O_RDONLY); fd2=open("cpy.txt", O_WRONLY|O_CREAT|O_TRUNC); while((len=read(fd1, buf, sizeof(buf)))>0) write(fd2, buf, len); close(fd1); close(fd2); return 0; }
Code for copying files using standard IO functions:
Use the fputs and fgets functions to copy files, which is a buffer based copy
stdcpy.c
#include <stdio.h> #define BUF_SIZE 3 int main(int argc, char *argv[]) { FILE* fp1; FILE* fp2; char buf[BUF_SIZE]; fp1 = fopen("news.txt","r"); fp2 = fopen("cyp.txt", "w"); while(fgets(buf, BUF_SIZE, fp1) != NULL) fputs(buf, fp2); fclose(fp1); fclose(fp1); return 0; }
The difference between the two methods becomes more obvious with the larger the file.
15.1. 3 several disadvantages of standard IO functions
It is not easy for two-way communication
It is also possible to call the flush function frequently
The FILE descriptor needs to be returned as a pointer to the FILE structure
In c language, when opening a file, if you want to read and write at the same time, you should open it in r +, w +, a + mode.
However, due to buffering, you should use the fflush function every time you switch between read and write working states. This also affects buffer based performance improvements. Moreover, in order to use standard I/O functions, a FILE structure pointer is required. When creating a socket, the FILE descriptor is returned by default. Therefore, you need to convert the FILE descriptor to a FILE pointer.
15.2 using standard IO functions
A FILE descriptor is returned when a socket is created, which can only be converted to a FILE structure pointer in order to use standard I/O functions. The conversion method is described below.
15.2. 1 use fopen function to convert to FILE structure pointer
The FILE descriptor returned when creating a socket can be converted into a FILE structure pointer used in standard I/O functions through the fdopen function
#include <stdio.h> FILE* fdopen(int fildes, const char* mode); -> Convert to on success FILE Structure pointer, returned on failure NULL. fildes:File descriptor to be converted mode: To be created FILE Structure pointer mode( mode)information
The second parameter mode above is the same as the open mode in the fopen function. Common parameters are
Read mode: "r" and write mode: "w". Let's use it briefly
desto.c
#include <stdio.h> #define BUF_SIZE 3 int main(int argc, char *argv[]) { FILE* fp1; FILE* fp2; char buf[BUF_SIZE]; fp1 = fopen("news.txt","r"); fp2 = fopen("cyp.txt", "w"); while(fgets(buf, BUF_SIZE, fp1) != NULL) fputs(buf, fp2); fclose(fp1); fclose(fp1); return 0; }
15.2. 2 use fileno function to convert to file descriptor
Describes functions that are the opposite of fopen functions:
#include <stdio.h> int fileno(FILE* stream); ->The converted file descriptor is returned on success and on failure-1
Illustrate with an example:
todes.c
#include <stdio.h> #include <fcntl.h> int main(void) { FILE *fp; int fd = open("data.dat",O_WRONLY|O_CREAT|O_TRUNC); if(fd == -1) { fputs("file open error", stdout); return -1; } printf("First file descripor:%d \n", fd); fp = fdopen(fd,"w"); fputs("tcp ip socket programming\n",fp); printf("Second file descriptor : %d\n", fileno(fp)); fclose(fp); return 0; }
15.3 socket based standard IO functions