Let's play OSU

本文详细解析了一个类似Let'splayOSU的游戏升级版算法,通过定义f[i]为长度为i的连续个数的期望,ans[i]表示长度为i,平方的期望,推导了f[i]和ans[i]的计算公式,并提供了C++实现代码。

Let’s play OSU
本题和OSU很像 ——升级版
首 先 , 老 套 路 , 定 义 f [ i ] 为 长 度 为 i 的 连 续 个 数 的 期 望 , a n s [ i ] 表 示 长 度 为 i , 平 方 的 期 望 首先,老套路,定义f[i]为长度为i的连续个数的期望,ans[i]表示长度为i,平方的期望 ,f[i]ians[i]i
我 们 发 现 , f [ i ] = ∑ x x ∗ p = ∑ x ∑ i = 1 x p ( 长 度 为 x 的 概 率 ) 我们发现,f[i]=\sum \limits_{x}x*p=\sum\limits_{x}\sum\limits_{i=1}^xp(长度为x的概率) ,f[i]=xxp=xi=1xp(x)
a n s [ i ] = ∑ x x 2 ∗ p ( 长 度 为 x 的 概 率 ) ans[i]=\sum \limits_{x}x^2*p(长度为x的概率) ans[i]=xx2p(x)即从i前随机抽取两个的方案数(可重)
——————————————————————————————————————
a n s [ i ] = a n s [ i − 1 ] ( 两 个 都 从 前 面 抽 并 且 不 一 定 连 续 ) + 2 ∗ f [ i − 1 ] ∗ p [ i ] ( 从 前 面 抽 一 个 , 这 一 个 必 抽 , 无 序 , 所 以 乘 2 ) + 1 ∗ p [ i ] ans[i]=ans[i-1](两个都从前面抽并且不一定连续)+2*f[i-1]*p[i](从前面抽一个,这一个必抽,无序,所以乘2)+1*p[i] ans[i]=ans[i1]()+2f[i1]p[i]2+1p[i]


