Linux--->网络编程(TCP并发服务器构建:[ 多进程、多线程、select ])

TCP并发服务器构建

一、服务器

单循环服务器:服务端同一时刻只能处理一个客户端的任务(TCP)

并发服务器:服务端同一时刻可以处理多个客户端的任务(UDP)

二、TCP服务端并发模型

1、多进程

进程资源开销大,安全性高。

以下是一个实现多进程TCP服务端代码:

cs 复制代码
#define SER_PORT 62300
#define SER_IP "192.168.0.165"

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return 0;
    }
    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)
    {
        printf("bind error");
        return -1;
    }
    int cnt = listen(sockfd, 100);
    if(cnt < 0)
    {
        perror("listen error");
        return -1;
    }
    return sockfd;
}

void wait_handler(int signo)
{
    wait(NULL);
}

int main(int argc, char *argv[])
{
    signal(SIGCHLD, wait_handler);
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        perror("init_tcp_ser error");
        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(pid == 0)
        {
            char buff[1024] = {0};
            while(1)
            {
                memset(buff, 0, sizeof(buff));
                ssize_t ret = recv(connfd, buff, sizeof(buff), 0);
                if(ret < 0)
                {
                    perror("send error");
                    return -1;
                }
                else if(ret == 0)
                {
                    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");
                ssize_t cnt = send(connfd, buff, strlen(buff), 0);
                if(cnt < 0)
                {
                    perror("send error");
                    break;
                }
            }   
            close(connfd);
        }
    }
    close(sockfd);
    return 0;
}

2、多线程

线程相对与进程资源开销小,相同资源环境下,并发量比进程大。

以下是一个实现多线程TCP服务端代码:

cs 复制代码
#include "head.h"

#define SER_PORT 62300
#define SER_IP "192.168.0.165"

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return 0;
    }
    //允许绑定处于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)
    {
        printf("bind error");
        return -1;
    }
    int cnt = listen(sockfd, 100);
    if(cnt < 0)
    {
        perror("listen error");
        return -1;
    }
    return sockfd;
}

void *task(void *sug)
{
    int connfd = *(int *)sug;
    char buff[1024] = {0};
    while(1)
    {
        memset(buff, 0, sizeof(buff));
        ssize_t ret = recv(connfd, buff, sizeof(buff), 0);
        if(ret < 0)
        {
            perror("send error");
            break;
        }
        else if(ret == 0)
        {
            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");
        ssize_t cnt = send(connfd, buff, strlen(buff), 0);
        if(cnt < 0)
        {
            perror("send error");
            break;
        }
    }   
    close(connfd);
}

int main(int argc, char *argv[])
{
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        perror("init_tcp_ser error");
        return -1;
    }
    while(1)
    {
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen); 
        if(connfd < 0)
        {
            perror("accept error");
            return -1;
        }
        pthread_t tid;
        int fd = pthread_create(&tid, NULL, task, &connfd);
        if(fd < 0)
        {
            perror("thread create error");
            return -1;
        }  
        pthread_detach(tid);
    }
    close(sockfd);
    return 0;
}

3、线程池

为了解决多线程或者多进程模型,在服务器运行过程,频繁创建和销毁线程(进程)带来的时间消耗问题。

基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架。

4. IO多路复用

1)概念

对多个文件描述符的读写可以复用一个进程。

在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。

阻塞IO模式:

2)方式

实现方式有 select、poll、epoll 三种。

5、select 实现IO多路复用

1)实现步骤

(1)创建文件描述符集合 fd_set

(2)添加关注的文件描述符到集合 FD_SET

(3)使用 select 传递集合表给内核,内核开始监测事件 select()

(4)当内核监测到事件时,应用层select将解除阻塞,并获得相关的事件结果

(5)根据 select 返回的结果做不同的任务处理

2)select 的宏

将集合清0:void FD_ZERO(fd_set *set);

向集合中添加文件描述符:void FD_SET(int fd, fd_set *set);

从集合中移除文件描述符:void FD_CLR(int fd, fd_set *set);

检查文件描述符是否在集合中:int FD_ISSET(int fd, fd_set *set);

3)select() 函数接口

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
| 功能:传递文件描述符结合表给内核并等待获取事件结果 参数: nfds : 关注的最大文件描述符+1 readfds:读事件的文件描述符集合 writefds:写事件的文件描述符集合 exceptfds:其他事件的文件描述符集合 timeout:设置select监测时的超时时间 若为NULL : 不设置超时时间(select一直阻塞等待) 返回值: 成功:返回内核监测的到达事件的个数 失败:-1 0 : 超时时间到达,但没有事件发生,则返回0 |

4)应用示例

(1)通过select函数构建多路复用,实现管道和终端的读

管道向服务端写的代码:

cs 复制代码
int main(void)
{
    mkfifo("./myfifo", 0664);
    int fd = open("./myfifo", O_WRONLY);
    if(fd < 0)
    {
        perror("open error");
        return -1;
    }
    while(1)
    {
        write(fd, "hello world", 11);
        sleep(1);
    }
    close(fd);
    return 0;
}

读的代码:

cs 复制代码
int main(void)
{
    char buff[1024] = {0};
    mkfifo("./myfifo", 0664);

    int fifofd = open("./myfifo", O_RDONLY);
    if(fifofd < 0)
    {
        perror("open 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))//检查文件描述符0是否在集合中
        {
            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;
}

(2)使用select实现TCP服务端IO多路复用代码

cs 复制代码
#define SER_PORT  50000
#define SER_IP    "192.168.0.165"

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;
}

【END】

相关推荐
柳鲲鹏40 分钟前
未成功:使用 Nginx 搭建代理服务器(正向代理 HTTPS 网站)
运维·nginx
wuyang-ligerj1 小时前
路由基础(二):路由表和FIB表
运维·网络·网络协议·智能路由器
adyp1 小时前
Superset docker、ubuntu、windows安装方式
运维
来根烟了寂寞2 小时前
瑞芯微rv1106交叉编译openssl 1.x
linux·嵌入式
拾心212 小时前
【运维进阶】高可用和负载均衡技术
运维·负载均衡
hyy27952276842 小时前
自动化运维之k8s——Kubernetes集群部署、pod、service微服务、kubernetes网络通信
运维·kubernetes·自动化
wuyang-ligerj3 小时前
OSPF协议(三)
运维·网络·网络协议·智能路由器
EnigmaCoder3 小时前
【Linux】用户与用户组管理
linux·运维·服务器·数据库
TG_yilongcloud3 小时前
AWS亚马逊云账号注册指南
服务器·云计算·aws