使用Select方式创建TCP服务器
一些基本概念
select实现IO多路复用的特点
一个进程最多只能监听1024个文件描述符(千级别)
select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗cpu资源)
select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(注意:拷贝是非常耗时的)
注意
select 一旦返回将没有产生事件的文件描述符从表中清空。下次检测需要重新添加。表中的下标是文件描述符的值,添加的时候是根据对应的位置添加的,检测个数是添加进表中最大的文件描述符+1
重点函数
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/*
功能:用于检测是哪个或者哪些文件描述符产生的事件
参数:
nfds:用于检测最大文件描述符个数
readfds:读事件集合
writefds:写事件集合
exceptfds:异常事件类型集合
timeout:超时检测
(如果不做超时检测:传值NULL)
返回值:
<0出错
>0有事件产生
==0表示超时时间已经到达
struct timeval{
long tv_sec;//seconds
long tv_usec;//microseconds
};
*/
void FD_CLR(int fd,fd_set *set);
int FD_ISSET(int fd,fd_set *set);
void FD_SETZ(int fd,fd_set *set);
void FD_ZERO(fd_set *set);
select.h
//select.h
#ifndef __SELECT_H
#define __SELECT_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#endif
select.c
//select.c
#include "001select_demo.h"
int main(int argc, char const *argv[])
{
int sockfd, acceptfd;
if (argc != 2)
{
printf("please input %s <port>\n", argv[0]);
return -1;
}
//创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
//填充结构体
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(clientaddr);
//绑定
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err");
return 0;
}
//监听
if (listen(sockfd, 5) < 0)
{
perror("listen err");
return -1;
}
//select实现监听sockfd,实现多个客户端链接服务器
//创建表
fd_set readfds, tempfds;
//清空表
FD_ZERO(&readfds);
//添加关心的文件描述符:0 sockfd
FD_SET(0, &readfds);
FD_SET(sockfd, &readfds);
int maxfd = sockfd;
int ret;
char buf[128];
int recvbyte;
while (1)
{
tempfds = readfds;
//调用select循环检测是否有事件发生
ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
if (ret < 0)
{
perror("select err");
return -1;
}
for (int i = 0; i < maxfd + 1; i++)
{
if (FD_ISSET(i, &tempfds))
{
if (i == 0)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
printf("key:%s\n", buf);
//所有客户端发送通知
for (int j = sockfd + 1; j < maxfd + 1; j++)
{
if (FD_ISSET(j, &readfds))
{
send(j, buf, sizeof(buf), 0);
}
}
}
else if (i == sockfd)
{
if ((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len)) < 0)
{
perror("accept err");
return -1;
}
printf("client:ip=%s,port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
//用于通信的文件描述符添加到原表中
FD_SET(acceptfd, &readfds);
if (maxfd < acceptfd)
{
maxfd = acceptfd;
}
}
else
{
memset(buf, 0, sizeof(buf));
recvbyte = recv(i, buf, sizeof(buf), 0);
if (recvbyte < 0)
{
perror("recv err");
return -1;
}
else if (recvbyte == 0)
{
printf("client exit.\n");
FD_CLR(i, &readfds);
close(i);
}
else
{
printf("%d:%s\n", i, buf);
}
}
}
}
}
close(sockfd);
return 0;
}
使用Poll方式创建TCP服务器
一些基本概念
poll实现IO多路复用的特点
优化文件描述符个数限制(根据poll函数第一个函数参数个数来定,如果监听的事件为1个,则结构体数组元素个数为1,如果想监听100个,那么这个结构体数据的元素个数就位100,由程序员自行设定
poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低
poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可
重点函数
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
功能:和 select 实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列
参数:*fds:关心的文件描述符数组 struct pollfd fds[N];
nfds:文件描述符个数
timeout:超时检测(毫秒级 -1阻塞)
struct pollfd{
int fd;//检测的文件描述符
short events;//检测事件
short revents;//调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员的值就可以确定是否产生事件
}
事件:
POLLIN:读事件//监听消息一般都是读消息,终端的输入也是读消息,accept请求客户端也是读消息
POLLOUT:写事件
POLLERR:异常事件
返回值:
<0出错
>0表示有事件产生
==0表示超时时间已到
*/
poll.h
#ifndef __POLL_H
#define __POLL_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/time.h>
#include <unistd.h>
#endif
poll.c
#include "002poll_demo.h"
int main(int argc, const char *argv[])
{
int sockfd, acceptfd;
if (argc != 2)
{
printf("please input %s <port>\n", argv[0]);
return -1;
}
//创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
//填充结构体
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(clientaddr);
//绑定
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err");
return -1;
}
//监听
if (listen(sockfd, 5) < 0)
{
perror("listen err");
return -1;
}
//poll实现监听sockfd,实现多个客户端链接服务器
//创建表
struct pollfd fds[20] = {0};
//将关心的文件描述符添加到表中
fds[0].fd = 0;
fds[0].events = POLLIN; //读事件
fds[1].fd = sockfd;
fds[1].events = POLLIN;
int num = 2;
int ret;
char buf[128];
int recvbyte;
while (1)
{
//调用poll循环检测是否有事件产生
ret = poll(fds, num, -1); //阻塞等待事件产生返回
if (ret < 0)
{
perror("poll err");
return -1;
}
for (int i = 0; i < num; i++)
{
if (fds[i].revents == POLLIN)
{
if (fds[i].fd == 0)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
printf("key:%s\n", buf);
for (int j = 2; j < num; j++)
{
send(fds[j].fd, buf, sizeof(buf), 0);
}
}
else if (fds[i].fd == sockfd)
{
if ((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len)) < 0)
{
perror("accept err");
return -1;
}
printf("client:ip=%s,port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
fds[num].fd = acceptfd;
fds[num].events = POLLIN;
num++;
}
else
{
memset(buf, 0, sizeof(buf));
recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0);
if (recvbyte < 0)
{
perror("recv err");
return -1;
}
else if (recvbyte == 0)
{
printf("client exit.\n");
close(fds[i].fd);
for (int j = i + 1; j < num; j++)
{
fds[j - 1] = fds[j];
}
num--;
i--;
}
else
{
printf("%d:%s\n", fds[i].fd, buf);
}
}
}
}
}
return 0;
}