多进程侦听同一端口

2024-09-01 04:38
文章标签 进程 端口 同一 侦听

本文主要是介绍多进程侦听同一端口,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://blog.csdn.net/cwj649956781/article/details/21533341

一、端口侦听
我们知道,系统中的互联网端口地址是系统级唯一的,在默认情况下,IPV4和IPV6的同一个协议的套接口也不能再同一个端口侦听,而套接口编程的五元组就是<IP,port,peerip,peerport,inet proto>,其中没有进程区分,所以一个系统的套接口对于同一个网络地址来说是唯一的。但是有时候为了实现负载平衡,可能希望有多个进程来侦听同一个套接口,从而并发执行某个任务,此时就希望多个相同的进程(相同的可执行文件)来对同一个套接口进行侦听,从而完成负载分流和平衡。
当然,多线程也是一种实现方法,但是缺点就是需要实现用户态编码,不对可执行程序透明,用户态的代码需要自己调用pthread_create来创建多个线程,这样属于一种硬编码的方式,有其资源共享的优点,但是会增加维护的复杂度。而一个程序同时执行多份的话,由于代码段共享的原因,系统同样不会有太大的内存开销,并且可以方便的由用户态决定启动多少个任务而不依赖代码实现。
二、fastcgi starter实现
通产来说,如果让同一个进程依次派生执行,那么这个多进程侦听同一个套接口是一定无法实现的,因为在bind系统调用会返回端口被占用错误,所以此时就需要由一个父进程来完成这个同一个的bind+listen动作,这时候把一个套接口已经培养到可以执行accept系统调用来获得连接请求的时候,这个fd相当于已经被培育成熟,所以此时根据需要个数派生服务进程,这样子进程就可以在照约定的文件描述符上进行accept接收外部连接请求。或者任务fastcgi派生的都是“官二代”,当这些子进程启动起来之后,它就可以直接从一个文件描述符上进行accept来接见各种连接请求,并且每个子进程都有这种接收机会。
这个流程无论从实现和原理上来讲都不是很复杂,但是比较有创意。大家经常说“文件是unix的精髓”,但是能够把它用到这种地步还真是不容易,同样的套接口,同样的文件描述符,就是可以做到多进程侦听同一个端口的实现。这一点和busybox的可执行文件“多路复用”一样,是一种化腐朽为神奇,或者至少是“化平凡为神奇”的实现方法。而两者也的确是依靠这两个比较有创意的思路,实现了两种非常有用的机制,busybox在嵌入式中几乎是根文件系统的基础,而fastcgi则是网络服务器中的快速响应流行模型。
httpd-2.4.2\support\fcgistarter.c中相关代码:

  rv = apr_sockaddr_info_get(&skaddr, interface, APR_UNSPEC, port, 0, pool);
    if (rv) {
        exit_error(rv, "apr_sockaddr_info_get");
    }

    rv = apr_socket_create(&skt, skaddr->family, SOCK_STREAM, APR_PROTO_TCP, pool);
    if (rv) {
        exit_error(rv, "apr_socket_create");
    }

    rv = apr_socket_bind(skt, skaddr);
    if (rv) {
        exit_error(rv, "apr_socket_bind");
    }

    rv = apr_socket_listen(skt, 1024);
    if (rv) {
        exit_error(rv, "apr_socket_listen");
    }
 while (--num_to_start >= 0) {完成套接口侦听之后循环创建子进程。
        rv = apr_proc_fork(&proc, pool);
……            apr_os_file_t oft = 0;注意这个文件描述符,被fcgi派生的子进程就是通过在这个文件描述符上直接执行accept系统调用来完成服务请求的,这个文件描述符在fastcgi.h中定义为#define FCGI_LISTENSOCK_FILENO 0,数值同样为零。
            apr_os_sock_t oskt;

}

三、多进程竞争连接请求
内核实现部分其实并不重要,也没什么好说的,只是比较好奇,就大致看一下相关实现。
1、等待队列头创建
最原始的等待队列在sock_alloc--->>>
static struct inode *sock_alloc_inode(struct super_block *sb)
{
    init_waitqueue_head(&ei->socket.wait);
}
中实现,这里其实没有什么初始化,就是初始化了一个自旋锁,并且初始化为可获取状态,它并没有初始化方法成员。
然后在__sock_create--->>inet_create--->>>sock_init_data
        sk->sk_sleep    =    &sock->wait;
