• 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命令模拟客户端。

    1. // 阻塞IO模型TCP服务器的案例
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #define MAX_LISTEN 50 // 最大能处理的连接数
    14. #define SERVER_IP "192.168.64.128" // 记得改为自己IP
    15. #define SERVER_PORT 20000 // 不能超过65535,也不要低于1000,防止端口误用
    16. struct ClientInfo
    17. {
    18. int fd;
    19. uint16_t port;
    20. char ip[20];
    21. };
    22. // 线程的例程函数
    23. void *recv_routinue(void *arg)
    24. {
    25. int ret = 0;
    26. char recv_msg[128] = {0};
    27. struct ClientInfo client = *((struct ClientInfo*)arg);
    28. pthread_detach(pthread_self()); // 设置强制分离
    29. while(1)
    30. {
    31. memset(recv_msg, 0, sizeof(recv_msg));
    32. ret = recv(client.fd, recv_msg, sizeof(recv_msg), 0);
    33. if(ret == 0)
    34. {
    35. printf("[%s:%d] disconnect\n", client.ip, client.port);
    36. pthread_exit(0);
    37. }
    38. else if(ret > 0)
    39. {
    40. printf("[%s:%d] send data: %s\n", client.ip, client.port, recv_msg);
    41. }
    42. }
    43. }
    44. int main(int argc, char *argv[])
    45. {
    46. // 1、建立套接字,指定IPV4网络地址,TCP协议
    47. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    48. if(sockfd == -1)
    49. {
    50. perror("socket fail");
    51. return -1;
    52. }
    53. // 2、设置端口复用(推荐)
    54. int optval = 1; // 这里设置为端口复用,所以随便写一个值
    55. int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    56. if(ret == -1)
    57. {
    58. perror("setsockopt fail");
    59. close(sockfd);
    60. return -1;
    61. }
    62. // 3、绑定自己的IP地址和端口号(不可以省略)
    63. struct sockaddr_in server_addr = {0};
    64. socklen_t addr_len = sizeof(struct sockaddr);
    65. server_addr.sin_family = AF_INET; // 指定协议为IPV4地址协议
    66. server_addr.sin_port = htons(SERVER_PORT); // 端口号
    67. server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // IP地址
    68. ret = bind(sockfd, (struct sockaddr*)&server_addr, addr_len);
    69. if(ret == -1)
    70. {
    71. perror("bind fail");
    72. close(sockfd);
    73. return -1;
    74. }
    75. // 4、设置监听
    76. ret = listen(sockfd, MAX_LISTEN);
    77. if(ret == -1)
    78. {
    79. perror("listen fail");
    80. close(sockfd);
    81. return -1;
    82. }
    83. uint16_t port = 0;
    84. char ip[20] = {0};
    85. struct sockaddr_in client_addr = {0};
    86. printf("wait client connect...\n");
    87. while(1)
    88. {
    89. // 5、接受连接请求
    90. int new_client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);
    91. if(new_client_fd == -1)
    92. {
    93. perror("accept fail");
    94. close(sockfd);
    95. return -1;
    96. }
    97. else
    98. {
    99. memset(ip, 0, sizeof(ip));
    100. strcpy(ip, inet_ntoa(client_addr.sin_addr));
    101. port = ntohs(client_addr.sin_port);
    102. printf("[%s:%d] connect\n", ip, port);
    103. struct ClientInfo client = {0};
    104. client.fd = new_client_fd;
    105. client.port = port;
    106. strcpy(client.ip, ip);
    107. // 创建线程,一个客户端对应一个线程
    108. pthread_t tid;
    109. pthread_create(&tid, NULL, recv_routinue, (void*)&client);
    110. }
    111. }
    112. // 7、关闭套接字
    113. close(sockfd);
    114. return 0;
    115. }

    七、总结

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

  • 相关阅读:
    JavaFX介绍
    MyBatis 后端对数据库进行操作
    《Linux高性能服务器编程》--TCP/IP协议族
    conan入门(二十八):解决conan 1.60.0下 arch64-linux-gnu交叉编译openssl/3.1.2报错问题
    AI产品经理还不会数据挖掘❓看完这篇就够了
    使用c++实现通讯录管理系统
    关于Ubuntu18.04安装后没有gcc、make、网卡驱动的问题总结以及解决办法
    JavaSE——继承和多态详解
    架构思考(七)
    8. SQL中Order by和Group by子句的使用简介
  • 原文地址:https://blog.csdn.net/AABond/article/details/133419145