Navigation for this series of articles: Write C + + server by hand (0): column - Summary navigation [updating]
The default blocking mode is used when connect is created, but in reality, the connection speed may be slow due to poor network, intermediate proxy server, gateway and other factors. At this time, in blocking mode, the program will block in connect for a long time. Therefore, in actual projects, we generally tend to use asynchronous connect technology to learn how to use IO reuse technology to set asynchronous connect, which can not only lay a foundation for high concurrency and multithreading, but also a necessary knowledge point for back-end development interview.
catalogue
Detailed explanation of parameters
Detailed explanation of important structures
Preparatory knowledge
1. connect function
The client uses connect() to establish a connection with the server:
#include<sys/types.h> #include<sys/socket.h> int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen);
The sockfd parameter returns a socket from the socket system call. serv_ The addr parameter is the socket address monitored by the server, and the addrlen parameter specifies the length of this address.
Function return:
0 is returned when connect is successful. Once the connection is successfully established, sockfd uniquely identifies the connection, and the client can communicate with the server by reading and writing sockfd. If connect fails, return - 1 and set errno. Two common errnos are econnreused and ETIMEDOUT. Their meanings are as follows:
- Econnreused, the target port does not exist, and the connection is rejected.
- ETIMEDOUT, connection timeout.
2. getsocketopt method
getsockopt,setsockopt
Read and set the properties and methods of the file descriptor.
#include <sys/socket.h> int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len); int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t* restrict option_len);
level: specify attributes, such as IPv4, IPv6, TCP, etc
Specific options and parameter meanings: getsockopt
#define SOL_IP 0 #define SOL_IPX 256 #define SOL_AX25 257 #define SOL_ATALK 258 #define SOL_NETROM 259 #define SOL_TCP 6 #define SOL_UDP 17 #define SOL_SOCKET 0xffff
Common options:
1,SO_REUSEADDR
When the TCP connection is in time_ In the wait state, SO_REUSEADDR to force the use of time_ socket address occupied by connection in wait state. Enables the address to be reused immediately.
2,SO_RCVBUF
TCP receive buffer size
3,SO_SNDBUF
TCP send buffer size
4,SO_RCVLOWAT
The TCP receive buffer low water mark is called by the I/O multiplexing system to determine whether the socket is writable.
5,SO_SNDLOWAT
The TCP send buffer low water mark is called by the I/O multiplexing system to determine whether the socket is writable.
6,SO_LINGER
Controls the behavior of the close system call when closing a TCP connection
3. select for IO reuse
select is used to listen for readable, writable, exception and other events on the file descriptor of interest to the user within a specified period of time. The function prototype is as follows:
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
Function return
- Returns the total number of ready file descriptors when select is successful;
- If no file descriptor is ready within the timeout period, select will return 0;
- If select fails, return - 1 and set errno.;
- If the program receives a signal during the select wait, select immediately returns - 1 and sets errno to EINTR.
Detailed explanation of parameters
- nfds: Specifies the total number of listened file descriptors. It is usually set to select to listen to the maximum value + 1 in all file descriptors.
- readfds: set of file descriptors corresponding to readable events.
- writefds: set of file descriptors corresponding to writable events.
- exceptfds: set of file descriptors corresponding to exception events.
- Timeout: sets the select timeout.
Detailed explanation of important structures
readfds, writefds and exceptfds are all fd_set structure. timeout is the timeval structure. Here we will explain these two structures in detail.
1,fd_set
fd_ The definition of set structure is complex, involving in place operation. So we usually use macros to access FD_ Bit in set.
#include <sys/select.h> FD_ZERO(fd_set* fdset); // Clear all bits in fdset FD_SET(int fd, fd_set* fdset); // Set bit in fdset FD_CLR(int fd, fd_set* fdset); // Clear bits in fdset int FD_ISSET(int fd, fd_set* fdset); // Test whether the bit fd of fdset is set
- FD_ZERO is used to empty the file descriptor group. It needs to be emptied before each call to select.
- FD_SET adds a file descriptor to the group, FD_CLR moves a file descriptor out of the group.
- FD_ISSET detects whether a file descriptor is in the group. We use this to detect which file descriptors can perform IO operations after a select call.
2,timeval
struct timeval { long tv_sec; // Seconds long tv_usec; // Subtle number };
Use process
To sum up, our general use process is:
- Preparation - define readfds, timeval, etc
- Use FD_ZERO is cleared and FD is used_ Set sets the file descriptor. Because the set of file descriptors will be modified by the kernel after the event occurs.
- Call select
- Use FD_ISSET detects whether the file descriptor is in the group
Official start
1. Code flow
- Apply the general framework of socket.
- Set the created socket to non blocking mode.
- Connect to the server.
- Call select to listen for writable events on the socket where the connection failed.
- Call getsockopt to read the error code and clear the error on the socket.
2. Client code
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <iostream> #include <fcntl.h> // #define SEND_DATA "NO BLOCKING MODEL" #define SEND_DATA "hello" using namespace std; // Set the file descriptor to non blocking mode int setnoblocking(int fd) { // Gets the old status flag of the file descriptor int old_option = fcntl(fd, F_GETFL); // Set non blocking flag int new_option = old_option | O_NONBLOCK; // Set non blocking mode if (fcntl(fd, F_SETFL, new_option) == -1) { // std::cout << "set no blocking model is error" << std::endl; return -1; } // Returns the old status flag of the file descriptor for later recovery return old_option; } // Set non blocking connect int set_unblock_connect(int port, int time = 10) { //Create socket int clientfd = socket(AF_INET, SOCK_STREAM, 0); if (clientfd == -1) { std::cout << "create client error" << std::endl; return -1; } //Make a request to the server (specific IP and port) struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //Each byte is filled with 0 serv_addr.sin_family = AF_INET; //Use IPv4 address serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //Specific IP address serv_addr.sin_port = htons(port); //port // Set clientfd to non blocking mode int oldconnectfd = setnoblocking(clientfd); if (oldconnectfd == -1) { std::cout << "set no block model is error" << std::endl; close(clientfd); return -1; } int connectfd = connect(clientfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (connectfd == 0) { // Connection succeeded std::cout << "connect success!" << std::endl; // Restore clientfd properties fcntl(clientfd, F_SETFL, oldconnectfd); return clientfd; } else if (errno != EINPROGRESS) { // Only the EINPROGRESS mode indicates that the connection is still in progress std::cout << "connect is error!" << std::endl; return -1; } fd_set readfds; fd_set writefds; struct timeval timeout; FD_ZERO(&readfds); FD_SET(clientfd, &writefds); timeout.tv_sec = time; timeout.tv_usec = 0; int ret = select(clientfd + 1, nullptr, &writefds, nullptr, &timeout); if (ret <= 0) { // select setting error or timeout std::cout << "set select is error" << std::endl; close(clientfd); return -1; } if (!FD_ISSET(clientfd, &writefds)) { std::cout << "no events on clientfd found!" << std::endl; close(clientfd); return -1; } int error = 0; socklen_t length = static_cast<socklen_t>(sizeof error); // Use getsockopt to get and clear the error above sockfd if(getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0) { std::cout << "get socket option is error" << std::endl; close(clientfd); return -1; } if (error != 0) { std::cout << "connect is fail after select with error " << error << std:: endl; close(clientfd); return -1; } // Connection succeeded std::cout << "connect is success after select!" << std::endl; fcntl(clientfd, F_SETFL, oldconnectfd); return clientfd; } int main(int argc, char* argv[]) { if (argc <= 1) { printf("error! please input port!\n"); return 1; } // The first input parameter is the port int port = atoi(argv[1]); // Set non blocking connect int clientfd = set_unblock_connect(port, 10); if (clientfd < 0) { std::cout << "setting unblock connect is error" << std::endl; return 0; } // while (true) { // char recvbuf[32] = {0}; // //In non blocking mode, the program will not be blocked whether there is data or not // int ret = recv(clientfd, recvbuf, 32, 0); // if (ret > 0) { // std::cout << "recv data successfully!" << std::endl; // } else if(ret == 0) { // std::cout << "socket is closed!" << std::endl; // break; // } else { // if (errno == EWOULDBLOCK) { // std::cout << "no data avaliable" << std::endl; // } else if (errno == EINTR) { // std::cout << "recv data interruptered by signal" << std::endl; // } else { // break; // } // } // } close(clientfd); return 0; }
3. Server code
// Set non blocking mode #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <assert.h> #include <errno.h> #include <netinet/in.h> #include <iostream> int main(int argc, char* argv[]) { if (argc <= 1) { printf("error! please input port!\n"); return 1; } // Create listening socket int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(listenfd); int port = atoi(argv[1]); // Bind socket to IP and port struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //Each byte is filled with 0 serv_addr.sin_family = AF_INET; //Use IPv4 address serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //Specific IP address serv_addr.sin_port = htons(port); //port if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { std::cout << "bind listen socket is error" << std::endl; close(listenfd); return -1; } // Enter the listening state and wait for the user to initiate a request if (listen(listenfd, SOMAXCONN) == -1) { std::cout << "listen error" << std::endl; close(listenfd); return -1; } // Receive client requests while (true) { struct sockaddr_in clientaddr; socklen_t clientaddrlen = sizeof(clientaddr); int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen); if (clientfd != -1) { std::cout << "accept a client connection" << std::endl; } else { std::cout << "accept connection error!" << std::endl; } } close(listenfd); return 0; }
4. Experimental effect
reference resources
- Essence of C + + server development
- Linux high performance server programming
- Write C + + server by hand (35): tear code by hand -- non blocking send [ten thousand words long text], the cornerstone of high concurrency and high QPS Technology_ A fan boy addicted to cycling - CSDN blog
- Hand in hand C + + server (34): high concurrency and high throughput IO secret weapon -- epoll pooling technology [20000 words]_ A fan boy addicted to cycling - CSDN blog
- Write C + + server by hand (0): column - Summary navigation [updating]_ A fan boy addicted to cycling - CSDN blog