多进程侦听同一端口

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

相关文章

kkFileView启动报错:报错2003端口占用的问题及解决

《kkFileView启动报错:报错2003端口占用的问题及解决》kkFileView启动报错因office组件2003端口未关闭,解决:查杀占用端口的进程,终止Java进程,使用shutdown.s... 目录原因解决总结kkFileViewjavascript启动报错启动office组件失败,请检查of

一文解密Python进行监控进程的黑科技

《一文解密Python进行监控进程的黑科技》在计算机系统管理和应用性能优化中,监控进程的CPU、内存和IO使用率是非常重要的任务,下面我们就来讲讲如何Python写一个简单使用的监控进程的工具吧... 目录准备工作监控CPU使用率监控内存使用率监控IO使用率小工具代码整合在计算机系统管理和应用性能优化中,监

nginx中端口无权限的问题解决

《nginx中端口无权限的问题解决》当Nginx日志报错bind()to80failed(13:Permissiondenied)时,这通常是由于权限不足导致Nginx无法绑定到80端口,下面就来... 目录一、问题原因分析二、解决方案1. 以 root 权限运行 Nginx(不推荐)2. 为 Nginx

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

zookeeper端口说明及介绍

《zookeeper端口说明及介绍》:本文主要介绍zookeeper端口说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、zookeeper有三个端口(可以修改)aVNMqvZ二、3个端口的作用三、部署时注意总China编程结一、zookeeper有三个端口(可以

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

前端如何通过nginx访问本地端口

《前端如何通过nginx访问本地端口》:本文主要介绍前端如何通过nginx访问本地端口的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、nginx安装1、下载(1)下载地址(2)系统选择(3)版本选择2、安装部署(1)解压(2)配置文件修改(3)启动(4)