第15章 高并发服务器编程(1)_非阻塞I/O模型
1. 高性能I/O
(1)通常,recv函数没有数据可用时会阻塞等待。同样,当socket发送缓冲区没有足够多空间来发送消息时,函数send会阻塞。
(2)当socket在非阻塞模式下,这些函数不会阻塞,如果发送/接收缓冲区没有数据时,调用会失败并设置errno为EWOULDBLOCK或EAGAIN。
(3)可以调用fcntl函数实现非阻塞式I/O或调用select实现I/O多路复用以提高使用I/O而出现的效率问题。
2. 非阻塞I/O模型: fcntl函数
【编程实验】echo服务器(非阻塞IO方式实现)
(1)主线程创建一个服务于所有客户端的子线程。
(2)主线程调用accept与客户端建立连接,并将新的socket设置为非阻塞方式。然后将这个新的socket放入数组中。
(3)利用一个子线程遍历(轮询)数组中各个socket,并调用read/write(非阻塞式)与客户端进行通信。
//vector_fd.h
- #ifndef __VECTOR_H__
- #define __VECTOR_H__
- #include <pthread.h>
- //用于存放sock的动态数组(线程安全!)
- typedef struct{
- int *fd;
- int counter; //元素个数
- int max_counter;//最多存数个数,会动态增长
- pthread_mutex_t mutex;
- }VectorFD, *PVectorFD;
- //动态数组相关的操作函数
- extern VectorFD* create_vector_fd(void);
- extern void destroy_vector_fd(VectorFD* vfd);
- extern int get_fd(VectorFD* vfd, int index);
- extern void remove_fd(VectorFD* vfd, int fd);
- extern void add_fd(VectorFD* vfd, int fd);
- #endif
//vector_fd.c //动态数组操作函数
- #include "vector_fd.h"
- #include <memory.h>
- #include <malloc.h>
- #include <assert.h>
- //查找指定fd在数组中的索引值
- static int indexof(VectorFD* vfd, int fd)
- {
- int ret = -;
- int i=;
- for(; i<vfd->counter; i++){
- if(vfd->fd[i] == fd){
- ret = i;
- break;
- }
- }
- return ret;
- }
- //数组空间的动态增长
- static void encapacity(VectorFD* vfd)
- {
- if(vfd->counter >=vfd->max_counter){
- int* fds = (int*)calloc(vfd->counter + , sizeof(int));
- assert(fds != NULL);
- memcpy(fds, vfd->fd, sizeof(int) * vfd->counter);
- free(vfd->fd);
- vfd->fd = fds;
- vfd->max_counter += ;
- }
- }
- //动态数组相关的操作
- VectorFD* create_vector_fd(void)
- {
- VectorFD* vfd = (VectorFD*)calloc(, sizeof(VectorFD));
- assert(vfd != NULL);
- //分配存放fd的数组空间
- vfd->fd = (int*)calloc(, sizeof(int));
- assert(vfd->fd != NULL);
- vfd->counter = ;
- vfd->max_counter = ;
- //对互斥锁进行初始化
- pthread_mutex_init(&vfd->mutex, NULL);
- return vfd;
- }
- void destroy_vector_fd(VectorFD* vfd)
- {
- assert(vfd != NULL);
- //销毁互斥锁
- pthread_mutex_destroy(&vfd->mutex);
- free(vfd->fd);
- free(vfd);
- }
- int get_fd(VectorFD* vfd, int index)
- {
- int ret = ;
- assert(vfd != NULL);
- pthread_mutex_lock(&vfd->mutex);
- if(( <= index) && (index < vfd->counter)){
- ret = vfd->fd[index];
- }
- pthread_mutex_unlock(&vfd->mutex);
- return ret;
- }
- void remove_fd(VectorFD* vfd, int fd)
- {
- assert(vfd != NULL);
- pthread_mutex_lock(&vfd->mutex);
- int index = indexof(vfd, fd);
- if(index >= ){
- int i = index;
- for(; i<vfd->counter-; i++){
- vfd->fd[i] = vfd->fd[i+];
- }
- vfd->counter--;
- }
- pthread_mutex_unlock(&vfd->mutex);
- }
- void add_fd(VectorFD* vfd, int fd)
- {
- assert(vfd != NULL);
- encapacity(vfd);
- vfd->fd[vfd->counter++] = fd;
- }
//echo_tcp_server_fcntl.c
- #include <netdb.h>
- #include <sys/socket.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <memory.h>
- #include <time.h>
- #include <pthread.h>
- #include <signal.h>
- #include <errno.h>
- #include "vector_fd.h"
- #include <fcntl.h>
- /*基于非阻塞IO的高并发服务器编程
- 测试:telnet 127.0.0.1 xxxx
- http://xxx.xxx.xxx.xxx:端口号
- 注意:演示时可关闭服务器的防火墙,防火墙口被过滤
- #service iptables status 查看防火墙
- #service iptables stop 关闭防火墙
- */
- VectorFD* vfd;
- int sockfd;
- int bStop = ;
- void sig_handler(int signo)
- {
- if(signo == SIGINT){
- bStop = ;
- printf("server close\n");
- exit();
- }
- }
- void out_addr(struct sockaddr_in* clientAddr)
- {
- char ip[];
- memset(ip, , sizeof(ip));
- int port = ntohs(clientAddr->sin_port);
- inet_ntop(AF_INET, &clientAddr->sin_addr.s_addr, ip, sizeof(ip));
- printf("%s(%d) connnected!\n", ip, port);
- }
- /*服务程序
- * fd对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
- */
- void do_service(int fd)
- {
- /*服务端和客户端进行读写操作(双向通信)*/
- char buff[];
- memset(buff, , sizeof(buff));
- size_t size = read(fd, buff, sizeof(buff));
- //读取客户端发送过来的消息
- //由于采用非阻塞方式,若读不到数据直接返回了,直接服务于下一个客户端
- //因此不需要判断size小于0的情况。
- if(size == ){ //客户端己关闭连接
- char info[] = "client close\n";
- write(STDOUT_FILENO, info, sizeof(info));
- //将fd从动态数组中删除
- remove_fd(vfd, fd);
- close(fd);
- }else if(size > ){
- write(STDOUT_FILENO, buff, sizeof(buff));//显示客户端发送的消息
- //写回客户端(回显功能)
- if(write(fd, buff, sizeof(buff)) != size){
- if(errno == EPIPE){
- //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号
- //并将errno设置为EPIPE
- perror("write error");
- remove_fd(vfd, fd);
- close(fd);
- }
- }
- }
- }
- //线程函数
- void* th_fn(void* arg)
- {
- int i= ;
- //轮询动态数组中的socket描述符
- while(!bStop){
- for(i=; i<vfd->counter; i++){
- do_service(get_fd(vfd, i));
- }
- }
- return (void*);
- }
- int main(int argc, char* argv[])
- {
- if(argc < ){
- printf("usage: %s port\n", argv[]);
- exit();
- }
- //按ctrl-c时中止服务端程序
- if(signal(SIGINT, sig_handler) == SIG_ERR){
- perror("signal sigint error");
- exit();
- }
- /*步骤1:创建socket(套接字)
- *注:socket创建在内核中,是一个结构体
- *AF_INET:IPv4
- *SOCK_STREAM:tcp协议
- */
- sockfd = socket(AF_INET, SOCK_STREAM, );
- /*步骤2:将sock和地址(包括ip、port)进行绑定*/
- struct sockaddr_in servAddr; //使用专用地址结构体
- memset(&servAddr, , sizeof(servAddr));
- //往地址中填入ip、port和Internet地址族类型
- servAddr.sin_family = AF_INET;//IPv4
- servAddr.sin_port = htons(atoi(argv[])); //port
- servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP
- if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < ){
- perror("bind error");
- exit();
- }
- /*步骤3:调用listen函数启动监听
- * 通知系统去接受来自客户端的连接请求
- */
- if(listen(sockfd, ) < ){ //队列中最多允许10个连接请求
- perror("listen error");
- exit();
- }
- //创建放置套接字描述符的动态数组
- vfd = create_vector_fd();
- //设置线程的分离属性
- pthread_t th;
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- //启动子线程
- int err;
- if((err = pthread_create(&th, &attr, th_fn, (void*))) != ){
- perror("pthread create error");
- exit();
- }
- pthread_attr_destroy(&attr);
- /*(1)主线程获得客户端连接,将新的socket描述符放置到动态数组中
- *(2)子线程负责遍历动态数组中socket描述符并和对应的客户端进行
- * 双向通信(采用非阻塞方式读写)
- */
- struct sockaddr_in clientAddr;
- socklen_t len = sizeof(clientAddr);
- while(!bStop){
- /*步骤4:调用accept函数,从请求队列中获取一个连接
- * 并返回新的socket描述符
- * */
- int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &len);
- if(fd < ){
- perror("accept error");
- continue;
- }
- //将读写修改为非阻塞方式
- int val;
- fcntl(fd, F_GETFL, &val);
- val |= O_NONBLOCK; //非阻塞式
- fcntl(fd, F_SETFL, val);
- //输出客户端信息
- out_addr(&clientAddr);
- //将返回的新socket描述符加入到动态数组中
- add_fd(vfd, fd);
- }
- close(sockfd);
- destroy_vector_fd(vfd);
- return ;
- }
- /*输出结果
- * [root@localhost 15.AdvNet]# gcc -o bin/echo_tcp_client src/echo_tcp_client.c
- * [root@localhost 15.AdvNet]# bin/echo_tcp_server_fcntl 8888
- * 127.0.0.1(40694) connnected!
- * abcdefaadeafcdafacdaegadeageadfacadegadaddeagdafddeagd^Cserver close
- */
//echo_tcp_client.c
- #include <netdb.h>
- #include <sys/socket.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <memory.h>
- int main(int argc, char* argv[])
- {
- if(argc < ){
- printf("usage: %s ip port\n", argv[]);
- exit();
- }
- /*步骤1: 创建socket(套接字)*/
- int sockfd = socket(AF_INET, SOCK_STREAM, );
- if(sockfd < ){
- perror("socket error");
- }
- //往servAddr中填入ip、port和地址族类型
- struct sockaddr_in servAddr;
- memset(&servAddr, , sizeof(servAddr));
- servAddr.sin_family = AF_INET;
- servAddr.sin_port = htons(atoi(argv[]));
- //将ip地址转换成网络字节序后填入servAdd中
- inet_pton(AF_INET, argv[], &servAddr.sin_addr.s_addr);
- /*步骤2: 客户端调用connect函数连接到服务器端*/
- if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < ){
- perror("connect error");
- exit();
- }
- /*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/
- char buff[];
- size_t size;
- char* prompt = ">";
- while(){
- memset(buff, , sizeof(buff));
- write(STDOUT_FILENO, prompt, );
- size = read(STDIN_FILENO, buff, sizeof(buff));
- if(size < ) continue;
- buff[size-] = '\0';
- //将键盘输入的内容发送到服务端
- if(write(sockfd, buff, sizeof(buff)) < ){
- perror("write error");
- continue;
- }else{
- memset(buff, , sizeof(buff));
- //读取来自服务端的消息
- if(read(sockfd, buff, sizeof(buff)) < ){
- perror("read error");
- continue;
- }else{
- printf("%s\n", buff);
- }
- }
- }
- /*关闭套接字*/
- close(sockfd);
- }
- /*输出结果
- *[root@localhost 15.AdvNet]# bin/echo_tcp_client 127.0.0.1 8888
- >abcdef
- abcdef
- >aade
- aade
- >afcdaf
- afcdaf
- >acdaeg
- acdaeg
- >^C
- */
第15章 高并发服务器编程(1)_非阻塞I/O模型的更多相关文章
- 第15章 高并发服务器编程(2)_I/O多路复用
3. I/O多路复用:select函数 3.1 I/O多路复用简介 (1)通信领域的时分多路复用 (2)I/O多路复用(I/O multiplexing) ①同一线程,通过“拨开关”方式,来同时处理多 ...
- SpringCloud、Nginx高并发核心编程 【2020年11月新书 】
文章太长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典极品 : 三大本< Java 高并发 三部曲 > 面试 + 大厂 + 涨薪必备 疯狂创客圈 经 ...
- Linux + C + Epoll实现高并发服务器(线程池 + 数据库连接池)(转)
转自:http://blog.csdn.net/wuyuxing24/article/details/48758927 一, 背景 先说下我要实现的功能,server端一直在linux平台下面跑,当客 ...
- Linux下高并发网络编程
Linux下高并发网络编程 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时, 最高的并发数量都要受到系统对用户单一进程同时可打 ...
- JAVA NIO non-blocking模式实现高并发服务器
JAVA NIO non-blocking模式实现高并发服务器 分类: JAVA NIO2014-04-14 11:12 1912人阅读 评论(0) 收藏 举报 目录(?)[+] Java自1.4以后 ...
- PHP写的异步高并发服务器,基于libevent
PHP写的异步高并发服务器,基于libevent 博客分类: PHP PHPFPSocketLinuxQQ 本文章于2013年11月修改. swoole已使用C重写作为PHP扩展来运行.项目地址:h ...
- JAVA NIO non-blocking模式实现高并发服务器(转)
原文链接:JAVA NIO non-blocking模式实现高并发服务器 Java自1.4以后,加入了新IO特性,NIO. 号称new IO. NIO带来了non-blocking特性. 这篇文章主要 ...
- 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
1. [推荐]高并发服务器建议调小 TCP 协议的 time_wait 超时时间. 说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 ...
- 为一个支持GPRS的硬件设备搭建一台高并发服务器用什么开发比较容易?
高并发服务器开发,硬件socket发送数据至服务器,服务器对数据进行判断,需要实现心跳以保持长连接. 同时还要接收另外一台服务器的消支付成功消息,接收到消息后控制硬件执行操作. 查了一些资料,java ...
随机推荐
- tomcat部署和启动2
catalina run 启动服务器后,按下CTRL+C,停止服务器,选择"y",退回到正常命令行. catalina stop
- Kaggle新手入门之路(完结)
学完了Coursera上Andrew Ng的Machine Learning后,迫不及待地想去参加一场Kaggle的比赛,却发现从理论到实践的转变实在是太困难了,在此记录学习过程. 一:安装Anaco ...
- P1002 谁拿了最多奖学金
P1002 谁拿了最多奖学金 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 NOIP2005复赛提高组第一题 描述 某校的惯例是在每学期的期末考试之后发放奖 ...
- asp.net mvc 快捷下拉列表
各种表单中可能经常会遇到使用各种下拉列表的地方, 有些数据是从数据库来的, 有些则是固定数值, 为了方便, 快速的构造一个可以保持状态的下拉列表, 就出现了下面的方法 2分钟构思的代码, 比较粗糙, ...
- magento -- 如何在magento中进行产品的批量上传
花费了好多时间,阅读了magento官方论坛上几乎所有的批量上传产品的相关帖子,分析了大量相关magento代码,终于可以完全实现指产品批量上传的功能,免除网速慢,在页面之间跳来跳去,以及重复输入数据 ...
- python3 线性同余发生器 ( random 随机数生成器 ) 伪随机数产生周期的一些探究
import random x=[str(random.randint(0, 5)) for i in range(10)] x_str=''.join(x) y=[str(random.randin ...
- 【机器学习算法】Boostrapping算法
参考 1.AdaBoost从原理到实现: 完
- Activity的四大启动模式
在自己清单中的Activity里配置这四大启动之一. stander 标准模式 先进后出 singletop 会检查栈顶如果有,那么就复用,不会重新开启. singletask ...
- 大家一起做训练 第一场 G CD
题目来源:UVA 624 题目的意思就是:我现在需要从 t 张CD中拿出一部分来,尽可能的凑出接近 N 这么久的音乐,但是不能超过 N. CD不超过20张,每张长度不超过 N ,不能重复选. 一个很简 ...
- (1)集合 ---遍历map集合
Map接口 实现Map接口的类用来存储键(key)-值(value) 对.Map 接口的实现类有HashMap和TreeMap等.Map类中存储的键-值对通过键来标识,所以键值不能重复. Ha ...