Linux网络编程系列之服务器编程——阻塞IO模型

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模型适用于系统资源有限,小规模通信的场景,无法适应高并发、高吞吐量的应用场景。通常做法是一个客户端对应一个线程,这样极度消耗系统资源,因此也无法处理大规模的客户端连接请求。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值