TCP 并发
由于tcp协议只能实现一对一的通信模式。为了实现一对多,有以下的的处理方式
1. 多进程
开销大
效率低
2. 多线程
创建线程需要耗时
3. 线程池
多线程模型创建线程耗时问题,提前创建
4. IO多路复用
在不创建进程和线程的前提下,对多个文件描述符进行同时监测
IO:fd读写, 共用一个进程
1.多进程(服务端)
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
int init_tpc_ser(const char *ip , unsigned short port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("fail socket");
return -1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
if(ret < 0)
{
perror("fail bind ");
return -1;
}
return sockfd;
}
void handler(int signo)
{
wait(NULL);
}
int main(int agrc, char *agrv[])
{
signal (17,handler);
int sockfd = init_tpc_ser("192.168.0.179",50000);
int ret = listen (sockfd,100);
char buff[1024] = {0};
if(ret < 0)
{
perror("fail listen");
return -1;
}
while(1)
{
int connfd = accept(sockfd,NULL,NULL);
if(connfd < 0)
{
perror("fail accept");
return -1;
}
pid_t pid = fork();
if(pid > 0)
{
}
else if(pid == 0)
{
while(1)
{
memset(buff, 0 ,sizeof(buff));
int ret = recv(connfd,buff ,sizeof(buff),0);
printf("%s\n",buff);
if(ret <= 0)
{
return -1;
}
strcat(buff,"-----ok");
ret = send(connfd, buff,sizeof(buff),0);
if(ret < 0)
{
return -1;
}
}
return 0;
}
else
{
printf("fork fail");
return -1;
}
}
close(sockfd);
return-1;
}
2.多线程(服务端)
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
int init_tpc_ser(const char *ip , unsigned short port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("fail socket");
return -1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
if(ret < 0)
{
perror("fail bind ");
return -1;
}
return sockfd;
}
char buff[1024] = {0};
void* task(void *reg)
{
int connfd = *(int *)reg;//传过来保存就不会收到其他线程影响
while(1)
{
memset(buff, 0 ,sizeof(buff));
int ret = recv(connfd,buff ,sizeof(buff),0);
printf("%s\n",buff);
if(ret == 0)
{
break;
}
strcat(buff,"-----ok");
ret = send(connfd, buff,sizeof(buff),0);
if(ret = 0)
{
break;
}
}
close(connfd);
pthread_detach(pthread_self());//设置线程分离属性。主线程无暇回收次线程
return 0;
}
int main(int agrc, char *agrv[])
{
int sockfd = init_tpc_ser("192.168.0.179",50000);
int ret = listen (sockfd,100);
int connfd = 0;
pthread_t tid = 0;
if(ret < 0)
{
perror("fail listen");
return -1;
}
while(1)
{
connfd = accept(sockfd,NULL,NULL);
if(connfd < 0)
{
perror("fail accept");
return -1;
}
pthread_create(&tid,NULL,task,&connfd);
}
close(sockfd);
return-1;
}
3.io多路复用(具体见下)
2.IO模型
Linux下常用的IO模型:
1. 阻塞IO
fgets
read
getchar
fread
fgetc
recv
recvfrom
1. 让多个IO事件以同步方式处理(顺序处理),
2. 多个IO事件之间互相影响
3. CPU占有率低
2. 非阻塞IO
将IO对应的文件描述符设置成非阻塞方式。
O_NONBLOCK
fcntl
1.非阻塞一般搭配轮询方式同时监测多个IO
2.cpu占有率高
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo", 0664);
char buff[1024] = {0};
int fifofd = open("./fifo", O_RDONLY);
if (fifofd < 0)
{
perror("fail open fifo");
return -1;
}
//1, 获取文件描述符(终端)原flags特性
int flags = fcntl(0, F_GETFL);
//2. 给原flags增加非阻塞特性
flags = flags | O_NONBLOCK;
//3. 给文件描述符设置新flags特性
fcntl(0, F_SETFL, flags);
while (1)
{
memset(buff, 0, sizeof(buff));
fgets(buff, sizeof(buff), stdin);//不会堵塞在这里,内核检测到键盘输入自动运行
printf("STDIN : %s\n", buff);
memset(buff, 0, sizeof(buff));
read(fifofd, buff, sizeof(buff));
printf("FIFO : %s\n", buff);
}
close(fifofd);
return 0;
}
3. 信号驱动IO
SIGIO
1.实现异步IO操作,节省CPU开销
2.只能针对比较少的IO事件(信号很少)
#include "head.h"
char buff[1024] = {0};
void handler (int signo)
{
memset(buff, 0, sizeof(buff));
fgets(buff, sizeof(buff), stdin);
printf("STDIN : %s\n", buff);
}
int main(int argc, const char *argv[])
{
signal(SIGIO,handler);
mkfifo("./fifo", 0664);
int fifofd = open("./fifo", O_RDONLY);
if (fifofd < 0)
{
perror("fail open fifo");
return -1;
}
//1, 获取文件描述符原flags特性
int flags = fcntl(0, F_GETFL);
//2. 给原flag异步
flags = flags |O_ASYNC;
//3. 给文件描述符设置新flags特性
fcntl(0, F_SETFL, flags);
//关联信号与当前进程
fcntl(0,F_SETOWN,getpid());
while (1)
{
memset(buff, 0, sizeof(buff));
read(fifofd, buff, sizeof(buff));
printf("FIFO : %s\n", buff);
}
close(fifofd);
return 0;
}
4. IO多路复用(IO多路转接)
在不创建新的进程和线程的前提下, 在一个进程中,同时监测多个IO
1. select
1. 文件描述符集合以数组(位图)的方式保存,最多监测1024个文件描述符。
2. 文件描述符集合创建在应用层,需要应用层和内核层反复拷贝
3. 内核层返回整个文件描述符集合,需要用户层遍历查找到达事件的文件描述符
4. 只能工作在水平触发模式(低速模式)
2. poll
1. 文件描述符集合以链表的方式保存,监测文件描述符可超过1024。
2. 文件描述符集合创建在应用层,需要应用层和内核层反复拷贝
3. 内核层返回整个文件描述符集合,需要用户层遍历查找到达事件的文件描述符
4. 只能工作在水平触发模式(低速模式)
3. epoll
1. 文件描述符集合以树型结构(红黑树)保存,监测文件描述符可超过1024, 提高了数据的查找速度。
2. 文件描述符集合创建在内核层;
3. 返回的是到达事件的文件描述符集合,无需遍历查找
4. 可以工作在水平触发模式(低速模式),也可以工作在边沿触发模式(高速模式)
1.select
- 创建文件描述符集合
2. 添加关注的文件描述符到集合
3. 传递给内核,内核开始监测IO事件 select
4. IO事件到达则返回结果–》
2.哪个fd事件到达执行哪个
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:通知内核监测IO事件并返回事件结果
参数:
nfds:最大文件描述符+1
readfds: 文件描述符读事件集合
writefds:文件描述符写事件集合
exceptfds:其他
timeout : 超时时间
返回值:
成功:返回达到的事件的个数
失败:-1
设置超时:超时时间到,但没有事件到达:0
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include "head.h"
int main(int argc, const char *argv[])
{
mkfifo("./fifo", 0664);
char buff[1024] = {0};
int fifofd = open("./fifo", O_RDONLY);
if (fifofd < 0)
{
perror("fail open fifo");
return -1;
}
int maxfd = 0;
fd_set rdfds;//创建集合
FD_ZERO(&rdfds);//清0
//加入集合
FD_SET(0,&rdfds);
FD_SET(fifofd,&rdfds);
maxfd = fifofd >maxfd ?fifofd:maxfd;
fd_set rdfdscopy;
while(1)
{
rdfdscopy= rdfds;//防止rdfds被修改,
//发送位图给内核
int cnt = select(maxfd+1,&rdfdscopy,NULL,NULL,NULL);
if(cnt < 0)
{
perror("fail select");
return -1;
}
//判断是否事件到达
if(FD_ISSET(0,&rdfdscopy))
{
memset(buff,0,sizeof(buff));
fgets(buff,sizeof(buff),stdin);
printf("stdin : %s",buff);
}
if(FD_ISSET(fifofd,&rdfdscopy))
{
memset(buff,0,sizeof(buff));
read(fifofd,buff,sizeof(buff));
printf("fifo : %s\n",buff);
}
}
close(fifofd);
return 0;
}
select实现并发tcp
#include "head.h"
int init_tcp_ser(const char *ip, unsigned short port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("fail socket");
return -1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
if (ret < 0)
{
perror("fail bind");
return -1;
}
ret = listen(sockfd, 100);
if (ret < 0)
{
perror("fail listen");
return -1;
}
return sockfd;
}
int main(int argc, const char *argv[])
{
char buff[1024] = {0};
int sockfd = init_tcp_ser("192.168.0.166", 50000);
if (sockfd < 0)
{
return -1;
}
struct sockaddr_in cli;
socklen_t clilen = sizeof(cli);
int maxfd = 0;
//1、 创建文件描述符集合
fd_set rdfds, rdfdstmp;
FD_ZERO(&rdfds);
//2. 将关注的监听套接字加入到集合
FD_SET(sockfd, &rdfds);
maxfd = sockfd;
while (1)
{
//3. 内核开始检测套接字事件
rdfdstmp = rdfds;
int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
if (cnt < 0)
{
perror("fail select");
return -1;
}
// 是否有监听套接字事件到达
if (FD_ISSET(sockfd, &rdfdstmp))
{
int connfd = accept(sockfd, (struct sockaddr *)&cli, &clilen);
if (connfd < 0)
{
perror("fail accept");
return -1;
}
printf("[%s : %d] get online!\n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));
FD_SET(connfd, &rdfds);
maxfd = maxfd > connfd ? maxfd : connfd;
}
//循环遍历剩余套接字,是否有通讯套接字事件到达
for (int i = sockfd+1; i <= maxfd; i++)
{
if (FD_ISSET(i, &rdfdstmp))
{
memset(buff, 0, sizeof(buff));
ssize_t size = recv(i, buff, sizeof(buff), 0);
if (size < 0)
{
perror("fail recv");
FD_CLR(i, &rdfds);
close(i);
continue;
}
else if (0 == size)
{
FD_CLR(i, &rdfds);
close(i);
continue;
}
printf("%s\n", buff);
strcat(buff, "-----ok!");
size = send(i, buff, strlen(buff), 0);
if (size < 0)
{
perror("fail recv");
FD_CLR(i, &rdfds);
close(i);
continue;
}
}
}
}
close(sockfd);
return 0;
}