第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 ...
随机推荐
- 腾讯云nginx配置PHP
腾讯云nginx配置文件 #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log ...
- PAT 天梯赛 是否完全二叉搜索树 (30分)(二叉搜索树 数组)
将一系列给定数字顺序插入一个初始为空的二叉搜索树(定义为左子树键值大,右子树键值小),你需要判断最后的树是否一棵完全二叉树,并且给出其层序遍历的结果. 输入格式: 输入第一行给出一个不超过20的正整数 ...
- web测试小结
今年5月份开始接触web测试,经过大半年的测试及学习,简单总结下 测试过程: 1.需求理解 2.测试策略.方案.用例编写及评审 3.测试环境搭建 4.测试执行 5.bug提单.问题跟踪 6.回归测试 ...
- HAL_RTC_MspInit Msp指代什么?
/********************************************************************************* * HAL_RTC_MspInit ...
- UNIMRCP 代码走读
基于UNIMRCP1.5.0的代码走读 与 填坑记录 1. server启动配置加载 入口:unimrcp_server.c static apt_bool_t unimrcp_server_load ...
- GPA
原题: GPA Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- Android Studio安装、配置教程全 - 安卓开发环境的配置手册
Android Studio的安装.配置 本文提供全流程,中文翻译.Chinar坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) 一 Downloa ...
- jboss 5.1 启动问题解决
在安装好后启动时可能遇到这样的情况: ERROR [AbstractKernelController] Error installing to Instantiated: name=Attachmen ...
- 微信小程序实现多选分享
微信小程序拉取好友列表后,默认只能选一个分享,现在在分享回调onShareAppMessage里加上这段代码,拉取好友列表时,右上角会出现多选按钮,这样就解决了微信小程序安卓下只能单选分享的问题. / ...
- nginx+keepalived实现负载均衡nginx的高可用
准备四台服务器 两台做主备,另外两台做访问 192.168.1.120 master 192.168.1.121 backup 192.168.1.122 nginx 192.168.1.123 ng ...