#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n;
double f[N],ans[N],p[N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){scanf("%lf",&p[i]);}
    for(int i=1;i<=n;i++){
    	f[i]=(f[i-1]*p[i])+1*p[i];
    	ans[i]=ans[i-1]+2*f[i-1]*p[i]+1*p[i];
    }
    printf("%.15f",ans[n]);
}
#!/bin/sh RUN_ONCE_FILE=/var/run/upnpc_running RUN_ONCE_FILE_RESTART=/var/run/upnpc_running_restart RUN_ONCE_FILE_STOP=/var/run/upnpc_running_stop if [ -e $RUN_ONCE_FILE ]; then exit 0 fi touch $RUN_ONCE_FILE . /lib/functions.sh . /usr/share/libubox/jshn.sh __network_ipaddr() { local __var="$1" local __family="$2" local __prefix="${3:-0}" local __tmp="$(ubus -t 2 call network wan_status 2>/dev/null)" json_load "${__tmp:-{}}" json_get_type __tmp "ipv${__family}_address" if [ "$__tmp" = array ]; then json_select "ipv${__family}_address" json_get_type __tmp 1 if [ "$__tmp" = object ]; then json_select 1 json_get_var $__var address [ $__prefix -gt 0 ] && { json_get_var __tmp mask eval "export -- \"$__var=\${$__var}/$__tmp\"" } return 0 fi fi return 1 } # IP_ADDR=$(echo $(ifconfig) |cut -d ' ' -f7|cut -d ':' -f2) # IP_ADDR=$(cat /tmp/udhcpc/dhcpip) # network_get_ipaddr() { __network_ipaddr $1 4 0; } BIND_STATUS="" PUB_IP="" COMM_STATUS="" IP_ADDR="" COMPARE_RESULT=0 DELETE_STRING="" ADD_STRING="" # random method RANDOM_INDEX="" RANDOM_MIN=10000 RANDOM_MAX=30000 upnpc_mode=$(uci_get "upnpc" "upnpc_info" "mode") upnpc_enabled=$(uci_get "upnpc" "upnpc_info" "enabled") uhttpd_inner_port=$(uci_get "uhttpd" "main" "listen_https" "443") #vhttpd_inner_port=$(uci_get "cet" "vhttpd" "port" "8080") tp_live_inner_port="19443" tp_vod_inner_port="18443" tp_speaker_inner_port="17443" rtsp_inner_port=$(uci_get "cet" "rtsp" "port" "554") onvif_service_inner_port="2020" uhttpd_status=$(uci_get "upnpc" "uhttpd" "status") uhttpd_ext_port=$(uci_get "upnpc" "uhttpd" "ext_port") uhttpd_proto=$(uci_get "upnpc" "uhttpd" "proto") uhttpd_desc=$(uci_get "upnpc" "uhttpd" "desc") uhttpd_flag=1 uhttpd_mapping=0 rtsp_status=$(uci_get "upnpc" "rtsp" "status") rtsp_ext_port=$(uci_get "upnpc" "rtsp" "ext_port") rtsp_proto=$(uci_get "upnpc" "rtsp" "proto") rtsp_desc=$(uci_get "upnpc" "rtsp" "desc") rtsp_flag=2 rtsp_mapping=0 onvif_service_status=$(uci_get "upnpc" "onvif_service" "status") onvif_service_ext_port=$(uci_get "upnpc" "onvif_service" "ext_port") onvif_service_proto=$(uci_get "upnpc" "onvif_service" "proto") onvif_service_desc=$(uci_get "upnpc" "onvif_service" "desc") onvif_service_flag=4 onvif_service_mapping=0 tp_live_status=$(uci_get "upnpc" "tp_live" "status") tp_live_ext_port=$(uci_get "upnpc" "tp_live" "ext_port") tp_live_proto=$(uci_get "upnpc" "tp_live" "proto") tp_live_desc=$(uci_get "upnpc" "tp_live" "desc") tp_live_flag=8 tp_live_mapping=0 tp_vod_status=$(uci_get "upnpc" "tp_vod" "status") tp_vod_ext_port=$(uci_get "upnpc" "tp_vod" "ext_port") tp_vod_proto=$(uci_get "upnpc" "tp_vod" "proto") tp_vod_desc=$(uci_get "upnpc" "tp_vod" "desc") tp_vod_flag=16 tp_vod_mapping=0 tp_speaker_status=$(uci_get "upnpc" "tp_speaker" "status") tp_speaker_ext_port=$(uci_get "upnpc" "tp_speaker" "ext_port") tp_speaker_proto=$(uci_get "upnpc" "tp_speaker" "proto") tp_speaker_desc=$(uci_get "upnpc" "tp_speaker" "desc") tp_speaker_flag=32 tp_speaker_mapping=0 upnpc_get_mapping() { local compare_times=0 # we will try three times at most to get port mapping while [ "$compare_times" -lt 3 ] do upnpc -l > "/tmp/upnpc_output" if [ -s "/tmp/upnpc_output" ] then break; else let compare_times=$compare_times+1 sleep 1 fi done } upnpc_update_comm() { local port_status=$1 local secname=$2 local port=$3 # check status local config_status local config_ext_port local config_comm_status eval config_get config_status "${secname}" status eval config_get config_ext_port "${secname}" ext_port eval config_get config_comm_status "${secname}" comm_status if [ $config_status == $port_status ] then if [ -z $port ] then return 0 elif [ $config_ext_port == $port ] then return 0 fi fi if [ $port_status == "on" ] then if [ $PUB_IP == "null" ] then if [ $config_comm_status != "failed" ] then uci set upnpc."${secname}".comm_status='failed' timestamp=`date +%s` uci set upnpc."${secname}".timestamp=$timestamp fi else if [ $config_comm_status != "unknown" ] then uci set upnpc."${secname}".comm_status='unknown' timestamp=`date +%s` uci set upnpc."${secname}".timestamp=$timestamp fi fi elif [ $port_status == "off" ] then if [ $config_comm_status != "failed" ] then uci set upnpc."${secname}".comm_status='failed' timestamp=`date +%s` uci set upnpc."${secname}".timestamp=$timestamp fi fi } # get config values from the upnpc config file upnpc_get_config() { local secname="$1" config_get "${secname}_status" "$secname" status config_get "${secname}_ext_port" "$secname" ext_port config_get "${secname}_proto" "$secname" proto config_get "${secname}_desc" "$secname" desc } # set config values base on the "upnpc -l" results upnpc_set_config_from_real() { local secname="$1" local file_path="$2" local ip local inner_port local flag=0 while read proto ext_port ip_port desc do # divide ip_port such as 192.168.0.60:8000 into 192.168.0.60 8000 ip=$(echo $ip_port | sed 's/:.*//g') inner_port=$(echo $ip_port | sed 's/.*://g') local config_inner_port local config_proto local config_desc local config_ext_port eval config_inner_port="\${${secname}_inner_port}" eval config_proto="\${${secname}_proto}" eval config_desc="\${${secname}_desc}" eval config_ext_port="\${${secname}_ext_port}" if [ "$IP_ADDR" == "$ip" ] \ && [ "$config_inner_port" == "$inner_port" ] \ && [ "$config_proto" == "$proto" ] \ && [ "$config_desc" == "$desc" ]; then if [ "manual" == "$upnpc_mode" ] then # upnp works in manual mode # we need to keep user`s config to do port mapping again uci set upnpc."$secname".status='on' flag=1 break fi # 处理一下port变化的情况 if [ $secname == "tp_live" ] \ || [ $secname == "tp_vod" ] \ || [ $secname == "tp_speaker" ]; then upnpc_update_comm "on" $secname $ext_port fi uci set upnpc."$secname".status='on' uci set upnpc."$secname".ext_port=$ext_port flag=1 break fi done < "$file_path" if [ $flag == 0 ] then if [ $secname == "tp_live" ] \ || [ $secname == "tp_vod" ] \ || [ $secname == "tp_speaker" ]; then upnpc_update_comm "off" $secname fi uci set upnpc."$secname".status='off' fi } # compare config values with the "upnpc -l" results upnpc_compare_real_config() { local secname="$1" local file_path="$2" local mode="$3" local flag=1 local ip local inner_port while read proto ext_port ip_port desc do # divide ip_port such as 192.168.0.60:8000 into 192.168.0.60 8000 ip=$(echo $ip_port | sed 's/:.*//g') inner_port=$(echo $ip_port | sed 's/.*://g') local config_inner_port local config_ext_port local config_proto local config_desc eval config_inner_port="\${${secname}_inner_port}" eval config_ext_port="\${${secname}_ext_port}" eval config_proto="\${${secname}_proto}" eval config_desc="\${${secname}_desc}" if [ "$IP_ADDR" == "$ip" ] \ && [ "$config_inner_port" == "$inner_port" ] \ && [ "$config_ext_port" == "$ext_port" ] \ && [ "$config_proto" == "$proto" ] \ && [ "$config_desc" == "$desc" ]; then if [ "auto" == "$mode" ] \ && [ "$ext_port" -le "$RANDOM_MIN" ]; then break fi if [ $secname == "tp_live" ] \ || [ $secname == "tp_vod" ] \ || [ $secname == "tp_speaker" ]; then upnpc_update_comm "on" $secname $ext_port fi uci set upnpc."$secname".status='on' flag=0 break else # delete port, if not in config if [ "$config_desc" == "$desc" ] then upnpc -d $ext_port $proto fi fi done < "$file_path" if [ "$flag" == "1" ] then local config_flag eval config_flag="\${${secname}_flag}" let COMPARE_RESULT=$COMPARE_RESULT+$config_flag fi } # decide whether to delete certain port mapping upnpc_decide_delete_string() { local secname="$1" local proto="$2" local ext_port="$3" local ip_port="$4" local desc="$5" local mode="$6" local inner_port # divide ip_port such as 192.168.0.60:8000 into 192.168.0.60 8000 inner_port=$(echo $ip_port | sed 's/.*://g') local config_inner_port local config_ext_port local config_proto local config_desc eval config_inner_port="\${${secname}_inner_port}" eval config_ext_port="\${${secname}_ext_port}" eval config_proto="\${${secname}_proto}" eval config_desc="\${${secname}_desc}" if [ "$config_inner_port" == "$inner_port" ] \ && [ "$config_ext_port" == "$ext_port" ] \ && [ "$config_proto" == "$proto" ] \ && [ "$config_desc" == "$desc" ]; then if [ "auto" == "$mode" ] \ && [ "$ext_port" -lt "$RANDOM_MIN" ]; then # need to delete this port mapping DELETE_FLAG=1 let ${secname}_mapping=0 else # do not need to delete this port mapping DELETE_FLAG=0 # mark that the port mapping of this secname has already been done let ${secname}_mapping=1 fi fi } # build ADD_STRING upnpc_build_add_string() { local secname="$1" local mode="$2" local config_ext_port local config_proto local config_desc local config_inner_port local port_mapping eval config_inner_port="\${${secname}_inner_port}" eval config_ext_port="\${${secname}_ext_port}" eval config_proto="\${${secname}_proto}" eval config_desc="\${${secname}_desc}" eval port_mapping="\${${secname}_mapping}" if [ "0" == "$port_mapping" ] then # the port mapping of this secname has not already been done # we need to do port mapping of this secname, so add to ADD_STRING if [ "auto" != "$mode" ] \ || [ "$config_ext_port" -ge "$RANDOM_MIN" ]; then ADD_STRING=$ADD_STRING" "$config_inner_port" "$config_ext_port" "$config_proto" "$config_desc fi fi } # reconfigure port mapping in auto mode upnpc_auto_reconfigure() { #RANDOM_INDEX=$(lua -e 'math.randomseed(tostring(os.time()):reverse():sub(1, 6)); print(math.random(10000, 30000))') myrand() { if [ -z "$RANDOM" ] ; then SEED=`tr -cd 0-9 </dev/urandom | head -c 8` else SEED=$RANDOM fi RND_NUM=`echo $SEED $1 $2|awk '{srand($1);printf "%d",rand()*10000%($3-$2)+$2}'` echo $RND_NUM } RANDOM_INDEX=$(myrand 20000 30000) local compare_times=0 # we will try three times at most to do port mapping while [ "$compare_times" -lt 3 ] do COMPARE_RESULT=0 config_foreach upnpc_compare_real_config entry /tmp/upnpc_output auto if [ "0" == "$COMPARE_RESULT" ] then # the port mappings have been all successful break else # the port mappings have not been all successful # we need to build ADD_STRING based on COMPARE_RESULT ADD_STRING=$IP_ADDR local flag # randomly set the external port by +1 let flag="$COMPARE_RESULT"\&"$uhttpd_flag" if [ "$flag" -ne "0" ] then uhttpd_ext_port=$RANDOM_INDEX let RANDOM_INDEX=$RANDOM_INDEX+1 ADD_STRING=$ADD_STRING" "$uhttpd_inner_port" "$uhttpd_ext_port" "$uhttpd_proto" "$uhttpd_desc fi let flag="$COMPARE_RESULT"\&"$rtsp_flag" if [ "$flag" -ne "0" ] then rtsp_ext_port=$RANDOM_INDEX let RANDOM_INDEX=$RANDOM_INDEX+1 ADD_STRING=$ADD_STRING" "$rtsp_inner_port" "$rtsp_ext_port" "$rtsp_proto" "$rtsp_desc fi let flag="$COMPARE_RESULT"\&"$onvif_service_flag" if [ "$flag" -ne "0" ] then onvif_service_ext_port=$RANDOM_INDEX let RANDOM_INDEX=$RANDOM_INDEX+1 ADD_STRING=$ADD_STRING" "$onvif_service_inner_port" "$onvif_service_ext_port" "$onvif_service_proto" "$onvif_service_desc fi let flag="$COMPARE_RESULT"\&"$tp_live_flag" if [ "$flag" -ne "0" ] then tp_live_ext_port=$RANDOM_INDEX let RANDOM_INDEX=$RANDOM_INDEX+1 ADD_STRING=$ADD_STRING" "$tp_live_inner_port" "$tp_live_ext_port" "$tp_live_proto" "$tp_live_desc fi let flag="$COMPARE_RESULT"\&"$tp_vod_flag" if [ "$flag" -ne "0" ] then tp_vod_ext_port=$RANDOM_INDEX let RANDOM_INDEX=$RANDOM_INDEX+1 ADD_STRING=$ADD_STRING" "$tp_vod_inner_port" "$tp_vod_ext_port" "$tp_vod_proto" "$tp_vod_desc fi let flag="$COMPARE_RESULT"\&"$tp_speaker_flag" if [ "$flag" -ne "0" ] then tp_speaker_ext_port=$RANDOM_INDEX let RANDOM_INDEX=$RANDOM_INDEX+1 ADD_STRING=$ADD_STRING" "$tp_speaker_inner_port" "$tp_speaker_ext_port" "$tp_speaker_proto" "$tp_speaker_desc fi # execute the upnpc comand to do the port mapping # grep the "upnpc -l" results to get only map values that are belonged to current_ip upnpc -a $ADD_STRING \ -l | grep $IP_ADDR > /tmp/upnpc_output let compare_times=$compare_times+1 fi done } # reconfigure port mapping upnpc_reconfigure() { config_load upnpc config_get upnpc_mode upnpc_info mode config_get PUB_IP pub_ip ip IP_ADDR=$(echo $(ifconfig) |cut -d ' ' -f7|cut -d ':' -f2) if [ -e /tmp/upnpc_pre_ip ] then PRE_IP=`cat /tmp/upnpc_pre_ip` else PRE_IP="" fi if [ -z "$IP_ADDR" ] \ && [ -z "$PRE_IP" ]; then # current ip and preview ip are all empty cat /tmp/upnpc_output > /tmp/upnpc_output1 elif [ "$IP_ADDR" == "$PRE_IP" ] \ || [ -z "$PRE_IP" ] then # preview_ip is equal to current_ip # so we do not need to check PRE_IP PRE_IP="" # grep the "upnpc -l" results to get only map values that are belonged to current_ip cat /tmp/upnpc_output | grep "$IP_ADDR" > /tmp/upnpc_output1 elif [ -z "$IP_ADDR" ] then # grep the "upnpc -l" results to get only map values that are belonged to preview_ip cat /tmp/upnpc_output | grep "$PRE_IP" > /tmp/upnpc_output1 else # grep the "upnpc -l" results to get only map values that are belonged to preview_ip and current_ip cat /tmp/upnpc_output | grep -E "$IP_ADDR|$PRE_IP" > /tmp/upnpc_output1 fi # grep the "upnpc -l" results to get only map values that are belonged to preview_ip and current_ip cat /tmp/upnpc_output | grep -E "$IP_ADDR|$PRE_IP" > /tmp/upnpc_output1 config_foreach upnpc_get_config entry local ip while read proto ext_port ip_port desc do # divide ip_port such as 192.168.0.60:8000 into 192.168.0.60 8000 # and get the ip value such as "192.168.0.60" ip=$(echo $ip_port | sed 's/:.*//g') if [ "$IP_ADDR" == "$ip" ] then # this port mapping is belonged to IP_ADDR DELETE_FLAG=1 config_foreach upnpc_decide_delete_string entry $proto $ext_port $ip_port $desc $upnpc_mode if [ "1" == "$DELETE_FLAG" ] then # this port mapping is not useful, delete it # package the DELETE_STRING with the form: ext_port proto DELETE_STRING=$DELETE_STRING" "$ext_port" "$proto fi fi if [ "$PRE_IP" == "$ip" ] then # this port mapping is belonged to PRE_IP, delete it # package the DELETE_STRING with the form: ext_port proto DELETE_STRING=$DELETE_STRING" "$ext_port" "$proto fi done < "/tmp/upnpc_output1" # begin to build ADD_STRING ADD_STRING=$IP_ADDR config_foreach upnpc_build_add_string entry $upnpc_mode # begin to build upnpc_string local upnpc_string if [ -n "$DELETE_STRING" ] then # DELETE_STRING is not empty, add to upnpc_string upnpc_string=$upnpc_string" -d "$DELETE_STRING fi if [ "$IP_ADDR" != "$ADD_STRING" ] then # ADD_STRING is not default, add to upnpc_string upnpc_string=$upnpc_string" -a "$ADD_STRING fi # add -l to upnpc_string to list all the port mappings upnpc_string=$upnpc_string" -l" # execute the upnpc comand to do the port mapping # grep the "upnpc -l" results to get only map values that are belonged to current_ip upnpc $upnpc_string | grep $IP_ADDR > /tmp/upnpc_output # save current ip into /tmp/upnpc_pre_ip echo $IP_ADDR > /tmp/upnpc_pre_ip if [ "auto" == "$upnpc_mode" ] then # upnp works in auto mode, if failed # we need to use randomly port to do port mapping again upnpc_auto_reconfigure fi # set upnpc config file based on the "upnpc -l" results config_foreach upnpc_set_config_from_real entry /tmp/upnpc_output uci commit upnpc rm -f "/tmp/upnpc_output1" rm -f "/tmp/upnpc_output" } # the following function will be called outside asynchronously # so we need to add lock to prevent them from being called at the same time # maintain port mapping upnpc_maintain() { config_load cloud_brd config_get BIND_STATUS bind status config_load upnpc config_get upnpc_enabled upnpc_info enabled config_get upnpc_mode upnpc_info mode config_get PUB_IP pub_ip ip timestamp=`date +%s` echo $timestamp >/dev/console if [ "$BIND_STATUS" == "1" ] then if [ "$upnpc_enabled" == "on" ] then IP_ADDR=$(echo $(ifconfig) |cut -d ' ' -f7|cut -d ':' -f2) if [ -n "$IP_ADDR" ] then lock "/var/run/upnpc" #upnpc -l > "/tmp/upnpc_output" upnpc_get_mapping # grep the "upnpc -l" results to get only map values that are belonged to current_ip cat /tmp/upnpc_output | grep $IP_ADDR > /tmp/upnpc_output1 COMPARE_RESULT=0 config_foreach upnpc_compare_real_config entry /tmp/upnpc_output1 $upnpc_mode if [ $COMPARE_RESULT == 0 ] then uci commit upnpc # do not need to maintain # save current ip into /tmp/upnpc_pre_ip echo $IP_ADDR > /tmp/upnpc_pre_ip rm -f "/tmp/upnpc_output1" rm -f "/tmp/upnpc_output" else # need to maintain, reconfigure upnpc upnpc_reconfigure fi lock -u "/var/run/upnpc" fi fi else upnpc_stop fi } # stop port mapping upnpc_stop() { if [ -e $RUN_ONCE_FILE_STOP ]; then exit 0 fi touch $RUN_ONCE_FILE_STOP lock "/var/run/upnpc" config_load upnpc config_get PUB_IP pub_ip ip # network_get_ipaddr IP_ADDR IP_ADDR=$(echo $(ifconfig) |cut -d ' ' -f7|cut -d ':' -f2) local DELETE_STRING local PRE_IP local ip if [ -e /tmp/upnpc_pre_ip ] then PRE_IP=`cat /tmp/upnpc_pre_ip` else PRE_IP="" fi # grep the "upnpc -l" results to get only map values that are belonged to preview_ip and current_ip upnpc -l | grep -E "$IP_ADDR|$PRE_IP" > "/tmp/upnpc_output" while read proto ext_port ip_port desc do # divide ip_port such as 192.168.0.60:8000 into 192.168.0.60 8000 # and get the ip value such as "192.168.0.60" ip=$(echo $ip_port | sed 's/:.*//g') if [ "$ip" == "$IP_ADDR" ] \ || [ "$ip" == "$PRE_IP" ]; then DELETE_STRING=$DELETE_STRING" "$ext_port" "$proto fi done < "/tmp/upnpc_output" if [ -n "$DELETE_STRING" ] then upnpc -d $DELETE_STRING \ -l | grep $IP_ADDR > "/tmp/upnpc_output" fi config_foreach upnpc_set_config_from_real entry /tmp/upnpc_output uci commit upnpc rm -f /tmp/upnpc_output lock -u "/var/run/upnpc" rm $RUN_ONCE_FILE_STOP } # restart port mapping upnpc_restart() { if [ -e $RUN_ONCE_FILE_RESTART ]; then exit 0 fi touch $RUN_ONCE_FILE_RESTART config_load cloud_brd config_get BIND_STATUS bind status config_load upnpc config_get upnpc_enabled upnpc_info enabled if [ "$BIND_STATUS" == "1" ] then if [ "$upnpc_enabled" == "on" ] then lock "/var/run/upnpc" upnpc -l > "/tmp/upnpc_output" upnpc_reconfigure lock -u "/var/run/upnpc" else upnpc_stop fi else upnpc_stop fi rm $RUN_ONCE_FILE_RESTART } case "$1" in maintain) upnpc_maintain ;; esac rm $RUN_ONCE_FILE 运行这个脚本有什么作用?
最新发布
09-18
<think> 这个脚本是一个用于管理UPnP(Universal Plug and Play)端口映射的Shell脚本。它主要用于在路由器或网络设备上自动配置端口转发规则,以便外部网络可以访问设备内部的服务(如HTTP服务器、RTSP流媒体服务等)。下面是对脚本功能的详细解释: ### 脚本主要功能 1. **端口映射管理**: - 添加、删除和更新UPnP端口映射规则。 - 支持多种服务(如uhttpd、RTSP、ONVIF服务等)的端口转发配置。 2. **工作模式**: - **手动模式**:使用用户配置的固定外部端口进行映射。 - **自动模式**:当固定端口冲突时,自动选择随机端口(在10000-30000范围内)进行映射。 3. **状态维护**: - 定期检查当前端口映射状态(通过`upnpc -l`命令)。 - 根据设备绑定状态(从`cloud_brd`配置文件获取)和UPnP启用状态(从`upnpc`配置文件获取)决定是否执行端口映射。 4. **冲突处理**: - 检测到端口冲突时(例如,外部端口已被占用),自动切换到其他端口。 - 删除无效或旧的端口映射(如设备IP变更后遗留的映射)。 ### 脚本关键部分解析 #### 1. **初始化与配置加载** - 脚本开头定义了几个标志文件(如`/var/run/upnpc_running`)用于防止脚本重复运行。 - 加载必要的函数库(`/lib/functions.sh`和`/usr/share/libubox/jshn.sh`)。 - 通过`uci_get`命令从配置文件中读取各种服务的配置(如端口号、协议、描述等)。 #### 2. **辅助函数** - `__network_ipaddr`:通过UBus获取网络接口的IP地址。 - `upnpc_get_mapping`:执行`upnpc -l`命令获取当前端口映射列表,并保存到临时文件。 - `upnpc_update_comm`:更新服务的通信状态(成功、失败或未知)。 - `upnpc_get_config`:从配置文件中读取特定服务的配置。 - `upnpc_set_config_from_real`:根据实际的端口映射状态更新配置文件。 - `upnpc_compare_real_config`:比较配置和实际的端口映射,决定是否需要重新映射。 #### 3. **核心逻辑** - **`upnpc_reconfigure`**: - 比较当前IP和前一次记录的IP(`/tmp/upnpc_pre_ip`),处理IP变更。 - 构建删除字符串(`DELETE_STRING`)和添加字符串(`ADD_STRING`)。 - 执行`upnpc`命令删除旧映射并添加新映射。 - 在自动模式下,如果映射失败,则尝试使用随机端口重新映射(`upnpc_auto_reconfigure`)。 - **`upnpc_maintain`**: - 检查设备绑定状态和UPnP启用状态。 - 锁定后执行端口映射的维护(比较当前映射和配置,必要时重新配置)。 - **`upnpc_stop`**: - 停止所有由该脚本管理的端口映射(删除当前IP和前一次IP相关的映射)。 - **`upnpc_restart`**: - 重新启动端口映射(先停止再重新配置)。 #### 4. **执行入口** - 脚本通过命令行参数调用不同功能: - `maintain`:执行维护操作(定期检查并更新映射)。 - 其他功能(如`restart`和`stop`)通过其他机制触发(如系统信号或定时任务)。 ### 运行脚本的作用 运行此脚本(通常通过传递`maintain`参数)会执行以下操作: 1. **检查运行条件**: - 设备必须绑定到云服务(`BIND_STATUS=1`)。 - UPnP功能必须启用(`upnpc_enabled=on`)。 2. **获取当前IP地址**。 3. **锁定机制**: - 使用文件锁(`/var/run/upnpc`)确保同一时间只有一个实例运行。 4. **获取当前端口映射列表**: - 执行`upnpc -l`并过滤出当前IP的映射。 5. **比较配置与实际映射**: - 如果配置的映射不存在或端口变化,则重新配置。 6. **执行端口映射更新**: - 删除无效的旧映射(如前一次IP的映射或配置变更的映射)。 - 添加新的映射(根据配置或自动选择的端口)。 7. **自动模式处理**: - 如果端口冲突,自动选择随机端口重试(最多3次)。 8. **更新配置状态**: - 将映射结果写回配置文件(`uci commit upnpc`)。 9. **清理临时文件**。 ### 总结 这个脚本是一个UPnP端口映射的自动化管理工具,特别适合在路由器或嵌入式设备上运行,用于动态管理端口转发规则,确保外部网络可以访问设备内部服务。它处理了IP变动、端口冲突等常见问题,并提供了两种模式(手动和自动)以适应不同需求。 §§相关问题§§: 1. 如何在C程序中实现类似Shell脚本中的端口冲突检测? 2. 如何使用C语言直接操作UPnP协议进行端口映射,而不依赖`upnpc`命令行工具? 3. 在嵌入式设备中,如何通过C程序监控网络接口的IP地址变化? 4. 如何用C语言实现配置文件的读写(类似脚本中的`uci_get`和`uci_set`)? </think> ### 脚本功能详解及运行作用 这个脚本是一个完整的 **UPnP端口映射管理工具**,主要用于在路由器环境中动态管理和维护端口转发规则。以下是详细分析: #### 核心功能 ```mermaid graph TD A[脚本入口] --> B{维护模式} B -->|maintain| C[执行端口映射维护] C --> D[检测设备绑定状态] D -->|已绑定| E[获取当前IP] E --> F[获取现有UPnP映射] F --> G[比较配置和实际映射] G -->|不一致| H[重新配置端口映射] H --> I[删除旧映射] I --> J[添加新映射] J --> K[更新配置文件] ``` #### 具体作用: 1. **动态端口映射管理**: - 自动检测网络环境变化(如IP地址变更) - 维护7种服务的端口映射: - HTTP服务(uhttpd) - RTSP流媒体服务 - ONVIF设备服务 - TP-Live直播服务 - TP-VoD点播服务 - TP-Speaker音频服务 2. **双模式运作机制**: ```c // 配置获取示例 upnpc_mode=$(uci_get "upnpc" "upnpc_info" "mode") // manual或auto ``` - **手动模式**: - 使用用户配置的固定端口 - 端口冲突时会报错 - **自动模式**: - 自动生成10000-30000的随机端口 - 冲突时自动重试新端口 3. **智能状态维护**: - 通过锁文件防止重复运行: ```bash lock "/var/run/upnpc" ``` - 记录前次IP防止配置丢失: ```bash echo $IP_ADDR > /tmp/upnpc_pre_ip ``` 4. **错误恢复机制**: - 映射失败时自动重试(最多3次) - 自动清理无效映射: ```bash upnpc -d $ext_port $proto ``` #### 运行效果示例 当执行 `./script.sh maintain` 时: 1. **典型成功流程**: ```bash 1. 检查设备绑定状态 (cloud_brd) 2. 获取当前IP地址 (ifconfig) 3. 获取现有UPnP映射 (upnpc -l) 4. 比较配置和实际映射 5. 添加缺失映射: upnpc -a 192.168.1.100 443 8443 TCP "HTTP_Service" 6. 更新配置文件 (uci commit upnpc) ``` 2. **端口冲突处理**: ```bash 1. 检测到443端口冲突 2. 自动模式启用: RANDOM_INDEX=23456 3. 尝试新端口: upnpc -a 192.168.1.100 443 23456 TCP 4. 更新配置文件中端口号 ``` #### 关键技术创新点 1. **双IP兼容机制**: ```bash # 同时处理新旧IP的映射 cat /tmp/upnpc_output | grep -E "$IP_ADDR|$PRE_IP" > /tmp/upnpc_output1 ``` 2. **服务位掩码管理**: ```c // 服务标识位掩码 uhttpd_flag=1 rtsp_flag=2 // 通过位运算检测需要处理的服务 let flag="$COMPARE_RESULT"\&"$uhttpd_flag" ``` 3. **原子化配置更新**: ```bash uci set upnpc.uhttpd.ext_port=23456 uci commit upnpc ``` #### 适用场景 1. 路由器重启后自动恢复端口映射 2. 网络环境变更时(如PPPoE重拨) 3. 端口冲突时自动恢复服务 4. 云绑定设备远程服务维护 ### 总结 该脚本是**专业的UPnP端口映射守护程序**,主要解决路由器环境中常见的端口冲突、IP变更导致的映射失效等问题。通过智能比较配置和实际映射状态,实现7种网络服务的端口转发自动化管理,特别适合嵌入式设备使用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值