第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 ...
随机推荐
- iOS-UIImage变为NSData并进行压缩
<iOS>UIImage变为NSData并进行压缩 //sdk中提供了方法可以直接调用 UIImage *img = [UIImage imageNamed:@"some.p ...
- magento关联产品
<?php $related= $_product->getRelatedProducts(); foreach($related as $_item): $_item = $_item- ...
- css hover遮罩层
马上月底了, 这个月忙于工作和生活, 没有很好的写一篇博客, 有点忧伤. 为了弥补, 就写点简单的. 最近项目有个需求, 就是鼠标移入的时候显示一个层, 移除的时候这个层消失. 当然层是可以点击进行额 ...
- 解决安装vmware-tools出现的“The path "" is not a valid path to the 3.2.0-4-amd64 kernel headers”问题
在用虚拟机安装使用64位Crunchbang(一种Debian GNU/Linux 的linux)的过程中出现很多小问题.其中vmware-tools安装就是第一个问题. 在使用终端安装vmware- ...
- 《DSP using MATLAB》Problem 4.1
用到的z变换的计算公式: 代码: %% ------------------------------------------------------------------------ %% Outp ...
- dgraph 基本查询语法 二
这部分主要是mutation 操作,(就是增加.删除操作) 参考git 项目 https://github.com/rongfengliang/dgraph-docker-compose-deploy ...
- PowerCollections
Wintellect 的Power collections 库 BigList<String> str = new BigList<String>(); str.Add(&qu ...
- Oracle sql之条件语句 循环语句
一 IF..THENIF condition THEN statements 1; statements 2; ....END IF; 二 IF..THEN...ELSEIF condit ...
- nyoj 幸运三角形
幸运三角形 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 话说有这么一个图形,只有两种符号组成(‘+’或者‘-’),图形的最上层有n个符号,往下个数依次减一,形成倒 ...
- Javascript 的严格模式 use strict
严格模式 "use strict"; x = 5; 这时浏览器会报错.