单循环服务器:服务器在同一时刻只能响应一个客户端的需求。
并发服务器:服务器在同一时刻可以响应多个客户端的需求。
构建TCP服务器的方法:
IO多路复用的函数接口[select() poll() epoll()]
1.多进程实现TCP并发服务器
#include <stdio.h>
#include "head.h"#define LISTEN_CLI__MAX_CNT 1024int 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, LISTEN_CLI__MAX_CNT);if (ret < 0){perror("fail listen");return -1;}return sockfd;
}void handler(int signum)
{wait(NULL);
}int main(int argc, const char *argv[])
{struct sockaddr_in cli;socklen_t clilen = sizeof(cli);signal(SIGCHLD, handler);int sockfd = init_tcp_ser("192.168.1.179", 50000);if (sockfd < 0){return -1;}while (1){int connfd = accept(sockfd, (struct sockaddr *)&cli, &clilen);if (connfd < 0){perror("fail accept");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 size = recv(connfd, buff, sizeof(buff), 0);if (size < 0){perror("fail recv");exit(1);}else if (0 == size) // closed{break;}printf("[%s : %d][%ld] %s\n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port), size, buff);strcat(buff, "--->ok");size = send(connfd, buff, strlen(buff), 0);if (size < 0){perror("fail send");exit(1);}}exit(0);}else{perror("fail fork");return -1;}}return 0;
}
2.多线程实现TCP并发服务器
#include <stdio.h>
#include "head.h"#define LISTEN_CLI__MAX_CNT 1024int 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, LISTEN_CLI__MAX_CNT);if (ret < 0){perror("fail listen");return -1;}return sockfd;
}void *do_communicate(void *arg)
{char buff[1024] = {0};int connfd = *(int *)arg;while (1){memset(buff, 0, sizeof(buff));size_t size = recv(connfd, buff, sizeof(buff), 0);if (size < 0){perror("fail recv");return NULL;}else if (0 == size){break;}printf("%s\n", buff);strcat(buff, "---->ok");size = send(connfd, buff, strlen(buff), 0);if (size < 0){perror("fail send");return NULL;}}return NULL;
}int main(int argc, const char *argv[])
{struct sockaddr_in cli;pthread_t tid;socklen_t clilen = sizeof(cli);int sockfd = init_tcp_ser("192.168.1.179", 50000);if (sockfd < 0){return -1;}while (1){int connfd = accept(sockfd, (struct sockaddr *)&cli, &clilen);if (connfd < 0){perror("fail accept");return -1;}pthread_create(&tid, NULL, do_communicate, &connfd);pthread_detach(tid);}return 0;
}
3.IO多路复用
方法:
select(),poll(),epoll()函数接口的优缺点
函数接口: select()
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
具体代码实现 :用select实现数据的接受(并发实现从管道读数据和从终端读数据)
#include <stdio.h>
#include "head.h"#define LISTEN_CLI__MAX_CNT 1024int 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, LISTEN_CLI__MAX_CNT);if (ret < 0){perror("fail listen");return -1;}return sockfd;
}int main(int argc, const char *argv[])
{struct sockaddr_in cli;pthread_t tid;int maxfd = -1;char buff[1024] = {0};socklen_t clilen = sizeof(cli);int sockfd = init_tcp_ser("192.168.1.158", 50000);if (sockfd < 0){return -1;}fd_set tmpfds;fd_set rdfds;FD_ZERO(&rdfds);FD_SET(sockfd, &rdfds);maxfd = maxfd > sockfd ? maxfd : sockfd;while (1){tmpfds = rdfds;int cnt = select(maxfd+1, &tmpfds, NULL, NULL, NULL);if (cnt < 0){perror("fail select");return -1;}for (int i = 0; i <= maxfd; i++){if (FD_ISSET(i, &tmpfds)){if (i == sockfd){int connfd = accept(sockfd, (struct sockaddr *)&cli, &clilen);if (connfd < 0){perror("fail accept");continue;}FD_SET(connfd, &rdfds);maxfd = maxfd > connfd ? maxfd : connfd;printf("[%s : %d] online\n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));}else{//connfdmemset(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 send");FD_CLR(i, &rdfds);close(i);continue;}}}}}return 0;
}
select()函数接口的不足处:
函数接口:poll();
poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:
监听文件描述符集合中的事件
参数:
fds:文件描述符集合事件数组首地址
nfds:事件个数
timeout:超时时间
返回值:
成功返回产生事件的文件描述符个数
失败返回-1
超时时间到达仍没有产生事件返回0结构体:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
函数接口epoll();
epoll模型
epoll模型:
1)epoll_create 创建epoll文件描述符集合
2)epoll_ctl 添加关注的文件描述符
3)epoll_wait 监控io事件
4)epoll_ctl 从事件集合中删除完成的文件描述符
epoll_create()
epoll_create
int epoll_create(int size);
功能:
创建一个监听事件表(内核中)
参数:
size:监听事件最大个数
返回值:
成功返回非负值:表示epoll事件表对象(句柄)
失败返回-1
epoll_ctl()
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
在监听事件表中新增一个事件
参数:
epfd:事件表文件描述符
opEPOLL_CTL_ADD 新增事件
EPOLL_CTL_MOD 修改事件
EPOLL_CTL_DEL 删除事件
fd:文件描述符
events:事件相关结构体
返回值:
成功返回0
失败返回-1
结构体说明
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};events:
EPOLLIN 读事件
EPOLLOUT 写事件
EPOLLET 边沿触发
LT 水平触发
用epoll()实现并发的从终端与管道文件中读取数据
#include "../head.h"#define EPOLL_FD_MAX 2int epoll_fd_add(int epfds, int fd, uint32_t events)
{struct epoll_event ev;ev.events = events;ev.data.fd = fd;int ret = epoll_ctl(epfds, EPOLL_CTL_ADD, fd, &ev);if (ret < 0){perror("fail epoll_ctl add");return -1;}return 0;
}int main(int argc, const char *argv[])
{char buff[1024] = {0};//保存epoll_wait返回的到达事件的结果struct epoll_event evs[EPOLL_FD_MAX];mkfifo("./myfifo", 0664);int fifofd = open("./myfifo", O_RDONLY);if (fifofd < 0){perror("fail open fifo");return -1;}//1 创建文件描述符集合int epfds = epoll_create(EPOLL_FD_MAX);if (epfds < 0){perror("fail epoll_create");return -1;}//2 添加关注的文件描述符epoll_fd_add(epfds, 0, EPOLLIN);epoll_fd_add(epfds, fifofd, EPOLLIN);//3. 使用epoll_wait开始监测while (1){int cnt = epoll_wait(epfds, evs, EPOLL_FD_MAX, -1);if (cnt < 0){perror("fail epoll_wait");return -1;}for (int i = 0; i < cnt; i++){if (0 == evs[i].data.fd){fgets(buff, sizeof(buff), stdin);printf("STDIN: %s\n", buff);}else if (fifofd == evs[i].data.fd){memset(buff, 0, sizeof(buff));read(fifofd, buff, sizeof(buff));printf("FIFO: %s\n", buff);}}}close(fifofd);return 0;
}