一、循环服务器模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h> //*******//
#include <netinet/in.h> //*******//
#include <arpa/inet.h> //*******//
int sockfd;
void my_exit(int sig)
{
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
printf("shutdown socket done\n");
exit(0);
}
void handle(int sig) // SIGPIPE的信号处理函数————以观察是否产生了SIGPIPE信号
{
if (sig == SIGPIPE)
{
printf("SIGPIPE is going\n");
}
}
int main(int argc, char **argv)
{
signal(SIGINT, my_exit);
signal(SIGPIPE, handle);
signal(SIGPIPE, SIG_IGN);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socked is error");
exit(-1);
}
printf("socket success\n");
int i;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
struct sockaddr_in sockaddr_in1;
sockaddr_in1.sin_family = AF_INET;
sockaddr_in1.sin_port = htons(4443);
// 正确的做法是使用htons函数将主机字节序转换为网络字节序————htons而非htonl因为端口号是16位
sockaddr_in1.sin_addr.s_addr = inet_addr("192.168.106.128"); //*****//
if (bind(sockfd, (struct sockaddr *)&sockaddr_in1, sizeof(sockaddr_in1)) < 0)
{
perror("bind error");
exit(-1);
}
printf("bind success\n");
if (listen(sockfd, 20) < 0)
{
perror("listen error");
exit(-1);
}
printf("listen success\n");
struct sockaddr_in addr2;
int len_addr2 = sizeof(addr2);
while (1)
{
int sock_fd1 = accept(sockfd, (struct sockaddr *)&addr2, &len_addr2); // 每来一个客户端的连接请求,就会生成一个描述符,只要知道这个描述符,就能通过此通信
if (sock_fd1 < 0)
{
perror("accept error");
exit(-1);
}
printf("client ip = %s ,port = %d\n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port)); // 1.inet_ntoa把ip地址转换为字符————2.把网络的转换为主机的
char buffer[1024] = {0};
int recv_t = recv(sock_fd1, buffer, sizeof(buffer) - 1, 0);
printf("recv_t : %d ", recv_t);
if (recv_t < 0)
{
perror("recv error");
exit(-1);
}
else if (recv_t == 0) // recv的返回值为零的时候,证明客户端关闭了!
{
printf("client is closed\n");
}
else
{
printf("recv :%s\n", buffer);
memset(buffer, 0, sizeof(buffer));
scanf("%s", buffer);
// int w_t = send(sock_fd1, buffer, strlen(buffer), 0);
int w_t = send(sock_fd1, buffer, strlen(buffer), MSG_NOSIGNAL); // MSG_NOSIGNAL:表示此操作不愿被SIGPIPE信号断开;或注册信号处理函数
if (w_t < 0)
{
perror("send data error");
exit(-1);
}
}
shutdown(sock_fd1, SHUT_RDWR);
}
return 0;
}
循环服务器模型:
阻塞:
同一时间只能处理一个客户端的请求(读写请求或者连接请求),accept和recv会阻塞
非阻塞:
1.accept非阻塞:
// flag标志位,置为sock_nonblock;
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
就可以将accept自动设置为非阻塞
2.recv非阻塞:
recv函数的flag位置为MSG_DONTWAIT;
int ret3 = recv(ret2, buffer, sizeof(buffer), MSG_DONTWAIT); // msg_dontwait非阻塞接收
3.read非阻塞:
fcntl设置文件描述符的flags位;
// read:
int flags = fcntl(ret2, F_GETFL);
flags = flags | O_NONBLOCK;
fcntl(ret2, F_SETFL,flags);
4.例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
int main(int argc, char **argv)
{
//*************************************************************//
// flag标志位,置为sock_nonblock;
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
//*************************************************************//
if (sockfd < 0)
{
perror("socket error");
exit(-1);
}
int j = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in addr1;
addr1.sin_family = AF_INET;
addr1.sin_addr.s_addr = inet_addr("127.0.0.1");
addr1.sin_port = htons(5555);
int ret1 = bind(sockfd, (struct sockaddr *)(&addr1), sizeof(addr1));
if (ret1 < 0)
{
perror("bind error");
exit(-1);
}
if (listen(sockfd, 20) < 0)
{
perror("listen error");
exit(-1);
}
struct sockaddr_in addr2;
int len;
memset(&addr2, 0, sizeof(addr2));
char buffer[1024] = {0};
while (1)
{
int ret2 = accept(sockfd, (struct sockaddr *)&addr2, &len);
if (ret2 < 0)
{
//-----------------------------------------------------------------//
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
//-----------------------------------------------------------------//
{
perror("accept error");
exit(-1);
}
else
{
sleep(1);
printf("accept going\n");
continue;
}
}
while (1)
{
// read:
int flags = fcntl(ret2, F_GETFD);
flags = flags | O_NONBLOCK;
fcntl(ret2, F_SETFD,flags);
memset(buffer, 0, sizeof(buffer));
//----------------------- ret2!!! ----------------------//
// int ret3 = recv(ret2, buffer, sizeof(buffer), MSG_DONTWAIT); // msg_dontwait非阻塞接收
//----------------------------------------------------------------------//
int ret3 = read(ret2, buffer, sizeof(buffer));
if (ret3 < 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
{
perror("recv error");
exit(-1);
}
else
{
continue;
}
}
else if (ret3 == 0)
{
printf("client closed\n");
break;
}
else
{
printf("receive message is : %s\n", buffer);
memset(buffer, 0, sizeof(buffer));
scanf("%s", buffer);
sendto(ret2, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr2, sizeof(addr2));
}
}
}
return 0;
}
二、阻塞IO+多进程
例子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void handler(int sig)
{
if(sig==SIGCHLD)
{
printf("child process is exit\n");
waitpid(-1,NULL,0);
}
}
int main(int argc, char **argv)
{
signal(SIGCHLD,handler);
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
perror("socket error");
exit(-1);
}
int j = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in addr1;
addr1.sin_addr.s_addr = inet_addr("127.0.0.1");
addr1.sin_family = AF_INET;
addr1.sin_port = htons(5555);
int ret1 = bind(socket_fd, (struct sockaddr *)&addr1, sizeof(addr1));
if (ret1 < 0)
{
perror("bind error");
exit(-1);
}
if (listen(socket_fd, 20) < 0)
{
perror("listen error");
exit(-1);
}
struct sockaddr_in addr2;
int len = sizeof(addr2);
while (1)
{
char message[1024] = {0};
memset(&addr2, 0, sizeof(addr2));
int fd1 = accept(socket_fd, (struct sockaddr *)&addr2, &len);
if (fd1 < 0)
{
perror("accept error");
exit(-1);
}
pid_t pid = fork();
if (pid == 0)
{
while (1)
{
read(fd1, message, sizeof(message));
printf("%s\n", message);
char *p = message;
for (; *p != '\0'; ++p)
{
*p = *p - 'a' + 'A';
}
write(fd1, message, strlen(message) + 1);
memset(message, 0, sizeof(message));
}
}
if (pid > 0)
{
close(fd1);
continue;
}
}
return 0;
}
waitpid 函数
是一个用于等待子进程终止并回收其资源的系统调用,它的函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
waitpid 函数用于等待指定的子进程终止,并且提供了更灵活的选项来控制等待的行为。下面是参数的含义:
pid:要等待的子进程的进程ID。可以使用以下值:
<-1:等待任何进程组ID等于pid的子进程。
-1:等待任何子进程,类似于 wait 函数。
0:等待与调用进程在同一个进程组的任何子进程。
> 0:等待指定进程ID等于pid的子进程。
status:用于存储子进程终止状态的整数指针。status 中将包含子进程的退出状态信息。
options:用于指定等待选项的整数。常用的选项包括:
WNOHANG:如果没有终止的子进程可用,则立即返回,不阻塞。
WUNTRACED:也等待已经停止但尚未报告的子进程。
WCONTINUED:等待已继续但尚未报告的子进程。
waitpid 函数之所以可以回收子进程资源,是因为它在等待子进程终止时会挂起当前进程,直到指定的子进程终止。一旦子进程终止,waitpid 会将子进程的退出状态信息(如退出码)存储在 status 参数中,然后返回子进程的进程ID。通过检查 status 中的信息,您可以确定子进程的终止状态,并根据需要采取相应的操作。
void handler(int sig)
{
if(sig==SIGCHLD)
{
printf("child process is exit\n");
waitpid(-1,NULL,0);
}
}
优缺点:
优点:
编码简单,不用考虑进程间的数据同步、服务器健壮;
缺点:
1.资源消耗大,启动一个进程消耗相对比启动一个线程消耗大得多;处理多个连接的时候,需要启动多个进程去处理
2.系统的进程数是有限制的
3.查看系统最多支持的进程数——ulimit -u命令;也可以修改
4.TCP服务器进程同时打开文件数的限制——ulimit -n命令;每一次accept就会产生一个文件描述符,数量限制在0-1024
参考:
Linux系统打开文件最大数量限制(进程打开的最大文件句柄数设置) - 废物大师兄 - 博客园 (cnblogs.com)https://www.cnblogs.com/cjsblog/p/9367043.html
三、阻塞IO+多线程
例子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
void change(char *p) // 字符大写
{
for (; *p != '\0'; ++p)
{
*p = *p - 'a' + 'A';
}
}
void *pthread_cs(void *arg)
{
int fd = (int)arg;
char message[1024] = {0};
pthread_detach(pthread_self()); // 获取当前线程的id号,然后设置为分离态(结束后可以自动回收资源)
while (1)
{
memset(message, 0, sizeof(message));
if (read(fd, message, sizeof(message)) == 0)
// 如果读取结果是0,则证明客户端已经关闭;需要主动退出线程,否则服务器主程序也会异常退出
{
printf("client cloesd\n");
pthread_exit(NULL);
}
printf("%s\n", message);
change(message);
write(fd, message, sizeof(message));
}
}
int main(int argc, char **argv)
{
pthread_t id;
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket < 0)
{
perror("socket error");
exit(-1);
}
int j = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in addr0;
addr0.sin_addr.s_addr = inet_addr("127.0.0.1");
addr0.sin_family = AF_INET;
addr0.sin_port = htons(5555);
int len_addr0 = sizeof(addr0);
int ret1 = bind(socket_fd, (struct sockaddr *)&addr0, len_addr0);
if (ret1 < 0)
{
perror("bind error");
exit(-1);
}
int ret2 = listen(socket_fd, 20);
if (ret2 < 0)
{
perror("listen error");
exit(-1);
}
while (1)
{
struct sockaddr_in addr1;
int len_addr1 = sizeof(addr1);
int fd = accept(socket_fd, (struct sockaddr *)&addr1, &len_addr1);
if (fd < 0)
{
perror("accept error");
exit(-1);
}
pthread_create(&id, NULL, pthread_cs, (void *)fd); //------------(void *)fd----------//
}
return 0;
}
注意事项:
1:
if (read(fd, message, sizeof(message)) == 0)
// 如果读取结果是0,则证明客户端已经关闭;需要主动退出线程,否则服务器主程序也会异常退出
{
printf("client cloesd\n");
pthread_exit(NULL);
}
2:
pthread_detach(pthread_self()); // 获取当前线程的id号,然后设置为分离态(结束后可以自动回收资源)
3:
//------------(void *)fd----------//这里需要传值而不是传地址(每次循环,地址上的值会变化)
pthread_create(&id, NULL, pthread_cs, (void *)fd);
优缺点:
优点:相对多进程,会节约一些资源,会更加高效一点
缺点:
1.相对于多进程,增加coding复杂度,因为需要考虑数据同步和锁保护【锁也是很浪费资源的】
2.一个进程中不能启动太多的线程——【当前进程的资源有限,线程数量受到进程栈空间的限制:线程函数压栈】
3.ulimit -s查看栈的大小,算出最大线程的个数
总结:线程是进程的优化,优先选择线程——
【解决线程数量限制——线程池】
【解决锁的开销——减小锁的颗粒度(上锁的代码越少越好)】
四、池化技术(线程池)
1.实现线程池
---------------------------------------------------------(抽空写)---------------------------------------------
2.利用线程池来实现创建多进程响应多个客户端的连接
五、I/O多路转接(复用)(单Reactor )
select
例子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <sys/select.h>
#include <errno.h>
int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (socket_fd < 0)
{
perror("socket creat error");
exit(-1);
}
int j = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in s_addr;
int len_s_addr = sizeof(s_addr);
s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(5555);
if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
{
perror("bind error");
exit(-1);
}
listen(socket_fd, 20);
return socket_fd;
}
void change(char *p) // 字符大写
{
for (; *p != '\0'; ++p)
{
*p = *p - 'a' + 'A';
}
}
int main(int argc, char **argv)
{
int n_r;
int cfd;
int sockfd;
int maxfd;
fd_set r_set;
fd_set all_set;
int client[FD_SETSIZE]; // 保存cfd——FD——SETSIZE = 1024;
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(c_addr));
socklen_t c_size = sizeof(struct sockaddr_in);
sockfd = socket_creat();
printf("socketfd is %d\n", sockfd);
for (int i = 0; i < FD_SETSIZE; i++) // 文件描述符集合全置为-1;
{
client[i] = -1;
}
FD_SET(sockfd, &all_set);
maxfd = sockfd;
char message[1024];
while (1)
{
r_set = all_set;
int rnum = select(maxfd + 1, &r_set, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &r_set))
{
c_size = sizeof(struct sockaddr_in);
printf("socketfd is %d\n", sockfd);
cfd = accept(sockfd, (struct sockaddr *)&c_addr, &c_size);
if (cfd < 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
{
perror("accept error! ");
exit(1);
}
continue;
}
printf("info client: ip = %s port = %d\n",
inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
FD_SET(cfd, &all_set);
printf("%d\n", __LINE__);
if (maxfd < cfd)
{
maxfd = cfd;
}
}
for (int i = 0; i < FD_SETSIZE; i++)
{
if (client[i] == -1)
{
client[i] = cfd;
printf("%d\n", __LINE__);
break;
}
}
if (--rnum > 0)
{
printf("%d\n", __LINE__);
continue;
}
for (int i = 0; i < FD_SETSIZE; i++)
{
if ((cfd = client[i]) != -1)
{
printf("%d\n", __LINE__);
if (FD_ISSET(cfd, &r_set))
{
printf("%d\n", __LINE__);
memset(message, 0, sizeof(message));
n_r = read(cfd, message, sizeof(message));
if (n_r < 0)
{
perror("read cfd data error! ");
FD_CLR(cfd, &all_set);
client[i] = -1;
}
if (n_r == 0)
{
printf("client is close! \n");
FD_CLR(cfd, &all_set);
client[i] = -1;
}
printf("%d\n", __LINE__);
printf("recv data:%s\n", message);
change(message);
write(cfd, message, strlen(message) + 1);
if (--rnum == 0)
{
break;
}
}
}
}
}
return 0;
}
my_select_server:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <sys/select.h>
#include <errno.h>
int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (socket_fd < 0)
{
perror("socket creat error");
exit(-1);
}
int j = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in s_addr;
int len_s_addr = sizeof(s_addr);
s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(5555);
if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
{
perror("bind error");
exit(-1);
}
listen(socket_fd, 20);
return socket_fd;
}
void change(char *p) // 字符大写
{
for (; *p != '\0'; ++p)
{
*p = *p - 'a' + 'A';
}
}
int main(int agrc, char **argv)
{
int socket_fd = socket_creat();
struct sockaddr_in c_addr;
socklen_t c_len;
memset(&c_addr, 0, sizeof(c_addr));
fd_set set_1;
fd_set set_all;
FD_SET(socket_fd, &set_all);
int maxfd = socket_fd;
int fd_ary[FD_SETSIZE];
for (int i = 0; i < FD_SETSIZE; ++i)
{
fd_ary[i] = -1;
}
char message[1024];
while (1)
{
set_1 = set_all;
int ret = select(maxfd + 1, &set_1, NULL, NULL, NULL);
if (FD_ISSET(socket_fd, &set_1))
{
int cfd = accept(socket_fd, (struct sockaddr *)&c_addr, &c_len);
if (cfd < 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
{
perror("accept error");
exit(-1);
}
printf("%d\n", __LINE__);
continue;
}
if (cfd > 0)
{
printf("client id : %s , client opt is %d\n",
inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
FD_SET(cfd, &set_all);
if (maxfd < cfd)
{
maxfd = cfd;
printf("%d\n", __LINE__);
}
for (int i = 0; i < FD_SETSIZE; ++i)
{
if (fd_ary[i] == -1)
{
fd_ary[i] = cfd;
printf("%d\n", __LINE__);
break;
}
}
}
}
for (int i = 0; i < FD_SETSIZE; ++i)
{
if (FD_ISSET(fd_ary[i], &set_1))
{
printf("%d\n", __LINE__);
memset(message, 0, sizeof(message));
int ret_r = read(fd_ary[i], message, sizeof(message));
if (ret_r == 0)
{
printf("client is closed\n");
fd_ary[i] = -1;
FD_CLR(fd_ary[i], &set_all);
}
if (ret_r < 0)
{
perror("read error");
exit(-1);
}
printf("%s\n", message);
change(message);
write(fd_ary[i], message, sizeof(message));
}
}
}
return 0;
}
select优缺点:
缺点:
1.select用到的文件描述符集合,包含的数量有限(宏定义:FD_SETSIZE为1024,虽然可以修改);
2.select是一种轮询全盘扫描(时间复杂度是O(n) ),只知道多少个描述符发生变化——遍历文件描述符集合,看哪个发生变化,再采取操作
优点:
1.可以跨平台,几乎所有平台
2.可以监听所有的文件描述符:普通文件、套接字和设备文件等等
poll
基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制(使用链表的方式存储fd)
函数原型
#include cpoll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd fds[POLL_SIZE];
fds[0].fd = sock_fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
for (int i = 1; i < POLL_SIZE; ++i)
{
fds[i].fd = -1;
fds[i].events = POLLIN;
fds[i].revents = 0;
}
revents字段表示实际发生的事件,可能包括POLLIN(可读事件)、POLLOUT(可写事件)
功能
监听集合有没有动静,如果没有动静就阻塞
如果有动静就成功返回,返回值为集合中有动静的fd的数里。
参数
2) nfds :数组的元素个数。
3)timeout:超时时间,如果写的是
-1:不设置超时,如果集合没有动静就一直阻塞下去,直到pol函数被信号中断〈唤醒)或者集合有动静为止
非-1值:比如3000 ( 3000徵妙),表示将超时时间设置为3秒
也就是说poll超时时间的单位时微妙s
返回值
-1:说明函数调失败,errno被设置。
如果是被信号中断从而导致出错返回-1时.errno被设置为EINTR,·如果不想o中断,要么重启poll的调用:要么忽略或者屏蔽下这些信号。
0:超时时间到,而且没有文件描述符有动静。
>0:返回有响应的文件描述符的数量。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#define POLL_SIZE 1024
int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (socket_fd < 0)
{
perror("socket creat error");
exit(-1);
}
int j = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in s_addr;
int len_s_addr = sizeof(s_addr);
s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(5555);
if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
{
perror("bind error");
exit(-1);
}
listen(socket_fd, 20);
return socket_fd;
}
void change(char *p) // 字符大写
{
for (; *p != '\0'; ++p)
{
*p = *p - 'a' + 'A';
}
}
int main(int argc, char **argv)
{
int sock_fd = socket_creat();
struct sockaddr_in c_addr;
socklen_t len_c_addr = sizeof(c_addr);
memset(&c_addr, 0, sizeof(c_addr));
struct pollfd fds[POLL_SIZE];
fds[0].fd = sock_fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
for (int i = 1; i < POLL_SIZE; ++i)
{
fds[i].fd = -1;
fds[i].events = POLLIN;
fds[i].revents = 0;
}
char message[1024] = "0";
while (1)
{
int ret_p = poll(fds, POLL_SIZE, -1);
if (ret_p < 0)
{
perror("poll error");
exit(-1);
}
if (ret_p > 0)
{
if (fds[0].events == fds[0].revents)
{
fds[0].revents = 0;
int c_fd = accept(sock_fd, (struct sockaddr *)&c_addr, &len_c_addr);
if (c_fd < 0)
{
perror("accept error");
exit(-1);
}
if (c_fd > 0)
{
for (int i = 1; i < POLL_SIZE; ++i)
{
if (fds[i].fd == -1)
{
fds[i].fd = c_fd;
break;//--------------- 注意---------------//
}
}
printf("cilent ip is %s , cilent port is %d\n",
inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
}
}
for (int i = 1; i < POLL_SIZE; ++i)
{
if (fds[i].events == fds[i].revents)
{
int ret_r = read(fds[i].fd, message, sizeof(message));
if (ret_r < 0)
{
perror("read error");
fds[i].fd = -1;
}
if (ret_r == 0)
{
printf("client is closed\n");
fds[i].fd = -1;
}
if (ret_r > 0)
{
printf("receive message is %s\n", message);
change(message);
write(fds[i].fd, message, sizeof(message));
memset(message, 0, sizeof(message));
}
fds[i].revents = 0;
}
}
}
}
return 0;
}
epoll—重点
使用步骤
1.epoll_create
size:
epoll上能关注的最大描述符数量。(真正用的时候随便设一个就行,epoll在容里不够的时候会做自动扩展)
返回值:返回红黑树的描述符epollfd ;出错时返回-1,错误代码置在ernno
epoll在内部是一个红黑制结构。文件描述符默认0—2被终端占用,每次有一个新的文件描述符就创建一个树节点。epoll认设置的是树上最多的节点数是2000:但是如果敌里超过2000:则大小会被自动扩展。
2.epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
3.epoll_wait
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/epoll.h>
#define POLL_SIZE 1024
int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (socket_fd < 0)
{
perror("socket creat error");
exit(-1);
}
int j = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
struct sockaddr_in s_addr;
int len_s_addr = sizeof(s_addr);
s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(5555);
if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
{
perror("bind error");
exit(-1);
}
listen(socket_fd, 20);
return socket_fd;
}
void change(char *p) // 字符大写
{
for (; *p != '\0'; ++p)
{
*p = *p - 'a' + 'A';
}
}
int main(int argc, char **argv)
{
int sock_fd = socket_creat();
struct sockaddr_in c_addr;
socklen_t len_c_addr = sizeof(c_addr);
memset(&c_addr, 0, sizeof(c_addr));
int epfd = epoll_create(2000); // 初始化红黑书,返回值是红黑树的标识符 ; 红黑书大小——暂定输入参数为2000;
//int epfd = epoll_create1(0);//也是一样的效果
if (epfd < 0)
{
perror("epoll_creat error");
exit(-1);
}
struct epoll_event event_0; // 先定义一个结构体,存放第一个sock_fd————以便于后面处理“新连接”的请求;
event_0.events = EPOLLIN; // 监听的是可读——也就是有新连接了
event_0.data.fd = sock_fd; // 存放sock_fd,以便于后面遍历的时候,通过event.data.fd找到sock_fd这个,而非处理新连接所新加的event
struct epoll_event event_a[2000]; // 定义一个总的,而且空的struct epoll_event数组,作为epoll_wait的传出参数,用以保存所有变化了的文件描述符
memset(event_a, 0, sizeof(event_a)); // 清空它
int ret1 = epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &event_0); // 把第一个含有sock_fd的结构体加到红黑里;
if (ret1 < 0)
{
perror("epoll add error");
exit(-1);
}
char message[1024] = {0};
while (1)
{
int ret = epoll_wait(epfd, event_a, 2000, -1); // 监听,-1代表阻塞,死等;
for (int i = 0; i < 2000; ++i) // 遍历这个“包含所有变化的”的struct epoll_event数组,以处理每一个请求
{
if (event_a[i].data.fd == sock_fd) // 这个对应的是有新连接的请求
{
int c_fd = accept(sock_fd, (struct sockaddr *)&c_addr, &len_c_addr);
if (c_fd < 0)
{
perror("accept error");
exit(-1);
}
if (c_fd > 0)
{
printf("cilent ip is %s , cilent port is %d\n",
inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
event_0.data.fd = c_fd; // 有新连接,就要把新的结构体(结点),挂到红黑树里 👇
epoll_ctl(epfd, EPOLL_CTL_ADD, c_fd, &event_0);
}
}
else // 处理的是已经连接,有收发消息的请求
{
int fd = event_a[i].data.fd;
int ret_r = read(fd, message, sizeof(message));
if (ret_r < 0)
{
perror("read error");
close(fd);// 需要关闭这个文件描述符;
}
if (ret_r == 0)
{
close(fd);// 需要关闭这个文件描述符;
printf("client is closed\n");
int ret2 = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
//删除,第四个参数置为NULL;
}
if (ret_r > 0)
{
printf("receive message is %s\n", message);
change(message);
write(fd, message, sizeof(message));
memset(message, 0, sizeof(message));
}
}
if (--ret <= 0)
{
break;
}
}
}
return 0;
}
注意:上图中虽然套接字已经是非阻塞的了,但是可以不需要处理accept的errno,因为,如果不是新客户端连接的话,是不会进入accept语句里的
支持操作:
水平触发LT
只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回——(数据没读完就会一直发出请求给服务器,占用资源,降低效率)
边缘触发ET
1.ET模式(边缘触发)只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩未读尽的数据不会导致epoll_wait返回
2.边缘触发需要一次性的把缓冲区的数据读完为止,也就是一直读,直到读到EGAIN(EGAIN说明缓冲区已经空了)为止
3.边缘触发需要设置文件句柄为非阻塞
// event_0.events = EPOLLIN | EPOLLET;//设置为边缘触发模式
// int flags=fcntl(c_fd,F_GETFL);
// flags=flags|O_NONBLOCK;
// fcntl(cfd,F_SETFL,flags);
4.epoll为什么要有EPOLLET触发模式?
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高,epoll工作在ET模式的时候。必须使用非阻塞接口;以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死
优缺点
select内部使用数组实现,poll是链表。他们需要做内核区到用户区的转换,还需要做数据拷贝,因此效率低。
epoll不需要做内核区到用户区的转换,因为数据存在共享内存中。epoll维护的树在共享内存中,内核区和用户区去操作共享内存;因此不需要区域转换,也不需要拷贝操作