linux socket学习(二)
2015-12-24 11:23
495 查看
原文转自
http://www.cnblogs.com/lzjsky/archive/2013/03/18/2965983.html
四.使用select
select这个系统调用,是一种多路复用IO方案,可以同时对多个文件描述符进行监控,从而知道哪些文件描述符可读,可写或者出错,不过select方法是阻塞的,可以设定超时时间。 select使用的步骤如下:
1.创建一个fd_set变量(fd_set实为包含了一个整数数组的结构体),用来存放所有的待检查的文件描述符
2.清空fd_set变量,并将需要检查的所有文件描述符加入fd_set
3.调用select。若返回-1,则说明出错;返回0,则说明超时,返回正数,则为发生状态变化的文件描述符的个数
4.若select返回大于0,则依次查看哪些文件描述符变的可读,并对它们进行处理
5.返回步骤2,开始新一轮的检测
若上面的聊天程序使用select进行改写,则是下面这样的
服务器端
[cpp] view
plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main (int argc, const char * argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1) {
perror("listen error");
return 1;
}
//fd_set
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
while (1) {
FD_ZERO(&server_fd_set);
//标准输入
FD_SET(STDIN_FILENO, &server_fd_set);
if (max_fd < STDIN_FILENO) {
max_fd = STDIN_FILENO;
}
//服务器端socket
FD_SET(server_sock_fd, &server_fd_set);
if (max_fd < server_sock_fd) {
max_fd = server_sock_fd;
}
//客户端连接
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
FD_SET(client_fds[i], &server_fd_set);
if (max_fd < client_fds[i]) {
max_fd = client_fds[i];
}
}
}
int ret = select(max_fd+1, &server_fd_set, NULL, NULL, &tv);
if (ret < 0) {
perror("select 出错\n");
continue;
}else if(ret == 0){
printf("select 超时\n");
continue;
}else{
//ret为未状态发生变化的文件描述符的个数
if (FD_ISSET(STDIN_FILENO, &server_fd_set)) {
//标准输入
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入 ".quit" 则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0) {
exit(0);
}
for (int i=0; i<CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}
if (FD_ISSET(server_sock_fd, &server_fd_set)) {
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
if (client_socket_fd > 0) {
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_socket_fd;
break;
}
}
if (index >= 0) {
printf("新客户端(%d)加入成功 %s:%d \n",index,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_socket_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}
}
}
for (int i = 0; i <CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
if (FD_ISSET(client_fds[i], &server_fd_set)) {
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(client_fds[i],recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(%d):%s\n",i,recv_msg);
}else if(byte_num < 0){
printf("从客户端(%d)接受消息出错.\n",i);
}else{
FD_CLR(client_fds[i], &server_fd_set);
client_fds[i] = 0;
printf("客户端(%d)退出了\n",i);
}
}
}
}
}
}
return 0;
}
客户端
[cpp] view
plaincopyprint?
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024
int main (int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
char recv_msg[BUFFER_SIZE];
char input_msg[BUFFER_SIZE];
if (connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {
fd_set client_fd_set;
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
while (1) {
FD_ZERO(&client_fd_set);
FD_SET(STDIN_FILENO, &client_fd_set);
FD_SET(server_sock_fd, &client_fd_set);
int ret = select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
if (ret < 0 ) {
printf("select 出错!\n");
continue;
}else if(ret ==0){
printf("select 超时!\n");
continue;
}else{
if (FD_ISSET(STDIN_FILENO, &client_fd_set)) {
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
if (send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1) {
perror("发送消息出错!\n");
}
}
if (FD_ISSET(server_sock_fd, &client_fd_set)) {
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(server_sock_fd,recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("服务器:%s\n",recv_msg);
}else if(byte_num < 0){
printf("接受消息出错!\n");
}else{
printf("服务器端退出!\n");
exit(0);
}
}
}
}
}
return 0;
}
当然select也有其局限性。当fd_set中的文件描述符较少,或者大都数文件描述符都比较活跃的时候,select的效率还是不错的。Mac系统中已经定义了fd_set 最大可以容纳的文件描述符的个数为1024
[cpp] view
plaincopyprint?
//sys/_structs.h
#define __DARWIN_FD_SETSIZE 1024
/////////////////////////////////////////////
//Kernel.framework sys/select.h
#define FD_SETSIZE __DARWIN_FD_SETSIZE
每一次select 调用的时候,都涉及到user space和kernel space的内存拷贝,且会对fd_set中的所有文件描述符进行遍历,如果所有的文件描述符均不满足,且没有超时,则当前进程便开始睡眠,直到超时或者有文件描述符状态发生变化。当文件描述符数量较大的时候,将耗费大量的CPU时间。所以后来有新的方案出现了,如windows2000引入的IOCP,Linux Kernel 2.6中成熟的epoll,FreeBSD4.x引入的kqueue。
五.使用kqueue
Mac是基于BSD的内核,所使用的是kqueue(kernel event notification mechanism,详细内容可以Mac中 man 2 kqueue),kqueue比select先进的地方就在于使用事件触发的机制,且其调用无需每次对所有的文件描述符进行遍历,返回的时候只返回需要处理的事件,而不像select中需要自己去一个个通过FD_ISSET检查。
kqueue默认的触发方式是level 水平触发,可以通过设置event的flag为EV_CLEAR 使得这个事件变为边沿触发,可能epoll的触发方式无法细化到单个event,需要查证。
kqueue中涉及两个系统调用,kqueue()和kevent()
kqueue() 创建kernel级别的事件队列,并返回队列的文件描述符
kevent() 往事件队列中加入订阅事件,或者返回相关的事件数组
kqueue使用的流程一般如下:
创建kqueue
创建struct kevent变量(注意这里的kevent是结构体类型名),可以通过EV_SET这个宏提供的快捷方式进行创建
通过kevent系统调用将创建好的kevent结构体变量加入到kqueue队列中,完成对指定文件描述符的事件的订阅
通过kevent系统调用获取满足条件的事件队列,并对每一个事件进行处理
[cpp] view
plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
struct kevent events[10];//CONCURRENT_MAX + 2
int main (int argc, const char * argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1) {
perror("listen error");
return 1;
}
struct timespec timeout = {10,0};
//kqueue
int kq = kqueue();
if (kq == -1) {
perror("创建kqueue出错!\n");
exit(1);
}
struct kevent event_change;
EV_SET(&event_change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
EV_SET(&event_change, server_sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
while (1) {
int ret = kevent(kq, NULL, 0, events, 10, &timeout);
if (ret < 0) {
printf("kevent 出错!\n");
continue;
}else if(ret == 0){
printf("kenvent 超时!\n");
continue;
}else{
//ret > 0 返回事件放在events中
for (int i = 0; i < ret; i++) {
struct kevent current_event = events[i];
//kevent中的ident就是文件描述符
if (current_event.ident == STDIN_FILENO) {
//标准输入
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入 ".quit" 则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0) {
exit(0);
}
for (int i=0; i<CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}else if(current_event.ident == server_sock_fd){
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
if (client_socket_fd > 0) {
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_socket_fd;
break;
}
}
if (index >= 0) {
EV_SET(&event_change, client_socket_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
printf("新客户端(fd = %d)加入成功 %s:%d \n",client_socket_fd,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_socket_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}
}
}else{
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv((int)current_event.ident,recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(fd = %d):%s\n",(int)current_event.ident,recv_msg);
}else if(byte_num < 0){
printf("从客户端(fd = %d)接受消息出错.\n",(int)current_event.ident);
}else{
EV_SET(&event_change, current_event.ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
close((int)current_event.ident);
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == (int)current_event.ident) {
client_fds[i] = 0;
break;
}
}
printf("客户端(fd = %d)退出了\n",(int)current_event.ident);
}
}
}
}
}
return 0;
}
其实kqueue的应用场景非常的广阔,可以监控文件系统中文件的变化(对文件变化的事件可以粒度非常的细,具体可以查看kqueue的手册),监控系统进程的生命周期。GCD的事件处理便是建立在kqueue之上的。
FROM: http://blog.csdn.net/zengraoli/article/details/19612169
http://www.cnblogs.com/lzjsky/archive/2013/03/18/2965983.html
四.使用select
select这个系统调用,是一种多路复用IO方案,可以同时对多个文件描述符进行监控,从而知道哪些文件描述符可读,可写或者出错,不过select方法是阻塞的,可以设定超时时间。 select使用的步骤如下:
1.创建一个fd_set变量(fd_set实为包含了一个整数数组的结构体),用来存放所有的待检查的文件描述符
2.清空fd_set变量,并将需要检查的所有文件描述符加入fd_set
3.调用select。若返回-1,则说明出错;返回0,则说明超时,返回正数,则为发生状态变化的文件描述符的个数
4.若select返回大于0,则依次查看哪些文件描述符变的可读,并对它们进行处理
5.返回步骤2,开始新一轮的检测
若上面的聊天程序使用select进行改写,则是下面这样的
服务器端
[cpp] view
plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main (int argc, const char * argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1) {
perror("listen error");
return 1;
}
//fd_set
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
while (1) {
FD_ZERO(&server_fd_set);
//标准输入
FD_SET(STDIN_FILENO, &server_fd_set);
if (max_fd < STDIN_FILENO) {
max_fd = STDIN_FILENO;
}
//服务器端socket
FD_SET(server_sock_fd, &server_fd_set);
if (max_fd < server_sock_fd) {
max_fd = server_sock_fd;
}
//客户端连接
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
FD_SET(client_fds[i], &server_fd_set);
if (max_fd < client_fds[i]) {
max_fd = client_fds[i];
}
}
}
int ret = select(max_fd+1, &server_fd_set, NULL, NULL, &tv);
if (ret < 0) {
perror("select 出错\n");
continue;
}else if(ret == 0){
printf("select 超时\n");
continue;
}else{
//ret为未状态发生变化的文件描述符的个数
if (FD_ISSET(STDIN_FILENO, &server_fd_set)) {
//标准输入
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入 ".quit" 则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0) {
exit(0);
}
for (int i=0; i<CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}
if (FD_ISSET(server_sock_fd, &server_fd_set)) {
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
if (client_socket_fd > 0) {
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_socket_fd;
break;
}
}
if (index >= 0) {
printf("新客户端(%d)加入成功 %s:%d \n",index,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_socket_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}
}
}
for (int i = 0; i <CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
if (FD_ISSET(client_fds[i], &server_fd_set)) {
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(client_fds[i],recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(%d):%s\n",i,recv_msg);
}else if(byte_num < 0){
printf("从客户端(%d)接受消息出错.\n",i);
}else{
FD_CLR(client_fds[i], &server_fd_set);
client_fds[i] = 0;
printf("客户端(%d)退出了\n",i);
}
}
}
}
}
}
return 0;
}
客户端
[cpp] view
plaincopyprint?
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024
int main (int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
char recv_msg[BUFFER_SIZE];
char input_msg[BUFFER_SIZE];
if (connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {
fd_set client_fd_set;
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
while (1) {
FD_ZERO(&client_fd_set);
FD_SET(STDIN_FILENO, &client_fd_set);
FD_SET(server_sock_fd, &client_fd_set);
int ret = select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
if (ret < 0 ) {
printf("select 出错!\n");
continue;
}else if(ret ==0){
printf("select 超时!\n");
continue;
}else{
if (FD_ISSET(STDIN_FILENO, &client_fd_set)) {
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
if (send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1) {
perror("发送消息出错!\n");
}
}
if (FD_ISSET(server_sock_fd, &client_fd_set)) {
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(server_sock_fd,recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("服务器:%s\n",recv_msg);
}else if(byte_num < 0){
printf("接受消息出错!\n");
}else{
printf("服务器端退出!\n");
exit(0);
}
}
}
}
}
return 0;
}
当然select也有其局限性。当fd_set中的文件描述符较少,或者大都数文件描述符都比较活跃的时候,select的效率还是不错的。Mac系统中已经定义了fd_set 最大可以容纳的文件描述符的个数为1024
[cpp] view
plaincopyprint?
//sys/_structs.h
#define __DARWIN_FD_SETSIZE 1024
/////////////////////////////////////////////
//Kernel.framework sys/select.h
#define FD_SETSIZE __DARWIN_FD_SETSIZE
每一次select 调用的时候,都涉及到user space和kernel space的内存拷贝,且会对fd_set中的所有文件描述符进行遍历,如果所有的文件描述符均不满足,且没有超时,则当前进程便开始睡眠,直到超时或者有文件描述符状态发生变化。当文件描述符数量较大的时候,将耗费大量的CPU时间。所以后来有新的方案出现了,如windows2000引入的IOCP,Linux Kernel 2.6中成熟的epoll,FreeBSD4.x引入的kqueue。
五.使用kqueue
Mac是基于BSD的内核,所使用的是kqueue(kernel event notification mechanism,详细内容可以Mac中 man 2 kqueue),kqueue比select先进的地方就在于使用事件触发的机制,且其调用无需每次对所有的文件描述符进行遍历,返回的时候只返回需要处理的事件,而不像select中需要自己去一个个通过FD_ISSET检查。
kqueue默认的触发方式是level 水平触发,可以通过设置event的flag为EV_CLEAR 使得这个事件变为边沿触发,可能epoll的触发方式无法细化到单个event,需要查证。
kqueue中涉及两个系统调用,kqueue()和kevent()
kqueue() 创建kernel级别的事件队列,并返回队列的文件描述符
kevent() 往事件队列中加入订阅事件,或者返回相关的事件数组
kqueue使用的流程一般如下:
创建kqueue
创建struct kevent变量(注意这里的kevent是结构体类型名),可以通过EV_SET这个宏提供的快捷方式进行创建
通过kevent系统调用将创建好的kevent结构体变量加入到kqueue队列中,完成对指定文件描述符的事件的订阅
通过kevent系统调用获取满足条件的事件队列,并对每一个事件进行处理
[cpp] view
plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
struct kevent events[10];//CONCURRENT_MAX + 2
int main (int argc, const char * argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1) {
perror("listen error");
return 1;
}
struct timespec timeout = {10,0};
//kqueue
int kq = kqueue();
if (kq == -1) {
perror("创建kqueue出错!\n");
exit(1);
}
struct kevent event_change;
EV_SET(&event_change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
EV_SET(&event_change, server_sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
while (1) {
int ret = kevent(kq, NULL, 0, events, 10, &timeout);
if (ret < 0) {
printf("kevent 出错!\n");
continue;
}else if(ret == 0){
printf("kenvent 超时!\n");
continue;
}else{
//ret > 0 返回事件放在events中
for (int i = 0; i < ret; i++) {
struct kevent current_event = events[i];
//kevent中的ident就是文件描述符
if (current_event.ident == STDIN_FILENO) {
//标准输入
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入 ".quit" 则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0) {
exit(0);
}
for (int i=0; i<CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}else if(current_event.ident == server_sock_fd){
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
if (client_socket_fd > 0) {
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_socket_fd;
break;
}
}
if (index >= 0) {
EV_SET(&event_change, client_socket_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
printf("新客户端(fd = %d)加入成功 %s:%d \n",client_socket_fd,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_socket_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}
}
}else{
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv((int)current_event.ident,recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(fd = %d):%s\n",(int)current_event.ident,recv_msg);
}else if(byte_num < 0){
printf("从客户端(fd = %d)接受消息出错.\n",(int)current_event.ident);
}else{
EV_SET(&event_change, current_event.ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
close((int)current_event.ident);
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == (int)current_event.ident) {
client_fds[i] = 0;
break;
}
}
printf("客户端(fd = %d)退出了\n",(int)current_event.ident);
}
}
}
}
}
return 0;
}
其实kqueue的应用场景非常的广阔,可以监控文件系统中文件的变化(对文件变化的事件可以粒度非常的细,具体可以查看kqueue的手册),监控系统进程的生命周期。GCD的事件处理便是建立在kqueue之上的。
FROM: http://blog.csdn.net/zengraoli/article/details/19612169
相关文章推荐
- Linux重启MYSQL的命令
- Linux远程连接windows桌面和screen后台虚拟终端进程管理
- linux socket学习(一)
- Linux下Socket编程
- linux_添加定时任务,每5min清理下某个文件夹下的文件
- linux的socket系列教程
- How to get multi-touch working(Linux and Andriod)
- 使用putty软件在Windows下远程控制Linux-并互传文件
- linux设置环境变量
- CentOS7.0上安装mysql重置root密码
- Centos下安装zmq库及pyzmq
- CentOS6.5-YUM安装最新MySQL5.7.10
- centos安装composer
- linux常用操作命令
- 个人杂记-Linux桌面快捷方式-查看图片命令
- 个人杂记-Linux操作快捷键命令-vim中的问题
- Linux下用C编写WebSocet服务以响应HTML5的WebSocket请求
- Linux信号(signal) 机制分析
- linux复习(二)
- linux系统中查找命令grep命令详解