TCP并发服务器
TCP并发服务器
单循环服务器 :服务器在同一时刻只能响应一个客户端的需求。
并发服务器 :服务器在同一时刻可以响应多个客户端的需求。
构建TCP服务器的方法:
IO多路复用的函数接口 [select() poll() epoll()]
1.多进程实现TCP并发服务器
#include <stdio.h>
#include "head.h"
#define LISTEN_CLI__MAX_CNT 1024
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, 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 1024
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, 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 1024
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, 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
{
//connfd
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 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: 事件表文件描述符
op
EPOLL_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 2
int 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;
}