TCP并发服务器构建:
单循环服务器:服务端同一时刻只能处理单个客户端的任务
并发服务器:服务端同一时刻能够处理多个客户端的任务
产生多个套接字可建立多个连接:
TCP服务端并发模型:
1:使用多进程
头文件:
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <pthread.h>
#endif
代码
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
ret = listen(sockfd, 100);
if (ret < 0)
{
perror("listen error");
return -1;
}
return sockfd;
}
void wait_handler(int signo)
{
wait(NULL);
}
int main(int argc, const char *argv[])
{
signal(SIGCHLD, wait_handler);
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int sockfd = init_tcp_ser();
if (sockfd < 0)
{
return -1;
}
while (1)
{
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (connfd < 0)
{
perror("accept error");
return -1;
}
pid_t pid = fork();
if (pid > 0)
{
}
else if (0 == pid)
{
char buff[1024] = {0};
while (1)
{
memset(buff, 0, sizeof(buff));
size_t cnt = recv(connfd, buff, sizeof(buff), 0);
if (cnt < 0)
{
perror("recv error");
break;
}
else if (0 == cnt)
{
printf("[%s : %d] : offline\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
break;
}
printf("[%s : %d] : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);
strcat(buff, "---->ok");
cnt = send(connfd, buff, strlen(buff), 0);
if (cnt < 0)
{
perror("send error");
break;
}
}
close(connfd);
}
}
close(sockfd);
return 0;
}
2:使用多线程
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
//允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
ret = listen(sockfd, 100);
if (ret < 0)
{
perror("listen error");
return -1;
}
return sockfd;
}
void *task(void *arg)
{
char buff[1024] = {0};
int connfd = *((int *)arg);
while (1)
{
memset(buff, 0, sizeof(buff));
size_t cnt = recv(connfd, buff, sizeof(buff), 0);
if (cnt < 0)
{
perror("recv error");
break;
}
else if (0 == cnt)
{
printf("offline\n");
break;
}
printf("%s\n", buff);
strcat(buff, "---->ok");
cnt = send(connfd, buff, strlen(buff), 0);
if (cnt < 0)
{
perror("send error");
break;
}
}
close(connfd);
}
int main(int argc, const char *argv[])
{
pthread_t tid;
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int sockfd = init_tcp_ser();
if (sockfd < 0)
{
return -1;
}
while (1)
{
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (connfd < 0)
{
perror("accept error");
return -1;
}
pthread_create(&tid, NULL, task, &connfd);
pthread_detach(tid);
}
close(sockfd);
return 0;
}
线程池:
为了解决多线程或者多进程模型,在服务器运行过程,频繁创建和销毁线程(进程)带来的时间消耗问题,基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架。
IO多路复用:
对多个文件描述符的读写操作可以复用一个进程。在不创建新的进程和线程的前提下,使用一个进程实现多个文件的读写的同时监测。三种方式:
linux内核监测事件:
1:select实现IO多路复用:
(1)创建文件描述符集合
(2)添加关注的文件描述符到集合
(3)使用select传递集合表给内核,内核检测事件
(4) 当内核监测到时间时,应用层select将解除阻塞,
(5)并将获得相关的事件结果做不同的任务处理

{
mkfifo("./myfifo", 0664);
int fd = open("./myfifo", O_WRONLY);
if (fd < 0)
{
perror("open fifo error");
return -1;
}
while (1)
{
write(fd, "hello world", 11);
sleep(1);
}
close(fd);
return 0;
}
#include "head.h"
int main(int argc, const char *argv[])
{
char buff[1024] = {0};
mkfifo("./myfifo", 0664);
int fifofd = open("./myfifo", O_RDONLY);
if (fifofd < 0)
{
perror("open fifo error");
return -1;
}
//1 创建文件描述符集合表
fd_set rdfds;
fd_set rdfdstmp;
//2. 清空文件描述符集合表
FD_ZERO(&rdfds);
//3. 添加关注的文件描述符到集合中
FD_SET(0, &rdfds);
int maxfd = 0;
FD_SET(fifofd, &rdfds);
maxfd = maxfd > fifofd ? maxfd : fifofd;
while (1)
{
rdfdstmp = rdfds;
//4. 传递集合表给内核并等待返回到达事件的结果
int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
if (cnt < 0)
{
perror("select error");
return -1;
}
if (FD_ISSET(0, &rdfdstmp))
{
fgets(buff, sizeof(buff), stdin); //0
printf("STDIN : %s\n", buff);
}
if (FD_ISSET(fifofd, &rdfdstmp))
{
memset(buff, 0, sizeof(buff));
read(fifofd, buff, sizeof(buff));
printf("FIFO : %s\n", buff);
}
}
close(fifofd);
return 0;
}
IO多路复用
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
ret = listen(sockfd, 100);
if (ret < 0)
{
perror("listen error");
return -1;
}
return sockfd;
}
int main(int argc, const char *argv[])
{
char buff[1024] = {0};
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int sockfd = init_tcp_ser();
if (sockfd < 0)
{
return -1;
}
//1. 创建文件描述符集合
fd_set rdfds;
fd_set rdfdstmp;
FD_ZERO(&rdfds);
//2. 添加关注的文件描述符到集合
FD_SET(sockfd, &rdfds);
int maxfd = sockfd;
while (1)
{
rdfdstmp = rdfds;
//3. 传递集合到内核,并等待返回监测结果
int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
if (cnt < 0)
{
perror("select error");
return -1;
}
//4. 是否有监听套接字事件到达 ----》三次握手已完成,可以accept
if (FD_ISSET(sockfd, &rdfdstmp))
{
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (connfd < 0)
{
perror("accept error");
return -1;
}
FD_SET(connfd, &rdfds);
maxfd = maxfd > connfd ? maxfd : connfd;
}
//5. 是否有通讯套接字事件到达
for (int i = sockfd+1; i <= maxfd; i++)
{
if (FD_ISSET(i, &rdfdstmp))
{
memset(buff, 0, sizeof(buff));
ssize_t cnt = recv(i, buff, sizeof(buff), 0);
if (cnt < 0)
{
perror("recv error");
FD_CLR(i, &rdfds);
close(i);
continue;
}
else if (0 == cnt)
{
FD_CLR(i, &rdfds);
close(i);
continue;
}
printf("%s\n", buff);
strcat(buff, "--->ok");
cnt = send(i, buff, strlen(buff), 0);
if (cnt < 0)
{
perror("send error");
FD_CLR(i, &rdfds);
close(i);
continue;
}
}
}
}
close(sockfd);
return 0;
}
2:poll
3:epoll
epoll:
1:创建文件描述符结合
2:添加关注的文件描述符
3:epoll通知内核开始进行事件监测
4:epoll返回时,获取到达事件的结果
5:
函数1:
函数2:
函数3:
selsect和epoll区别:EPOLL没有文件描述符上限限制,selsect上限1024个,