Socket programming practices in Linux (6) Unix domain protocol and socketpair file descriptor Transfer
The UNIX domain protocol is not an actual protocol family, but a method for executing Client/Server Communication on a single host, the APIs used are the same as those used to perform client/server communication on different hosts. The UNIX domain protocol can be regarded as one of the IPC methods. The Unix domain protocol is mainly used to transmit sockets between different processes on the same machine (only used for communication between local processes. Why not use TCP or UDP sockets?
1) on the same host, UNIX-domain sockets are more efficient, almost twice that of TCP (because UNIX-domain sockets do not need to go through the network protocol stack, and do not need to be packaged or detached, calculate the checksum, maintain the serial number and response, and only copy the application layer data from one process to another. In addition, the UNIX domain Protocol mechanism is essentially reliable communication, network protocols are designed for unreliable communication ).
2) UNIX domain sockets can transmit file descriptors between processes on the same host.
3) Newer implementations of UNIX domain sockets provide the customer's creden (user ID and group ID) to the server, so as to provide additional security check measures.
Note: UNIX domain socket also provides two API interfaces: Stream-oriented and packet-oriented, similar to TCP and UDP, but message-oriented UNIX socket is also reliable, messages are neither lost nor disordered. The Unix domain Protocol indicates that the Protocol address is the path name, not the IP address and port number of the Internet domain.
UNIX domain socket address structure:
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };As for the communication program, it is not very different from TCP communication. The source code of the server/client program based on UNIX domain socket is given below:
/** Server side **/int main () {int listenfd = socket (AF_UNIX, SOCK_STREAM, 0); // use AF_UNIX or AF_LOCAL if (listenfd =-1) err_exit ("socket error"); char pathname [] = "/tmp/test_for_unix"; unlink (pathname); // if the path name already exists in the file system, bind will fail. Therefore, we first call unlink to delete this path name to prevent it from having struct sockaddr_un servAddr; servAddr. sun_family = AF_UNIX; strcpy (servAddr. sun_path, pathname); if (bind (listenfd, (struct sockaddr *) & servAddr, sizeof (servAddr) =-1) err_exit ("bind error "); if (listen (listenfd, SOMAXCONN) =-1) err_exit ("listen error"); while (1) {int connfd = accept (listenfd, NULL, NULL ); if (connfd =-1) {if (connfd = EINTR) continue; err_exit ("accept") ;}} return 0 ;}
/** Client code **/int main () {int sockfd = socket (AF_UNIX, SOCK_STREAM, 0); if (sockfd =-1) err_exit ("socket error"); char pathname [] = "/tmp/test_for_unix"; struct sockaddr_un servAddr; servAddr. sun_family = AF_UNIX; strcpy (servAddr. sun_path, pathname); if (connect (sockfd, (struct sockaddr *) & servAddr, sizeof (servAddr) =-1) err_exit ("connect error "); return 0 ;}
Notes on socket programming in UNIX Domains
1. If bind is successful, a file is created, which is a socket type. You can use ls-l to view the file starting with s. The permission is 0777 &~ Umask
2. sun_path it is best to use the absolute path of the file under the/tmp directory. It is best to use unlink to delete the file when you start it again, otherwise the address will be prompted to be in use.
3. the UNIX domain protocol supports streaming sets of interfaces (which need to handle packet sticking issues) and reporting sets of interfaces (based on datagram)
4. When the UNIX domain streaming socket connect finds that the listening queue is full, an ECONNREFUSED will be immediately returned. This is different from TCP. If the listening queue is full, the incoming SYN will be ignored, which causes the other party to re-send SYN.
5. If you use streaming sockets, you still need to solve the problem of sticking packets.
Pass the file descriptor socketpair
#include
#include
int socketpair(int domain, int type, int protocol, int sv[2]);
Create a full-duplex Stream pipeline
Parameters:
Domain: the protocol family. You can use the AF_UNIX (AF_LOCAL) UNIX domain protocol. in Linux, this function only supports this protocol;
Type: socket type. You can use SOCK_STREAM.
Protocol: protocol type. Generally, the value is 0, indicating that the kernel automatically selects the protocol type.
Sv: returns the pairs of sv [0] and sv [1].
The socketpair function is similar to the pipe function: it can only communicate with unrelated processes, but the anonymous pipeline created by pipe is half-duplex, while the socketpair function can be considered as creating a full-duplex pipeline.
You can use socketpair to create the returned socket for parent-child process communication, as shown in the following example:
Int main () {int sockfds [2]; if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds) =-1) err_exit ("socketpair error "); pid_t pid = fork (); if (pid =-1) err_exit ("fork error"); // The parent process else if (pid> 0) {close (sockfds [1]); // similar to pipe, disable int iVal = 0 at one end first; while (true) {cout <"value =" <iVal <endl; write (sockfds [0], & iVal, sizeof (iVal )); // send it to the sub-process read (sockfds [0], & iVal, sizeof (iVal); sleep (1 );}} // sub-process else if (pid = 0) {close (sockfds [0]); int iVal = 0; while (read (sockfds [1], & iVal, sizeof (iVal)> 0) {++ iVal; write (sockfds [1], & iVal, sizeof (iVal); // send to parent process }}}
Sendmsg/recvmsg
#include
#include
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
They are similar to sendto/send and recvfrom/recv functions, but they can transmit more complex data structures with more powerful functions. They can not only transmit general data, but also transmit additional data, for example, a file descriptor, but only a socket, cannot be a file.
// Msghdr struct msghdr {void * msg_name;/* optional address */socklen_t msg_namelen;/* size of address */struct iovec * msg_iov; /* scatter/gather array */size_t msg_iovlen;/* # elements in msg_iov */void * msg_control;/* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags;/* flags on received message */}; struct iovec/* Scatter/gather array items */{void * iov_base; /* Starting address */size_t iov_len;/* Number of bytes to transfer */};
Msghdr struct member explanation:
1) msg_name: The address pointer of the Peer. If you do not care about it, set it to NULL;
2) msg_namelen: The address length. Set it to 0 if you do not care;
3) msg_iov: refers to the pointer of the structure iovec, pointing to the common data to be sent, see.
The member iov_base can be considered as the buf for normal data transmission;
The iov_len member is the buf size;
4) msg_iovlen: when there are n iovec struct, this value is n;
5) msg_control: a pointer to the cmsghdr struct (see figure). This field needs to be set when sending auxiliary data (such as control information/file descriptor, when sending normal data, you do not need to care about this field, and msg_controllen can be set to 0;
6) msg_controllen: there may be more than one cmsghdr struct (SEE ):
7) flags: Don't worry;
The padding byte is used for alignment, and the buffer size is an integer multiple of 4. to alignment, some padding bytes may exist (see figure), which is related to the implementation of the system, but we don't have to worry about it. We can use some function macros to obtain the relevant values, as shown below:
# Include
Struct cmsghdr * CMSG_FIRSTHDR (struct msghdr * msgh); // obtain the first message of secondary data, struct cmsghdr * CMSG_NXTHDR (struct msghdr * msgh, struct cmsghdr * cmsg ); // obtain the next information of the secondary data size_t CMSG_ALIGN (size_t length); size_t CMSG_SPACE (size_t length); size_t CMSG_LEN (size_t length); // The length is used (actual) for details about the data length, see unsigned char * CMSG_DATA (struct cmsghdr * cmsg );
File descriptor passed between processes
Struct filling is important and difficult:
/** Example: encapsulate two functions send_fd/recv_fd for transferring the file descriptor between processes **/int send_fd (int sockfd, int sendfd) {// fill in the name field struct msghdr msg; msg. msg_name = NULL; msg. msg_namelen = 0; // fill in the iov field struct iovec iov; char sendchar = '\ 0'; iov. iov_base = & sendchar; iov. iov_len = 1; msg. msg_iov = & iov; msg. msg_iovlen = 1; // fill in the cmsg field struct cmsghdr cmsg; cmsg. cmsg_len = CMSG_LEN (sizeof (int); cmsg. cmsg_level = SOL_SOCKET; cmsg. cmsg_type = SCM_RIGHTS; * (int *) CMSG_DATA (& cmsg) = sendfd; msg. msg_control = & cmsg; msg. msg_controllen = CMSG_LEN (sizeof (int); // send if (sendmsg (sockfd, & msg, 0) =-1) return-1; return 0 ;}
Int recv_fd (int sockfd) {// fill in the name field struct msghdr msg; msg. msg_name = NULL; msg. msg_namelen = 0; // fill in the iov field struct iovec iov; char recvchar; iov. iov_base = & recvchar; iov. iov_len = 1; msg. msg_iov = & iov; msg. msg_iovlen = 1; // fill in the cmsg field struct cmsghdr cmsg; msg. msg_control = & cmsg; msg. msg_controllen = CMSG_LEN (sizeof (int); // receives if (recvmsg (sockfd, & msg, 0) =-1) return-1; return * (int *) CMSG_DATA (& cmsg );}
To explain the send_fd function:
Msg. msg_name = NULL;
Msg. msg_namelen = 0;
Msg. msg_iov = & vec;
Msg. msg_iovlen = 1; // The main purpose is not to pass data, so only 1 character is passed
Msg. msg_flags = 0;
Vec. iov_base = & sendchar;
Vec. iov_len = sizeof (sendchar );
In these rows, we need to note that our current purpose is not to transmit normal data, but to transmit file descriptors, so we only define one 1-byte char, the rest can be understood by referring to the preceding parameters.
Now we only have one cmsghdr struct. We can transmit the length of the file descriptor send_fd, that is, the extra data size to be transmitted, to the CMSG_SPACE macro as a parameter to get the size of the entire struct, includes some padding bytes, as shown in, that is
Char cmsgbuf [CMSG_SPACE (sizeof (send_fd)];
The following two rows can be further obtained:
Msg. msg_control = cmsgbuf;
Msg. msg_controllen = sizeof (cmsgbuf );
Then, fill in the cmsghdr struct and pass in the msghdr pointer. The CMSG_FIRSTHDR macro can get the pointer of the first cmsghdr struct, that is
P_cmsg = CMSG_FIRSTHDR (& msg );
Then fill fields with pointers, as shown below:
P_cmsg-> cmsg_level = SOL_SOCKET;
P_cmsg-> cmsg_type = SCM_RIGHTS;
P_cmsg-> cmsg_len = CMSG_LEN (sizeof (send_fd ));
Input the send_fd size. The CMSG_LEN macro can get the size of the cmsg_len field.
Finally, input the struct pointer p_cmsg and the macro CMSG_DATA to get the position pointer for storing send_fd. Put send_fd in, as shown below:
P_fds = (int *) CMSG_DATA (p_cmsg );
* P_fds = send_fd; // transfers the file descriptor by passing the auxiliary data.
The recv_fd function is similar.
Int main () {int sockfds [2]; if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds) =-1) err_exit ("socketpair error "); pid_t pid = fork (); if (pid =-1) err_exit ("fork error"); // The sub-process opens the file in read-only mode, send the file descriptor to the sub-process else if (pid = 0) {close (sockfds [1]); int fd = open ("read.txt", O_RDONLY ); if (fd =-1) err_exit ("open error"); cout <"In child, fd =" <fd <endl; send_fd (sockfds [0], fd);} // The parent process reads data from the file descriptor else if (pid> 0) {close (sockfds [0]); int fd = recv_fd (sockfds [1]); if (fd =-1) err_exit ("recv_fd error"); cout <"In parent, fd = "<fd <endl; char buf [BUFSIZ] = {0}; int readBytes = read (fd, buf, sizeof (buf )); if (readBytes =-1) err_exit ("read fd error"); cout <buf ;}}
We know that the file descriptor opened by the parent process before fork can be shared by the child process, but the file descriptor opened by the child process cannot be shared by the parent process, the preceding program opens a file descriptor in the child process and passes the file descriptor to the parent process through the send_fd function. The parent process can receive the file descriptor through the recv_fd function. Create a file read.txt, enter a few characters, and then run the program.
Note:
(1) only the UNIX domain protocol can transmit file descriptors between local processes;
(2) Transferring file descriptors between processes is not the value of transferring file descriptors (in fact, the two values of send_fd/recv_fd are also different ), instead, a new file descriptor is created in the receiving process, and the file descriptor passed in the sending process points to the same file table item in the kernel.