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