Linux网络编程系列 (够吃,管饱)
1、Linux网络编程系列之网络编程基础
2、Linux网络编程系列之TCP协议编程
3、Linux网络编程系列之UDP协议编程
4、Linux网络编程系列之UDP广播
5、Linux网络编程系列之UDP组播
6、Linux网络编程系列之服务器编程——阻塞IO模型
7、Linux网络编程系列之服务器编程——非阻塞IO模型
8、Linux网络编程系列之服务器编程——多路复用模型
9、Linux网络编程系列之服务器编程——信号驱动模型
一、什么是阻塞IO模型
服务器阻塞IO模型是一种IO模型,其中服务器在处理INGRESS和EGRESS网络数据流时阻塞,并且无法处理其他连接请求。当服务器接收到一个连接请求时,它将读取数据直到读取完成,然后进行处理并发送响应,这期间,该连接请求将会阻塞其他连接请求的处理。
二、特性
1、阻塞IO调用,当服务器没有数据可读或者没有缓冲区可写入时,服务器将被阻塞。
2、每个连接都需要创建一个新的线程或进程来处理,因此服务器开销和资源占用会很大。
3、每个连接需要消耗一定的内存资源来存储相关信息,如连接状态和IO缓冲区等。
4、无法处理大量并发连接请求,可能会导致连接的延迟和响应时间过长。
5、对于大数据传输,阻塞IO可能会一次性读取所有数据并占用大量内存,从而导致服务器崩溃或性能下降。
6、由于阻塞IO模型无法实时处理并发连接请求,因此无法适应高并发、高吞吐量的应用场景。
三、使用场景
1、小规模的网络应用,如小型HTTP服务器、FTP服务器等。
2、对并发连接数量要求不高的网络应用,如内部OA系统、ERP系统等。
3、数据传输量较小的网络应用,如即时聊天应用、邮件系统等。
4、对实时响应要求不高的网络应用,如批处理任务、数据备份等。
5、对于资源受限的系统,如嵌入式系统、移动设备等,阻塞IO模型也可以用于网络应用。
四、模型框架(通信流程)
1、建立套接字。使用socket()
2、设置端口复用。使用setsockopt()
3、绑定自己的IP地址和端口号。使用bind()
4、设置监听。使用listen()
5、循环阻塞等待,接收新的客户端连接。使用accept()
6、为连接上来的客户端创建一条线程。使用pthread_create()
7、关闭套接字。使用close()
五、相关函数API接口
这里TCP服务通信的API在本系列其他博客中已经有大量讲解,这里省略,忘记了朋友可以点击本文开头上面对应的链接查看。
六、案例
使用阻塞IO模型结合TCP协议,完成服务器通信演示,使用nc命令模拟客户端。
// 阻塞IO模型TCP服务器的案例 #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <pthread.h> #define MAX_LISTEN 50 // 最大能处理的连接数 #define SERVER_IP "192.168.64.128" // 记得改为自己IP #define SERVER_PORT 20000 // 不能超过65535,也不要低于1000,防止端口误用 struct ClientInfo { int fd; uint16_t port; char ip[20]; }; // 线程的例程函数 void *recv_routinue(void *arg) { int ret = 0; char recv_msg[128] = {0}; struct ClientInfo client = *((struct ClientInfo*)arg); pthread_detach(pthread_self()); // 设置强制分离 while(1) { memset(recv_msg, 0, sizeof(recv_msg)); ret = recv(client.fd, recv_msg, sizeof(recv_msg), 0); if(ret == 0) { printf("[%s:%d] disconnect\n", client.ip, client.port); pthread_exit(0); } else if(ret > 0) { printf("[%s:%d] send data: %s\n", client.ip, client.port, recv_msg); } } } int main(int argc, char *argv[]) { // 1、建立套接字,指定IPV4网络地址,TCP协议 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) { perror("socket fail"); return -1; } // 2、设置端口复用(推荐) int optval = 1; // 这里设置为端口复用,所以随便写一个值 int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); if(ret == -1) { perror("setsockopt fail"); close(sockfd); return -1; } // 3、绑定自己的IP地址和端口号(不可以省略) struct sockaddr_in server_addr = {0}; socklen_t addr_len = sizeof(struct sockaddr); server_addr.sin_family = AF_INET; // 指定协议为IPV4地址协议 server_addr.sin_port = htons(SERVER_PORT); // 端口号 server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // IP地址 ret = bind(sockfd, (struct sockaddr*)&server_addr, addr_len); if(ret == -1) { perror("bind fail"); close(sockfd); return -1; } // 4、设置监听 ret = listen(sockfd, MAX_LISTEN); if(ret == -1) { perror("listen fail"); close(sockfd); return -1; } uint16_t port = 0; char ip[20] = {0}; struct sockaddr_in client_addr = {0}; printf("wait client connect...\n"); while(1) { // 5、接受连接请求 int new_client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len); if(new_client_fd == -1) { perror("accept fail"); close(sockfd); return -1; } else { memset(ip, 0, sizeof(ip)); strcpy(ip, inet_ntoa(client_addr.sin_addr)); port = ntohs(client_addr.sin_port); printf("[%s:%d] connect\n", ip, port); struct ClientInfo client = {0}; client.fd = new_client_fd; client.port = port; strcpy(client.ip, ip); // 创建线程,一个客户端对应一个线程 pthread_t tid; pthread_create(&tid, NULL, recv_routinue, (void*)&client); } } // 7、关闭套接字 close(sockfd); return 0; }
七、总结
阻塞IO模型适用于系统资源有限,小规模通信的场景,无法适应高并发、高吞吐量的应用场景。通常做法是一个客户端对应一个线程,这样极度消耗系统资源,因此也无法处理大规模的客户端连接请求。