一,IO复用几种方法的比较
select, poll, epoll都可以实现套接字I/O复用,但select这个函数是有缺陷的,主要体现在两个方面:
- 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024,这是在<sys/select.h>中的一个常量,因此 Select 模型的最大并发数就被相应限制了,但是对于大多数应用程序而言,这个数是够用的,而且有可能还是太大的,多数应用程序只使用3~10个描述符。而如今的网络服务器小小的都有几万的连接,虽然可以使用多线程多进程(也就有N*1024个)。但是这样处理起来既不方面,性能又低。
- 效率问题, select 每次调用后都会线性扫描全部的 fdset 集合,这样效率就会呈现线性下降。
- poll 模型
基本上效率和 select 是相同的, select 缺点的 1和 2 它都没有改掉。
二,select的一对一聊天程序:
1,服务器端代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#define SERVER_PORT 7777
#define BUF_LEN 256
int setupSocket()
{
int ret = 0;
//1,创建socket
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock < 0)
{
perror("socket");
return -1;
}
//2,绑定
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
//设置套接字可以复用,在关闭后,重新启动不用等待。
int flags=1;
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
{
perror("bind");
ret = -1;
goto sock_error;
}
//3,监听
if( listen(server_sock, 10) < 0)
{
perror("listen");
ret = -1;
goto sock_error;
}
ret = server_sock;
goto sock_ok;
sock_error:
close (server_sock);
sock_ok:
return ret;
}
int main()
{
int ret = 0, server_sock = 0, clisock = 0, maxid = 0, fd = 0, number = 0;
char buf[BUF_LEN] = {0};
struct sockaddr_in client_addr;
//socklent_t 就是一个无符号整型 unsigned int
socklen_t addrlen = sizeof(struct sockaddr_in);
//1,初始化socket
server_sock = setupSocket();
if(server_sock < 0)
{
perror("setupSocket");
return -1;
}
//(1)定义描述符集合
fd_set readfds, testfds;
struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0;
//(2)填充要关注的事件,包括,描述符,事件
//清空fd_set集合
FD_ZERO(&readfds);
//向fd_set集合中添加一个套接字
FD_SET(server_sock, &readfds);
FD_SET(STDIN_FILENO, &readfds);
maxid = server_sock + 1;
while(1)
{
testfds = readfds;
ret = select(maxid, &testfds, NULL, NULL, &tv);
//printf("select return: %d, timeout.tv_sec: %lu\n", ++number, (unsigned long)tv.tv_sec);
if(ret < 0)
{
perror("select");
continue;
}
else if(ret == 0)
{
tv.tv_sec = 3;
tv.tv_usec = 0;
//printf("timeout...\n");
continue;
}
//
for(fd = 0; fd < maxid; fd++)
{
//判断该描述符是否还存在于fd_set集合中,若存在,则表明该描述字发生了读/写事件
if(!FD_ISSET(fd, &testfds))
{
continue;
}
if(fd == server_sock)
{
//2,接受客户的连接请求
addrlen = sizeof(struct sockaddr_in);
if((clisock = accept(server_sock, (struct sockaddr *)&client_addr, &addrlen)) < 0)
{
perror("accept");
break;
}
printf("connect form : %s, port : %u\n",
(char *)inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
FD_SET(clisock, &readfds);
maxid = (maxid > clisock ? maxid : clisock + 1);
}
else if(fd == STDIN_FILENO)
{
bzero(buf, BUF_LEN);
if(fgets(buf, BUF_LEN, stdin) == NULL)
{
perror("fgets");
}
if(send(clisock, buf, strlen(buf), 0) < 0)
{
perror("send");
break;
}
}
else if(fd == clisock)
{
bzero(buf, BUF_LEN);
//read(fd, buf, BUF_LEN);
ret = recv(fd, buf, BUF_LEN, 0);
if(ret < 0)
{
perror("recv");
}
else if(ret == 0)
{
printf("client close\n");
close(fd);
FD_CLR(fd, &readfds);
}
else
{
printf("recv : %s\n", buf);
}
}
}
}
error_sock:
close(server_sock);
//4,关闭
return ret;
}
2,客户端代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#define SERVER_PORT 7777
#define BUF_LEN 256
int main()
{
int ret = 0, maxid = 0, i = 0;
char buf[BUF_LEN] = {0};
//1,创建socket
int clisock = socket(AF_INET, SOCK_STREAM, 0);
if(clisock < 0)
{
perror("socket");
return -1;
}
printf("socket ok.\n");
//2,连接到服务器
struct sockaddr_in server_addr;
memset(&server_addr, 0, 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");
if((ret = connect(clisock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))) < 0 )
{
perror("connect");
goto error_0;
}
printf("connect ok.\n");
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
FD_SET(clisock, &readfds);
maxid = clisock + 1;
while(1)
{
tempfds = readfds;
ret = select(maxid, &tempfds, NULL , NULL, NULL);
if(ret < 0)
{
perror("select");
}
for(i = 0; i < maxid; i++)
{
if(!FD_ISSET(i, &tempfds))
{
continue;
}
if(i == STDIN_FILENO)
{
memset(buf, 0, BUF_LEN);
scanf("%s", buf);
if((ret = send(clisock, buf, strlen(buf), 0)) <= 0)
{
perror("send");
goto error_0;
}
}
else
{
memset(buf, 0, BUF_LEN);
if((ret = recv(clisock, buf, BUF_LEN, 0)) <= 0)
{
perror("recv");
goto error_0;
}
printf("client recv : %s\n", buf);
}
}
}
error_0:
//4,关闭连接
close(clisock);
printf("client closed \n");
return 0;
}
三,epoll实现的一对一聊天程序:
1,服务器端代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#define PORT 10234
#define LISTEN_Q 5
#define BUF_SIZE 256
#define MAX_EVENTS 500
//服务器端的初始化,不需要客户端参与
int setup_listen_sock()
{
int sockfd, len;
struct sockaddr_in address;
//1,创建TCP套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1) {
perror("socket");
exit(1);
}
//2,填充要绑定的服务器地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
len = sizeof(address);
//设置可以复用地址
int on = 1;
if(setsockopt(sockfd,SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1){
perror("setsockopt");
exit(-1);
}
//3,绑定地址
if(bind(sockfd, (struct sockaddr *)&address, len) == -1) {
perror("bind");
exit(1);
}
//4,监听客户端连接
if(listen(sockfd, LISTEN_Q) == -1) {
perror("listen");
exit(1);
}
return sockfd;
}
int main()
{
int listen_sockfd, client_sockfd = 0, i = 0, ret = 0, client_len;
struct sockaddr_in client_address;
char rwbuf[BUF_SIZE] = {0};
//服务器端初始化,返回服务器端的监听套接字
listen_sockfd = setup_listen_sock();
//(1)定义epoll相关的数据结构
struct epoll_event eventArray[MAX_EVENTS];
struct epoll_event event_sock;
struct epoll_event event_stdin;
//(2)调用epoll_create,创建epoll描述符
int epollfd = epoll_create(MAX_EVENTS);
//(3)填充要关注的信息,包括套接字描述符和事件
event_sock.events = EPOLLIN;
event_sock.data.fd = listen_sockfd;//把listen_sockfd fd封装进events里面
event_stdin.events = EPOLLIN;
event_stdin.data.fd = STDIN_FILENO;
//(4)epoll_ctl设置属性,注册事件
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sockfd, &event_sock) < 0 )
{
printf("epoll 加入失败 fd:%d\n",listen_sockfd);
exit(-1);
}
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &event_stdin) < 0)
{
printf("epoll 加入失败 fd:%d\n",listen_sockfd);
exit(-1);
}
while(1)
{
printf("server waiting\n");
//(5)等待事件发生
ret = epoll_wait(epollfd, eventArray, MAX_EVENTS, -1);
if(ret < 0)
{
perror("epoll_wait");
exit(1);
}
//(4)获取哪个套接字上发生了事件
//select返回后会把未发生事件的描述符从fd_set集合中移除
for(i = 0; i < ret; i++)
{
//错误输出
if((eventArray[i].events & EPOLLERR) ||
(eventArray[i].events & EPOLLHUP) ||
!(eventArray[i].events & EPOLLIN))
{
perror("epoll error");
close(eventArray[i].data.fd);
exit(-1);
}
else if(eventArray[i].data.fd == listen_sockfd)
{
// receive client connect request
client_len = sizeof(client_address);
client_sockfd = accept(listen_sockfd, (struct sockaddr *)&client_address, &client_len);
//把客户端连接注册到监听中
struct epoll_event event_cli;
event_cli.data.fd = client_sockfd;
event_cli.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sockfd, &event_cli);
printf("adding client on fd %d\n", client_sockfd);
}
else if(STDIN_FILENO == eventArray[i].data.fd)
{
read(STDIN_FILENO, rwbuf, BUF_SIZE);
if(client_sockfd > 0)
{
send(client_sockfd, rwbuf, strlen(rwbuf), 0);
printf("send client_sockfd %d: %s\n",client_sockfd, rwbuf);
}
}
else
{
int nread = 0;
ioctl(eventArray[i].data.fd, FIONREAD, &nread);
//得到缓冲区里有多少字节要被读取,然后将字节数放入nread里面
if(nread == 0)
{
// client disconnect
close(eventArray[i].data.fd);
epoll_ctl(epollfd, EPOLL_CTL_DEL, eventArray[i].data.fd, &eventArray[i]);
printf("removing client on fd :%d\n", eventArray[i].data.fd);
}
else
{ // receive client data
read(eventArray[i].data.fd, rwbuf, BUF_SIZE);
//printf("serving client on fd %d : %d\n", fd, data);
printf("recv fd %d: %s\n", eventArray[i].data.fd, rwbuf);
}
}
}
}
close(epollfd);
}
2,epoll客户端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#define PORT 10234
#define BUF_LEN 256
#define MAX_EVENTS 500
int main()
{
int ret = 0, i = 0;
char buf[BUF_LEN] = {0};
//1,创建socket
int clisock = socket(AF_INET, SOCK_STREAM, 0);
if(clisock < 0)
{
perror("socket");
return -1;
}
printf("socket ok.\n");
//2,连接到服务器
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if((ret = connect(clisock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))) < 0 )
{
perror("connect");
goto error_0;
}
printf("connect ok.\n");
//(1)定义相关数据结构
int epollfd=0;
struct epoll_event event_stdin;
struct epoll_event event_clisock;
struct epoll_event eventArray[MAX_EVENTS];
//(2)创建epoll专用的描述符
epollfd = epoll_create(MAX_EVENTS);
//(3)填充要关注的信息,包括套接字描述和事件
event_stdin.events = EPOLLIN|EPOLLET;
event_stdin.data.fd = STDIN_FILENO;//把STDIN_FILENO封装进events里面
event_clisock.events = EPOLLIN|EPOLLET;
event_clisock.data.fd = clisock;//把clisock封装进events里面
//(4)epoll_ctl设置属性,注册事件
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &event_stdin) < 0 )
{
printf("epoll 加入失败 fd:%d\n",STDIN_FILENO);
exit(-1);
}
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clisock, &event_clisock) < 0 )
{
printf("epoll 加入失败 fd:%d\n",clisock);
exit(-1);
}
while(1)
{
//(5)等待事件发生
ret = epoll_wait(epollfd, eventArray, MAX_EVENTS, -1);
if(ret < 0)
{
perror("epoll_wait");
exit(1);
}
//(6)处理事件
for(i = 0; i < ret; i++)
{
//错误输出
if((eventArray[i].events & EPOLLERR) ||
(eventArray[i].events & EPOLLHUP) ||
!(eventArray[i].events & EPOLLIN))
{
perror("epoll error");
close(eventArray[i].data.fd);
exit(-1);
}
else if(STDIN_FILENO == eventArray[i].data.fd)
{
memset(buf, 0, BUF_LEN);
read(STDIN_FILENO, buf, BUF_LEN);
send(clisock, buf, strlen(buf), 0);
printf("send clisock %d: %s\n",clisock, buf);
}
else
{
memset(buf, 0, BUF_LEN);
ret = recv(clisock, buf, BUF_LEN, 0);
if(ret <0)
{
perror("recv");
goto error_0;
}
else if(ret == 0)
{
goto error_0;
}
printf("client recv : %s\n", buf);
}
}
}
error_0:
//4,关闭连接
close(clisock);
return 0;
}