并发服务器: 服务器在同一时刻可以响应多个客户端的请求。
UDP:无连接
单循环服务器:服务器同一时刻只能响应一个客户端的请求。
TCP:有连接
构建TCP并发服务器: 让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;
}
子进程结束会隐含一个SIGCHLD信号,对该信号进行注册,然后跳转到该函数进行操作。
方法二:多线程
资源消耗相比多进程的较小
--------------------------------------------------------------实现-------------------------------------------------------------
#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;
}
方法三:线程池
提前预创建大量线程,避免任务执行过程中创建线程的耗时。
方法四:IO多路复用(转接)
在不创建新的进程和线程的情况下,可以在一个进程中同时监测多个IO
===============“黑心饭店老板和一个牛马服务员的悲惨故事”=========================
#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函数:
/* According to POSIX.1-2001 */
#include <sys/select.h>/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);功能:阻塞等待IO事件,返回事件结果
参数:nfds:关注的最大文件描述符+1;readfds:文件描述符读事件的集合表writefds:文件描述符写事件的集合表exceptfds:其他事件表timeout:超时时间(如果不设置超时时间,则该位置为NULL)即select的等待时间
返回值:成功返回到达的文件描述符事件个数,失败返回-1,如果超时时间到达还没有IO事件时返回0void 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);
实现多路复用的过程(select,poll,epoll)
1.创建文件描述符集合
2.将关注的文件描述符加入到集合
3.等待IO时间到达
4.根据不同的IO事件处理不同的任务
select的不足之处:
1.底层使用位图管理文件描述符,最多允许同时监测1024个文件描述符(有上限)
2.文件描述符集合在应用层创建需要实现应用层和内核层的反复拷贝。
3.需要应用层对集合表进行遍历,循环找到达的事件。
4.只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)。
QUESTION:
完成多线程时,代码写完线程的时候不知道怎么关闭线程,如果就放在while(1)里面,就会阻塞后面的进程,那该怎么解决呢,我就查询了一些资料,发现可以对线程进行分离,