Network programming: 1. Network IO and select

Network programming: network IO and select

1. What is network IO?

The meaning of IO is input and output (input/output)

Network IO is essentially a type of IO, involving objects of two systems

  1. The process or thread that calls IO from user space
  2. Kernel system in kernel space

2. What is a socket?

The socket socket is a communication mechanism that provides the abstraction of the TCP/IP protocol and provides a set of interfaces to the outside world. Through this interface, the functions of the TCP/IP protocol can be used uniformly and conveniently.

3. What is FD?

FD (File descriptor), the file descriptor is a non-negative integer , essentially an index value

The value range of FD: 0~OPEN_MAX - 1

  • In the semantics of POSIX, the three fd values ​​​​of 0, 1, and 2 are respectively assigned as standard input (STDIN_FILENO), standard output (STDOUT_FILENO), and standard error (STDERR_FILENO)
  • The maximum number of open files can be viewed through the ulimit command, for example: ulimit -n

When opening a file, the kernel returns a file descriptor (obtained by the open system call) to the process. When reading and writing this file, you only need to use this file descriptor to identify the file, and pass it as a parameter to read and write read and write files

4. Linux network connection (single connection)

The following is the most basic TCP port monitoring code. By default, the listenfd that creates a socket is blocking. If it needs to become non-blocking, you need to use the fcntl function to perform an OR operation on listenfd (bit operation)

The following code can only establish a connection with one client (can only be connected once), and can connect to multiple clients, but it will only receive the information of the first connected client

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<pthread.h>
#include<unistd.h>

#define BUFFER_LENGTH 128

int main(){
    
    
    // 1. 使用socket套接字创建fd, fd默认是阻塞的
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
    
    
        printf("create listend fd failed");
        return -1;
    }
    // 2. listenfd绑定一个固定的地址
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
		// 2.1 将listenfd绑定协议簇, 端口9999, 这样listenfd就会一直监听9999端口的信息
    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))){
    
    
        printf("bind failed");
        return -2;
    }
		// 修改fd的性质, 改为非阻塞
		// int flag = fcntl(listenfd, F_GETFL, 0);
    // flag |= O_NONBLOCK; // 如果直接set可能会把原来已有的属性给覆盖掉,所以需要或
    // fcntl(listenfd, F_SETFL, flag);
		// 3. 新建一个监听队列
		// Prepare to accept connections on socket FD.
	  // N connection requests will be queued before further requests are refused.
		listen(listenfd, 10);
		// 4. 新建一个连接客户端的fd
    /* Structure describing an Internet socket address.  */
		struct sockaddr_in client;
    socklen_t len = sizeof(client);
    // 此时listenfd是一个阻塞的, 因为新建socket的时候默认就是阻塞的
		// Await a connection on socket FD.
    // When a connection arrives, open a new socket to communicate with it
    int clientfd = accept(listenfd, (struct sockaddr*)&client, &len);
    while(1){
    
    
				// 4.1 新建一个接收缓冲区
        unsigned char buffer[BUFFER_LENGTH] = {
    
    0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        printf("buffer: %s, ret: %d\n", buffer, ret);
				// Send N bytes of BUF to socket FD.  Returns the number sent or -1.
        ret = send(clientfd, buffer, ret, 0);
    }

5. Linux network connection (multithreading)

In order to solve the above problem of only communicating with the first client, use multi-threading to improve

Using multi-threads to handle multi-client connections will lead to a problem. If 10,000 clients connect, you have to create 10,000 new threads, which is a bit expensive.

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<pthread.h>
#include<unistd.h>

#define BUFFER_LENGTH 128

void *routine(void *arg){
    
    
    int clientfd = *(int *)arg;
    while(1){
    
    
        unsigned char buffer[BUFFER_LENGTH] = {
    
    0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        // 关闭客户端
        if(ret == 0){
    
    
            close(clientfd);
            break;
        }
        printf("buffer: %s, ret: %d\n", buffer, ret);

        ret = send(clientfd, buffer, ret, 0);
    }
}

int main(){
    
    
    // 1. 使用socket套接字创建fd, fd默认是阻塞的
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
    
    
        printf("create listend fd failed");
        return -1;
    }
    // 2. listenfd绑定一个固定的地址
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
		// 2.1 将listenfd绑定协议簇, 端口9999, 这样listenfd就会一直监听9999端口的信息
    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))){
    
    
        printf("bind failed");
        return -2;
    }
		// 3. 新建一个监听队列
		listen(listenfd, 10);
		while(1){
    
    
				// 4. 新建连接客户端的fd
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int clientfd = accept(listenfd, (struct sockaddr*)&client, &len);
	      // 创建线程去处理客户端fd发来的请求
        pthread_t threadid;
        pthread_create(&threadid, NULL, routine, &clientfd);
    }

