目录
一、TCP并发模型
1.1.阻塞IO
CPU占用率低,等待资源时将任务挂起,不占用CPU资源,等到拿到资源后继续向下执行;
1.2.非阻塞IO
能够让任务不阻塞,效率低,因为没有数据时,CPU一直空转;
1.步骤
1)获取文件描述符的IO状态
2)设置文件描述符的IO状态为非阻塞
3) 将该IO状态写入文件描述符
2.函数接口
fctnl() 具体使用看下面的例子
3.实例
此处,以写入端通过管道向接收端传递信息,接收端可以接收管道信息,并且也可以从终端接收。将管道和终端的描述符均设置为非阻塞,实现,接收端一边可以接收写入端的信息,一边可以接收终的信息。(核心在read.c)
write.c
#include "../head.h"
int main(void)
{
int fd = 0;
int ret = 0;
ssize_t nsize = 0;
char buff[1024] = {0};
mkfifo("/tmp/myfifo", 0664);
fd = open("/tmp/myfifo", O_WRONLY);
if (fd == -1)
{
perror("open");
return -1;
}
while(1)
{
memset(buff, 0, sizeof(buff));
gets(buff);
nsize = write(fd, buff, strlen(buff)+1);
if (nsize == -1)
{
perror("write");
return -1;
}
}
close(fd);
return 0;
}
read.c
#include "../head.h"
int main(void)
{
int fd = 0;
int flag = 0;
ssize_t nsize = 0;
char *pnisze = NULL;
char buf[1024] = {0};
mkfifo("/tmp/myfifo", 0664);
fd = open("/tmp/myfifo", O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
//获得文件描述符的熟悉
flag = fcntl(fd, F_GETFL);
//设置文件描述符为非阻塞
flag |= O_NONBLOCK;
//设置文件描述符
fcntl(fd, F_SETFL, flag);
/*设置终端IO*/
flag = fcntl(0, F_GETFL);
flag |= O_NONBLOCK;
fcntl(0, F_SETFL, flag);
while (1)
{
memset(buf, 0, sizeof(buf));
nsize = read(fd, buf, sizeof(buf));
if (nsize > 0)
{
printf("WRITE = %s\n", buf);
}
memset(buf, 0, sizeof(buf));
pnisze = gets(buf);
if (pnisze != NULL)
{
printf("STDIN = %s\n", buf);
}
}
close(fd);
return 0;
}
1.3.异步IO
将一个文件描述符设定为异步IO,当IO有事件发生时,内核会向用户层发送SIGIO信号提醒用户层处理事件
1.步骤
1)获取文件描述符的IO状态
2)设置文件描述符的IO状态为异步
3) 将该IO状态写入文件描述符
4)处理IO信号
2.函数接口
fctnl() 具体使用看下面的例子
signal(SIGIO, handel); 接收文件描述符信号
3.实例
此处,以写入端通过管道向接收端传递信息,接收端可以接收管道信息,并且也可以从终端接收。将管道描述符设置为异步,终端信息正常接收,当管道里面有了一个信息时,发送SIGIO信号。(核心在read.c)
write.c
#include "../head.h"
int main(void)
{
int fd = 0;
int ret = 0;
ssize_t nsize = 0;
char buff[1024] = {0};
mkfifo("/tmp/myfifo", 0664);
fd = open("/tmp/myfifo", O_WRONLY);
if (fd == -1)
{
perror("open");
return -1;
}
while(1)
{
memset(buff, 0, sizeof(buff));
gets(buff);
nsize = write(fd, buff, strlen(buff)+1);
if (nsize == -1)
{
perror("write");
return -1;
}
}
close(fd);
return 0;
}
read.c
#include "../head.h"
int fd = 0;
void handler(int sig)
{
char tmpbuff[1024] = {0};
memset(tmpbuff, 0, sizeof(tmpbuff));
read(fd, tmpbuff, sizeof(tmpbuff));
printf("RECV:%s\n", tmpbuff);
}
int main(void)
{
int flag = 0;
ssize_t nsize = 0;
char *pnisze = NULL;
char buf[1024] = {0};
mkfifo("/tmp/myfifo", 0664);
fd = open("/tmp/myfifo", O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
//获取管道的IO状态
flag = fcntl(fd, F_GETFL);
//设置为异步
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag);
//通知当前进程管道信号来了
fcntl(fd, F_SETOWN, getpid());
signal(SIGIO, handler);
while (1)
{
memset(buf, 0, sizeof(buf));
pnisze = gets(buf);
if (pnisze != NULL)
{
printf("STDIN = %s\n", buf);
}
}
close(fd);
return 0;
}
1.4.多路复用IO
1.select
函数接口:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
功能:
监听文件描述符是否有事件发生
参数:
nfds:最大文件描述符的值 + 1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:异常文件描述符集合
timeout:超时时间
返回值:
成功返回产生事件的文件描述符个数
失败返回-1
timeout时间到达仍然没有产生的事件返回0
void FD_CLR(int fd, fd_set *set);
功能:将fd从集合中清除
int FD_ISSET(int fd, fd_set *set);
功能:判断fd是否仍在文件描述符集合中
void FD_SET(int fd, fd_set *set);
功能:将fd加入文件描述符集合中
void FD_ZERO(fd_set *set);
功能:将文件描述符集合清0
实例
client.c
#include "head.h"
int CreateTcpConnection(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int sockfd)
{
char tmpbuff[4096] = {0};
static int cnt = 0;
ssize_t nsize = 0;
sprintf(tmpbuff, "hello world --- %d", cnt);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
cnt++;
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
return nsize;
}
int main(void)
{
int sockfd = 0;
int ret = 0;
sockfd = CreateTcpConnection(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("连接服务器异常\n");
return -1;
}
while (1)
{
ret = HandleConnection(sockfd);
if (-1 == ret)
{
printf("连接出错!\n");
break;
}
else if (0 == ret)
{
printf("连接关闭\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
server.c
#include "head.h"
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
fd_set rdfds;
fd_set tmpfds;
int nready = 0;
int maxfd = 0;
int i = 0;
//创建监听套接字
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
//将sockfd加入监听集合中
FD_ZERO(&rdfds);
FD_SET(sockfd, &rdfds);
maxfd = sockfd;
while (1)
{
//开始监听
tmpfds = rdfds;
nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
if (-1 == nready)
{
perror("fail to select");
return -1;
}
//如果sockfd产生事件,处理新的连接请求,并将新的文件描述符加入集合,下次一起监听
if (FD_ISSET(sockfd, &tmpfds))
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
FD_CLR(sockfd, &rdfds);
close(sockfd);
continue;
}
maxfd = maxfd > confd ? maxfd : confd;
FD_SET(confd, &rdfds);
}
//遍历所有已经连接的客户端中是否有事件发生
for (i = sockfd+1; i <= maxfd; i++)
{
if (FD_ISSET(i, &tmpfds))
{
ret = HandleConnection(i);
if (-1 == ret)
{
printf("连接异常\n");
FD_CLR(i, &rdfds);
close(i);
continue;
}
else if (0 == ret)
{
printf("连接断开\n");
FD_CLR(i, &rdfds);
close(i);
continue;
}
}
}
}
close(sockfd);
return 0;
}
2.poll
3.epoll
二、总结
2024年8月20日,学习的第36天。今天主要学习TCP的并发模型,主要学习如何模拟实现现实中统一服务器可以和好多不同的客户端同时交互。
加油!