这里将sk结构中的等待队列头指向socket中的wait成员,而这个sk_sleep将会是accept的等待队列头地址。
2、accept阻塞
sys_accept---->>>inet_accept--->>inet_csk_accept---->>>inet_csk_wait_for_connect--->>prepare_to_wait_exclusive(sk->sk_sleep, &wait,TASK_INTERRUPTIBLE)
wait->flags |= WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&q->lock, flags);
    if (list_empty(&wait->task_list))
        __add_wait_queue_tail(q, wait);
    /*
在加入等待队列之后,通过timeo = schedule_timeout(timeo);让出调度权。
这里比较特殊的是这里的唤醒是互斥的,也就是那个 WQ_FLAG_EXCLUSIVE标志,这个标志会在唤醒函数中使用,当遇到这个标志并且唤醒互斥进程个数为1(默认情况)时只唤醒一个进程,其中的prepare_to_wait_exclusiv的wait是通过下面宏创建的
DEFINE_WAIT(wait);
3、连接到来时唤醒
tcp_v4_do_rcv--->>>tcp_child_process
        /* Wakeup parent, send SIGIO */
        if (state == TCP_SYN_RECV && child->sk_state != state)
            parent->sk_data_ready(parent, 0);
inet_create--->>>sock_init_data

    sk->sk_state_change    =    sock_def_wakeup;
    sk->sk_data_ready    =    sock_def_readable;
    sk->sk_write_space    =    sock_def_write_space;
    sk->sk_error_report    =    sock_def_error_report;
    sk->sk_destruct        =    sock_def_destruct;
也就是执行的sk_data_ready即为sock_def_readable函数,在该函数中,其执行操作为
static void sock_def_readable(struct sock *sk, int len)
{
    read_lock(&sk->sk_callback_lock);
    if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
        wake_up_interruptible(sk->sk_sleep);
    sk_wake_async(sk,1,POLL_IN);
    read_unlock(&sk->sk_callback_lock);
}
#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
可以看到,通过sk->sk_sleep唤醒了正在accept的接收套接口,并且其中__wake_up的唤醒互斥任务个数为1,所以只会唤醒一个进程,这次连接的到来对其它任务透明。


这篇关于多进程侦听同一端口的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125961

相关文章

Flask解决指定端口无法生效问题

《Flask解决指定端口无法生效问题》文章讲述了在使用PyCharm开发Flask应用时,启动地址与手动指定的IP端口不一致的问题,通过修改PyCharm的运行配置,将Flask项目的运行模式从Fla... 目录android问题重现解决方案问题重现手动指定的IP端口是app.run(host='0.0.

Windows命令之tasklist命令用法详解(Windows查看进程)

《Windows命令之tasklist命令用法详解(Windows查看进程)》tasklist命令显示本地计算机或远程计算机上当前正在运行的进程列表,命令结合筛选器一起使用,可以按照我们的需求进行过滤... 目录命令帮助1、基本使用2、执行原理2.1、tasklist命令无法使用3、筛选器3.1、根据PID

linux本机进程间通信之UDS详解

《linux本机进程间通信之UDS详解》文章介绍了Unix域套接字(UDS)的使用方法,这是一种在同一台主机上不同进程间通信的方式,UDS支持三种套接字类型:SOCK_STREAM、SOCK_DGRA... 目录基础概念本机进程间通信socket实现AF_INET数据收发示意图AF_Unix数据收发流程图A

nginx配置多域名共用服务器80端口

《nginx配置多域名共用服务器80端口》本文主要介绍了配置Nginx.conf文件,使得同一台服务器上的服务程序能够根据域名分发到相应的端口进行处理,从而实现用户通过abc.com或xyz.com直... 多个域名,比如两个域名,这两个域名其实共用一台服务器(意味着域名解析到同一个IP),一个域名为abc

Python中多线程和多进程的基本用法详解

《Python中多线程和多进程的基本用法详解》这篇文章介绍了Python中多线程和多进程的相关知识,包括并发编程的优势,多线程和多进程的概念、适用场景、示例代码,线程池和进程池的使用,以及如何选择合适... 目录引言一、并发编程的主要优势二、python的多线程(Threading)1. 什么是多线程?2.

Nginx启动失败:端口80被占用问题的解决方案

《Nginx启动失败:端口80被占用问题的解决方案》在Linux服务器上部署Nginx时,可能会遇到Nginx启动失败的情况,尤其是错误提示bind()to0.0.0.0:80failed,这种问题通... 目录引言问题描述问题分析解决方案1. 检查占用端口 80 的进程使用 netstat 命令使用 ss

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Windows设置nginx启动端口的方法

《Windows设置nginx启动端口的方法》在服务器配置与开发过程中,nginx作为一款高效的HTTP和反向代理服务器,被广泛应用,而在Windows系统中,合理设置nginx的启动端口,是确保其正... 目录一、为什么要设置 nginx 启动端口二、设置步骤三、常见问题及解决一、为什么要设置 nginx

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程