6. IO multiplexing

IO multiplexing is select/epoll often mentioned in network programming, also known as event driven IO (event driven IO), the function is to detect whether there is an event in IO

  • Advantages: A single process can handle the IO of multiple network connections at the same time (polling)
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<pthread.h>
#include<unistd.h>

#define BUFFER_LENGTH 128

int main(){
    
    
    // 1. 使用socket套接字创建fd, fd默认是阻塞的
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
    
    
        printf("create listend fd failed");
        return -1;
    }
    // 2. listenfd绑定一个固定的地址
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
		// 2.1 将listenfd绑定协议簇, 端口9999, 这样listenfd就会一直监听9999端口的信息
    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))){
    
    
        printf("bind failed");
        return -2;
    }
		listen(listenfd, 10);
		// 3. 定义读事件和写事件
		fd_set rfds, wfds, rset, wset;
		// 3.1 清空readfd集合中所有bit, 置为0
    FD_ZERO(&rfds); 
		// 3.2 将listenfd加入到fd读集合中
    FD_SET(listenfd, &rfds);
		// 一定要比实际在用的fd大
    int maxfd = listenfd;
    int ret = 0;
		unsigned char buffer[BUFFER_LENGTH] = {
    
    0};
		while(1) {
    
    
				// 3.3 把判断位 赋值给 修改位
        rset = rfds;
        wset = wfds;
        //           可读集合 可写集合
        // select其实是一个for循环
        // 返回实时的有多少个
        int nready = select(maxfd + 1, &rset, &wset, NULL, NULL);
        // 判断fd在不在rset中, rset是一个二进制位, 就看有没有这个位
        if(FD_ISSET(listenfd, &rset)){
    
    
            printf("listenfd --> \n");

            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 新增加的fd, 也加到可读的fd集合中
            int clientfd = accept(listenfd, (struct sockaddr*)&client, &len);
            FD_SET(clientfd, &rfds);
            if(clientfd > maxfd) maxfd = clientfd;
        }
				int i = 0;
        // 4. 由于fd是依次增加的, 从listenfd开始遍历, 一直到最大的fd
        for(i = listenfd + 1; i <= maxfd; i++){
    
    
						// 4.1 判断当前fd是不是在读集合
            if(FD_ISSET(i, &rset)){
    
    
								// 4.2 读取这个fd的缓冲区
                ret = recv(i, buffer, BUFFER_LENGTH, 0);
                // 4.3 如果recv的返回是0,表面客户端已经关闭了
                if(ret == 0){
    
    
                    close(i);
                    // 4.4 再把当前fd的事件clear掉
                    FD_CLR(i, &rfds);
                }else if(ret > 0){
    
    
										// 4.5 从缓冲区中读到了数据
                    printf("buffer: %s, ret: %d\n", buffer, ret);
										// 4.6 将当前fd加入到写集合中
                    FD_SET(i, &wfds);
										// FD_CLR(i, &rfds);  这行要注释吗?
                }
            }
						// 5. 如果上面的fd读取到了内容
            if(FD_ISSET(i, &wset)){
    
    
								// 5.1 发送数据到客户端
                ret = send(i, buffer, ret, 0);
                FD_CLR(i, &wfds);
                FD_SET(i, &rfds);
            }
        }
		}
}

Recommend a free open course of Zero Sound Academy. I personally think the teacher taught it well, so I would like to share it with you:

Linux, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, coroutines, DPDK and other technical content, learn now

Guess you like

Origin blog.csdn.net/weixin_44839362/article/details/128977062