CSP202104-3 DHCP服務器 題解


這是道好題,雖然我沒在現場比,但是也寫了兩個多小時,還和其他dalao的題解對照了半天才調出錯來

一、題目

題目鏈接
↓↓↓如果上面沒有csp賬號,暫時沒法看題目的話,請看下面↓↓↓(好家伙把這個粘下來就200多行了湊字數,還鍛煉Markdown(((

試題背景

動態主機配置協議(Dynamic Host Configuration Protocol, DHCP)是一種自動為網絡客戶端分配 IP 地址的網絡協議。當支持該協議的計算機剛剛接入網絡時,它可以啟動一個 DHCP 客戶端程序。后者可以通過一定的網絡報文交互,從 DHCP 服務器上獲得 IP 地址等網絡配置參數,從而能夠在用戶不干預的情況下,自動完成對計算機的網絡設置,方便用戶連接網絡。DHCP 協議的工作過程如下:

  1. 當 DHCP 協議啟動的時候,DHCP 客戶端向網絡中廣播發送 Discover 報文,請求 IP 地址配置;
  2. 當 DHCP 服務器收到 Discover 報文時,DHCP 服務器根據報文中的參數選擇一個尚未分配的 IP 地址,分配給該客戶端。DHCP 服務器用 Offer 報文將這個信息傳達給客戶端;
  3. 客戶端收集收到的 Offer 報文。由於網絡中可能存在多於一個 DHCP 服務器,因此客戶端可能收集到多個 Offer 報文。客戶端從這些報文中選擇一個,並向網絡中廣播 Request 報文,表示選擇這個 DHCP 服務器發送的配置;
  4. DHCP 服務器收到 Request 報文后,首先判斷該客戶端是否選擇本服務器分配的地址:如果不是,則在本服務器上解除對那個 IP 地址的占用;否則則再次確認分配的地址有效,並向客戶端發送 Ack 報文,表示確認配置有效,Ack 報文中包括配置的有效時間。如果 DHCP 發現分配的地址無效,則返回 Nak 報文;
  5. 客戶端收到 Ack 報文后,確認服務器分配的地址有效,即確認服務器分配的地址未被其它客戶端占用,則完成網絡配置,同時記錄配置的有效時間,出於簡化的目的,我們不考慮被占用的情況。若客戶端收到 Nak 報文,則從步驟 1 重新開始;
  6. 客戶端在到達配置的有效時間前,再次向 DHCP 服務器發送 Request 報文,表示希望延長 IP 地址的有效期。DHCP 服務器按照步驟 4 確定是否延長,客戶端按照步驟 5 處理后續的配置;

在本題目中,你需要理解 DHCP 協議的工作過程,並按照題目的要求實現一個簡單的 DHCP 服務器。

問題描述

報文格式

為了便於實現,我們簡化地規定 DHCP 數據報文的格式如下:

<發送主機> <接收主機> <報文類型> <IP 地址> <過期時刻>

DHCP 數據報文的各個部分由空格分隔,其各個部分的定義如下:

  • 發送主機:是發送報文的主機名,主機名是由小寫字母、數字組成的字符串,唯一地表示了一個主機;
  • 接收主機:當有特定的接收主機時,是接收報文的主機名;當沒有特定的接收主機時,為一個星號(*);
  • 報文類型:是三個大寫字母,取值如下:
    • DIS:表示 Discover 報文;
    • OFR:表示 Offer 報文;
    • REQ:表示 Request 報文;
    • ACK:表示 Ack 報文;
    • NAK:表示 Nak 報文;
  • IP 地址,是一個非負整數:
    • 對於 Discover 報文,該部分在發送的時候為 0,在接收的時候忽略;
    • 對於其它報文,為正整數,表示一個 IP 地址;
  • 過期時刻,是一個非負整數:
    • 對於 Offer、Ack 報文,是一個正整數,表示服務器授予客戶端的 IP 地址的過期時刻;
    • 對於 Discover、Request 報文,若為正整數,表示客戶端期望服務器授予的過期時刻;
    • 對於其它報文,該部分在發送的時候為 0,在接收的時候忽略。
      例如下列都是合法的 DHCP 數據報文:
a * DIS 0 0
d a ACK 50 1000

服務器配置

為了 DHCP 服務器能夠正確分配 IP 地址,DHCP 需要接受如下配置:

  • 地址池大小 \(N\):表示能夠分配給客戶端的 IP 地址的數目,且能分配的 IP 地址是 ;
  • 默認過期時間 \(T_{def}\):表示分配給客戶端的 IP 地址的默認的過期時間長度;
  • 過期時間的上限和下限 \(T_{max}\)\(T_{min}\):表示分配給客戶端的 IP 地址的最長過期時間長度和最短過期時間長度,客戶端不能請求比這個更長或更短的過期時間;
  • 本機名稱 \(H\):表示運行 DHCP 服務器的主機名。

分配策略

當客戶端請求 IP 地址時,首先檢查此前是否給該客戶端分配過 IP 地址,且該 IP 地址在此后沒有被分配給其它客戶端。如果是這樣的情況,則直接將 IP 地址分配給它,否則,
總是分配給它最小的尚未占用過的那個 IP 地址。如果這樣的地址不存在,則分配給它最小的此時未被占用的那個 IP 地址。如果這樣的地址也不存在,說明地址池已經分配完畢,因此拒絕分配地址。

實現細節

在 DHCP 啟動時,首先初始化 IP 地址池,將所有地址設置狀態為未分配,占用者為空,並清零過期時刻。
其中地址的狀態有未分配、待分配、占用、過期四種。
處於未分配狀態的 IP 地址沒有占用者,而其余三種狀態的 IP 地址均有一名占用者。
處於待分配和占用狀態的 IP 地址擁有一個大於零的過期時刻。在到達該過期時刻時,若該地址的狀態是待分配,則該地址的狀態會自動變為未分配,且占用者清空,過期時刻清零;否則該地址的狀態會由占用自動變為過期,且過期時刻清零。處於未分配和過期狀態的 IP 地址過期時刻為零,即沒有過期時刻。

對於收到的報文,設其收到的時刻為 \(t\)。處理細節如下:

  1. 判斷接收主機是否為本機,或者為 *,若不是,則判斷類型是否為 Request,若不是,則不處理;
  2. 若類型不是 Discover、Request 之一,則不處理;
  3. 若接收主機為 *,但類型不是 Discover,或接收主機是本機,但類型是 Discover,則不處理。

對於 Discover 報文,按照下述方法處理:

  1. 檢查是否有占用者為發送主機的 IP 地址:
    • 若有,則選取該 IP 地址;
    • 若沒有,則選取最小的狀態為未分配的 IP 地址;
    • 若沒有,則選取最小的狀態為過期的 IP 地址;
    • 若沒有,則不處理該報文,處理結束;
  1. 將該 IP 地址狀態設置為待分配,占用者設置為發送主機;

  2. 若報文中過期時刻為 0 ,則設置過期時刻為 \(t+T_{def}\);否則根據報文中的過期時刻和收到報文的時刻計算過期時間,判斷是否超過上下限:若沒有超過,則設置過期時刻為報文中的過期時刻;否則則根據超限情況設置為允許的最早或最晚的過期時刻;

  3. 向發送主機發送 Offer 報文,其中,IP 地址為選定的 IP 地址,過期時刻為所設定的過期時刻。
    對於 Request 報文,按照下述方法處理:

  4. 檢查接收主機是否為本機:

    • 若不是,則找到占用者為發送主機的所有 IP 地址,對於其中狀態為待分配的,將其狀態設置為未分配,並清空其占用者,清零其過期時刻,處理結束;
  1. 檢查報文中的 IP 地址是否在地址池內,且其占用者為發送主機,若不是,則向發送主機發送 Nak 報文,處理結束;
  2. 無論該 IP 地址的狀態為何,將該 IP 地址的狀態設置為占用;
  3. 與 Discover 報文相同的方法,設置 IP 地址的過期時刻;
  4. 向發送主機發送 Ack 報文。
    上述處理過程中,地址池中地址的狀態的變化可以概括為如下圖所示的狀態轉移圖。為了簡潔,該圖中沒有涵蓋需要回復 Nak 報文的情況。
    image

輸入格式

輸入的第一行包含用空格分隔的四個正整數和一個字符串,分別是:\(N\)\(T_{def}\)\(T_{max}\)\(T_{min}\)\(H\),保證 。

輸入的第二行是一個正整數 \(n\),表示收到了 \(n\) 個報文。

輸入接下來有 \(n\) 行,第 \((i+2)\) 行有空格分隔的正整數 \(t_i\) 和約定格式的報文 \(P_i\) 。表示收到的第 \(i\) 個報文是在 \(t_i\) 時刻收到的,報文內容是 \(P_i\)。保證 \(t_i<t_{i+1}\)

輸出格式

輸出有若干行,每行是一個約定格式的報文。依次輸出 DHCP 服務器發送的報文。

樣例輸入

4 5 10 5 dhcp
16
1 a * DIS 0 0
2 a dhcp REQ 1 0
3 b a DIS 0 0
4 b * DIS 3 0
5 b * REQ 2 12
6 b dhcp REQ 2 12
7 c * DIS 0 11
8 c dhcp REQ 3 11
9 d * DIS 0 0
10 d dhcp REQ 4 20
11 a dhcp REQ 1 20
12 c dhcp REQ 3 20
13 e * DIS 0 0
14 e dhcp REQ 2 0
15 b dhcp REQ 2 25
16 b * DIS 0 0

樣例輸出

dhcp a OFR 1 6
dhcp a ACK 1 7
dhcp b OFR 2 9
dhcp b ACK 2 12
dhcp c OFR 3 12
dhcp c ACK 3 13
dhcp d OFR 4 14
dhcp d ACK 4 20
dhcp a ACK 1 20
dhcp c ACK 3 20
dhcp e OFR 2 18
dhcp e ACK 2 19
dhcp b NAK 2 0

樣例說明

輸入第一行,分別設置了 DHCP 的相關參數,並收到了 16 個報文。

第 1 個報文和第 2 個報文是客戶端 a 正常請求地址,服務器為其分配了地址 1,相應地設置了過期時刻是 7(即當前時刻 2 加上默認過期時間 5)。

第 3 個報文不符合 Discover 報文的要求,不做任何處理。

第 4 個報文 b 發送的 Discover 報文雖然有 IP 地址 3,但是按照處理規則,這個字段被忽略,因此服務器返回 Offer 報文,過期時刻是 9。

第 5 個報文中,Request 報文不符合接收主機是 DHCP 服務器本機的要求,因此不做任何處理。

第 6 個報文是 b 發送的 Request 報文,其中設置了過期時刻是 12,沒有超過最長過期時間,因此返回的 Ack 報文中過期時刻也是 12。

第 7 個報文中,過期時刻 11 小於最短過期時間,因此返回的過期時刻是 12。雖然此時為 a 分配的地址 1 過期,但是由於還有狀態為未分配的地址 3,因此為 c 分配地址 3。第 8 個報文同理,為 c 分配的地址過期時刻是 13。

第 9、10 兩個報文中,為 d 分配了地址 4,過期時刻是 20。

第 11 個報文中,a 請求重新獲取此前為其分配的地址 1,雖然為其分配的地址過期,但是由於尚未分配給其它客戶端,因此 DHCP 服務器可以直接為其重新分配該地址,並重新設置過期時刻為 20。

第 12 個報文中,c 請求延長其地址的過期時刻為 20。DHCP 正常向其回復 Ack 報文。

第 13、14 個報文中,e 試圖請求地址。此時地址池中已經沒有處於“未分配”狀態的地址了,但是有此前分配給 b 的地址 2 的狀態是“過期”,因此把該地址重新分配給 e。

第 15 個報文中,b 試圖重新獲取此前為其分配的地址 2,但是此時該地址已經被分配給 e,因此返回 Nak 報文。

第 16 個報文中,b 試圖重新請求分配一個 IP 地址,但是此時地址池中已經沒有可用的地址了,因此忽略該請求。

樣例輸入

4 70 100 50 dhcp
6
5 a * OFR 2 100
10 b * DIS 0 70
15 b dhcp2 REQ 4 60
20 c * DIS 0 70
70 d * DIS 0 120
75 d dhcp REQ 1 125

樣例輸出

dhcp b OFR 1 70
dhcp c OFR 1 70
dhcp d OFR 1 120
dhcp d ACK 1 125

樣例說明

在本樣例中,DHCP 服務器一共收到了 6 個報文,處理情況如下:

第 1 個報文不是 DHCP 服務器需要處理的報文,因此不回復任何報文。

第 2 個報文中,b 請求分配 IP 地址,因此 DHCP 服務器將地址 1 分配給 b,此時,地址 1 進入待分配狀態,DHCP 服務器向 b 發送 Offer 報文。

第 3 個報文中,b 發送的 REQ 報文是發給非本服務器的,因此需要將地址池中所有擁有者是 b 的待分配狀態的地址修改為未分配。

第 4 個報文中,c 請求分配 IP 地址。由於地址 1 此時是未分配狀態,因此將該地址分配給它,向它發送 Offer 報文,地址 1 進入待分配狀態。

第 5、6 個報文中,d 請求分配 IP 地址。注意到在收到第 5 個報文時,已經是時刻 70,地址 1 的過期時刻已到,它的狀態已經被修改為了未分配,因此 DHCP 服務器仍然將地址 1 分配給 d。

評測用例規模與約定

對於 20% 的數據,有 \(N\leq 200\),且 \(n\leq N\),且輸入僅含 Discover 報文,且 \(t<T_{min}\)

對於 50% 的數據,有 \(N\leq 200\),且 \(n\leq N\),且 \(t<T_{min}\),且報文的接收主機或為本機,或為 *;

對於 70% 的數據,有 \(N \leq 1000\),且 \(n\leq N\),且報文的接收主機或為本機,或為 *;

對於 100% 的數據,有 \(N\leq 10000\),且 \(n\leq 10000\),主機名的長度不超過 20,且 \(t,T_{min},T_{def},T_{max}\leq 10^9\),輸入的報文格式符合題目要求,且數字不超過 \(10^9\)

二、思路

估計大家一看到這個題目就嚇死了。這么長,一定不簡單吧?但是,你只要認真看了一遍題目,就會發現你其實只需要參考實現細節把代碼寫一遍就好了,所以這個模擬不難。
讓我們一步步的來。

-1.初始化

在 DHCP 啟動時,首先初始化 IP 地址池,將所有地址設置狀態為未分配,占用者為空,並清零過期時刻。
其中地址的狀態有未分配、待分配、占用、過期四種。
處於未分配狀態的 IP 地址沒有占用者,而其余三種狀態的 IP 地址均有一名占用者。
那很簡單,我們用結構體表示一下每個ip地址,存儲狀態、過期時間和占用者。

0.處理過期

處於待分配和占用狀態的 IP 地址擁有一個大於零的過期時刻。在到達該過期時刻時,若該地址的狀態是待分配,則該地址的狀態會自動變為未分配,且占用者清空,過期時刻清零;否則該地址的狀態會由占用自動變為過期,且過期時刻清零。處於未分配和過期狀態的 IP 地址過期時刻為零,即沒有過期時刻。
我們在每次輸入過后把時間跳躍到那個時間(數據保證了\(t_i<t_{i+1}\)),每次遍歷一次去找。

1.處理Discover報文

  1. 檢查是否有占用者為發送主機的 IP 地址:
    • 若有,則選取該 IP 地址;
    • 若沒有,則選取最小的狀態為未分配的 IP 地址;
    • 若沒有,則選取最小的狀態為過期的 IP 地址;
    • 若沒有,則不處理該報文,處理結束;
  1. 將該 IP 地址狀態設置為待分配,占用者設置為發送主機;
  2. 若報文中過期時刻為 0 ,則設置過期時刻為 \(t+T_{def}\);否則根據報文中的過期時刻和收到報文的時刻計算過期時間,判斷是否超過上下限:若沒有超過,則設置過期時刻為報文中的過期時刻;否則則根據超限情況設置為允許的最早或最晚的過期時刻;
  3. 向發送主機發送 Offer 報文,其中,IP 地址為選定的 IP 地址,過期時刻為所設定的過期時刻。

檢查是否有占用者為發送主機的IP地址,也是遍歷一遍,找到第一個就是了。
找未分配的和過期的,可以打包成一個函數,找指定的狀態,也是遍歷一遍。
第二步直接模擬即可。
第三步中的根據超限情況,只需要和最早時間選一個最大值,再和最晚時間選一個最小值。
第四步直接輸出。

2.處理Request報文

  1. 檢查接收主機是否為本機:
    • 若不是,則找到占用者為發送主機的所有 IP 地址,對於其中狀態為待分配的,將其狀態設置為未分配,並清空其占用者,清零其過期時刻,處理結束;
  1. 檢查報文中的 IP 地址是否在地址池內,且其占用者為發送主機,若不是,則向發送主機發送 Nak 報文,處理結束;
  2. 無論該 IP 地址的狀態為何,將該 IP 地址的狀態設置為占用;
  3. 與 Discover 報文相同的方法,設置 IP 地址的過期時刻;
  4. 向發送主機發送 Ack 報文。

第一步直接遍歷一遍,修改每一個。
第2~3步,照着寫。
第四步,和Discover的第三步一樣。
第五步直接輸出。

三、代碼

#include<bits/stdc++.h>
using namespace std;
struct IP{
	int sta;//0:未分配、1:待分配、2:占用、3:過期。
	string zhan;
	int gq;//過期
}a[10010];
int N,tdef,tmax,tmin,n,t;//地址池大小,一般延時,最長延時,最短延時
string h;//本機
string st,en,ty;//報文發送者、接受者和類型
int ip,ti,fp,gg;//IP地址和時間,分配的地址 ,計算過期時間
int finds(string s){//檢查是否有占用者為發送主機的 IP 地址
	for(int i=1;i<=N;i++){
		if(a[i].zhan==s) return i;
	}
	return 0;
}
int findn(int st){//選取最小的狀態為指定值的 IP 地址
	for(int i=1;i<=N;i++){
		if(a[i].sta==st) return i;
	}
	return 0;
}
void chuli(int t){//處理過期
	for(int i=1;i<=N;i++){
		if(a[i].gq&&a[i].gq<=t){//如果有過期時間,而且已經過期
			if(a[i].sta==1) a[i].sta=0,a[i].zhan="";//設成未分配
			else a[i].sta=3;//設成過期
			a[i].gq=0;
		}
	}
}
int main(){
	ios::sync_with_stdio(0);//防超時
	cin>>N>>tdef>>tmax>>tmin>>h>>n;//輸入
	while(n--){
		cin>>t>>st>>en>>ty>>ip>>ti;
		if(en!=h&&en!="*"&&ty!="REQ") continue;//如果這個是對別人的其他報文,那就與我們無瓜了
		if(ty!="REQ"&&ty!="DIS") continue;//其他種類報文不需要我們處理
		if(en=="*"&&ty!="DIS"||en==h&&ty=="DIS") continue;//如果是面向所有人的非Discover報文,或者是面向本機的Discover報文,均不符合格式
		chuli(t);//處理過期
		if(ty=="DIS"){//Discover報文
			fp=finds(st);//找到發送者的ip地址
			if(!fp) fp=findn(0);//找到未分配的ip地址
			if(!fp) fp=findn(3);//找到過期的ip地址
			if(!fp) continue;//都沒有,拒絕
			a[fp].sta=1;//設置
			a[fp].zhan=st;
			if(ti==0) a[fp].gq=t+tdef;//直接選擇
			else{
				gg=ti-t;//選擇界限
				gg=max(gg,tmin);
				gg=min(gg,tmax);
				a[fp].gq=t+gg;
			}
			cout<<h<<" "<<st<<" OFR "<<fp<<" "<<a[fp].gq<<endl;//offer報文
		}else{
			if(en!=h){//選擇了其他的服務器
				for(int i=1;i<=N;i++){
					if(a[i].zhan==st&&a[i].sta==1){
						a[i].sta=0,a[i].zhan="",a[i].gq=0;//清空所有有關ip
					}
				}
				continue;
			}
			if(ip>N||ip<1||a[ip].zhan!=st){//不存在的ip或者要別人地址
				//cout<<ip<<":"<<a[ip].zhan<<endl;
				cout<<h<<" "<<st<<" NAK "<<ip<<" "<<0<<endl;
			}else{
				a[ip].sta=2;//設為占用
				a[ip].zhan=st;//占用者
				if(ti==0) a[ip].gq=t+tdef;//和上面一樣
				else{
					gg=ti-t;
					gg=max(gg,tmin);
					gg=min(gg,tmax);
					a[ip].gq=t+gg;
				}
				cout<<h<<" "<<st<<" ACK "<<ip<<" "<<a[ip].gq<<endl;//設置成功
			}
		}
	}
	return 0;
}

四、總結

這個其實也沒啥好講的,就是一個純粹的模擬。
代碼也不長,只有80多行。
只能算是一個練碼量的小模擬吧,算一道好題,推薦在沒事干想刷題找回感覺的時候做。
順便慶祝題目和代碼使得本文超過了300行(

五、彩蛋

抱歉,這周一直忘了這件事(((
彩蛋就是:image
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM