试题背景
动态主机配置协议(Dynamic Host Configuration Protocol, DHCP)是一种自动为网络客户端分配 IP 地址的网络协议。当支持该协议的计算机刚刚接入网络时,它可以启动一个 DHCP 客户端程序。后者可以通过一定的网络报文交互,从 DHCP 服务器上获得 IP 地址等网络配置参数,从而能够在用户不干预的情况下,自动完成对计算机的网络设置,方便用户连接网络。DHCP 协议的工作过程如下:
- 当 DHCP 协议启动的时候,DHCP 客户端向网络中广播发送 Discover 报文,请求 IP 地址配置;
- 当 DHCP 服务器收到 Discover 报文时,DHCP 服务器根据报文中的参数选择一个尚未分配的 IP 地址,分配给该客户端。DHCP 服务器用 Offer 报文将这个信息传达给客户端;
- 客户端收集收到的 Offer 报文。由于网络中可能存在多于一个 DHCP 服务器,因此客户端可能收集到多个 Offer 报文。客户端从这些报文中选择一个,并向网络中广播 Request 报文,表示选择这个 DHCP 服务器发送的配置;
- DHCP 服务器收到 Request 报文后,首先判断该客户端是否选择本服务器分配的地址:如果不是,则在本服务器上解除对那个 IP 地址的占用;否则则再次确认分配的地址有效,并向客户端发送 Ack 报文,表示确认配置有效,Ack 报文中包括配置的有效时间。如果 DHCP 发现分配的地址无效,则返回 Nak 报文;
- 客户端收到 Ack 报文后,确认服务器分配的地址有效,即确认服务器分配的地址未被其它客户端占用,则完成网络配置,同时记录配置的有效时间,出于简化的目的,我们不考虑被占用的情况。若客户端收到 Nak 报文,则从步骤 1 重新开始;
- 客户端在到达配置的有效时间前,再次向 DHCP 服务器发送 Request 报文,表示希望延长 IP 地址的有效期。DHCP 服务器按照步骤 4 确定是否延长,客户端按照步骤 5 处理后续的配置;
在本题目中,你需要理解 DHCP 协议的工作过程,并按照题目的要求实现一个简单的 DHCP 服务器。
详细题目见链接:csp在线模拟网站
一些思路已经写在代码的注释里了:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define NOT_ASSIGN 1
#define WAIT_ASSIGN 2
#define OCCUPIED 3
#define IP_TIME_OUT 4
int N, Tdef, Tmax, Tmin, n;
char H[20];
struct Node_ip {
//地址的状态有未分配、待分配、占用、过期四种
int id; //1-4,分别对应上面四种情况
char host[20]; //处于未分配状态的 IP 地址没有占用者,而其余三种状态的 IP 地址均有一名占用者。
int timeout; //处于待分配和占用状态的 IP 地址拥有一个大于零的过期时刻
//在到达该过期时刻时,若该地址的状态是待分配,则该地址的状态会自动变为未分配,且占用者清空,过期时刻清零;否则该地址的状态会由占用自动变为过期,且过期时刻清零。
//处于未分配和过期状态的 IP 地址过期时刻为零,即没有过期时刻。
}DH_IP[10005]; //dh_ip[i]表示第i个ip的情况
void initial_dhcp(int N) //在 DHCP 启动时,首先初始化 IP 地址池,将所有地址设置状态为未分配,占用者为空,并清零过期时刻
{
for (int i = 1; i <= N; i++) //共N个ip地址
{
DH_IP[i].id = NOT_ASSIGN;
strcpy(DH_IP[i].host, "");
DH_IP[i].timeout = 0;
}
}
void Update_ip(int time)
{
//每次处理前要更新ip地址池状态,此时时间为time
//处于待分配和占用状态的 IP 地址拥有一个大于零的过期时刻。
//在到达该过期时刻时,若该地址的状态是待分配,则该地址的状态会自动变为未分配,且占用者清空,过期时刻清零;
//否则该地址的状态会由占用自动变为过期,且过期时刻清零。
//处于未分配和过期状态的 IP 地址过期时刻为零,即没有过期时刻。
for (int i = 1; i <= N; i++) //共N个ip地址
{
if (DH_IP[i].id == WAIT_ASSIGN)
{
if (time >= DH_IP[i].timeout)
{
DH_IP[i].id = NOT_ASSIGN;
strcpy(DH_IP[i].host, "");
DH_IP[i].timeout = 0;
}
}
else if (DH_IP[i].id == OCCUPIED)
{
if (time >= DH_IP[i].timeout)
{
DH_IP[i].id = IP_TIME_OUT;
DH_IP[i].timeout = 0;
}
}
}
}
void deal_discover(int time, char send[], char recv[], int ip, int time_out)
{
Update_ip(time);
int ip_select;
//1.检查是否有占用者为发送主机的 IP 地址
bool hint = false;
for (int i = 1; i <= N; i++)
{
if (strcmp(DH_IP[i].host, send) == 0)
{
ip_select = i;
hint = true;
break;
}
}
if(!hint)
{
//分配给它最小的尚未占用过的那个 IP 地址
bool find = false;
for (int i = 1; i <= N; i++)
{
if (DH_IP[i].id == NOT_ASSIGN)
{
ip_select = i;
find = true;
break;
}
}
if (!find) //如果未分配的ip不存在,则分配给它最小的此时未被占用的那个 IP 地址
{
for (int i = 1; i <= N; i++)
{
if (DH_IP[i].id == IP_TIME_OUT)
{
ip_select = i;
find = true;
break;
}
}
if (!find) //若没有,说明地址池已经分配完毕则不处理该报文,处理结束;
{
return;
}
}
}
//2.将该 IP 地址状态设置为待分配,占用者设置为发送主机;
DH_IP[ip_select].id = WAIT_ASSIGN;
strcpy(DH_IP[ip_select].host, send);
//host_to_ip[send] = ip_select;
//printf("-------------send = %s ip_select=%d\n", send, ip_select);
//3.若报文中过期时刻为 0 ,则设置过期时刻为 t+Tdef;
//否则根据报文中的过期时刻和收到报文的时刻计算过期时间,判断是否超过上下限:
//若没有超过,则设置过期时刻为报文中的过期时刻;
//否则则根据超限情况设置为允许的最早或最晚的过期时刻;
if (time_out == 0)
DH_IP[ip_select].timeout = time + Tdef;
else
{
int time_len = time_out - time;
if (time_len < Tmin)
DH_IP[ip_select].timeout = time + Tmin;
else if (time_len > Tmax)
DH_IP[ip_select].timeout = time + Tmax;
else
DH_IP[ip_select].timeout = time_out;
}
//4.向发送主机发送 Offer 报文,其中,IP 地址为选定的 IP 地址,过期时刻为所设定的过期时刻
printf("%s %s OFR %d %d\n", H, send, ip_select, DH_IP[ip_select].timeout);
return;
}
void deal_req(int time, char send[], char recv[], int ip, int time_out)
{
Update_ip(time);
//1.检查接收主机是否为本机:
//若不是,则找到占用者为发送主机的所有 IP 地址,对于其中状态为待分配的,将其状态设置为未分配,并清空其占用者,清零其过期时刻,处理结束;
if (strcmp(recv, H) != 0) //接受主机不是本机
{
for (int i = 1; i <= N; i++)
{
if (strcmp(DH_IP[i].host, send) == 0)
{
if (DH_IP[i].id == WAIT_ASSIGN)
{
DH_IP[i].id = NOT_ASSIGN;
strcpy(DH_IP[i].host, "");
DH_IP[i].timeout = 0;
}
}
}
return;
}
//2.检查报文中的 IP 地址是否在地址池内,且其占用者为发送主机,
//若不是,则向发送主机发送 Nak 报文,处理结束;
if (ip > N || ip < 1 || strcmp(DH_IP[ip].host, send) != 0)
{
printf("%s %s NAK %d %d\n", H, send, ip, 0);
return;
}
//3.无论该 IP 地址的状态为何,将该 IP 地址的状态设置为占用;
DH_IP[ip].id = OCCUPIED;
//4.与 Discover 报文相同的方法,设置 IP 地址的过期时刻;
if (time_out == 0)
DH_IP[ip].timeout = time + Tdef;
else
{
int time_len = time_out - time;
if (time_len < Tmin)
DH_IP[ip].timeout = time + Tmin;
else if (time_len > Tmax)
DH_IP[ip].timeout = time + Tmax;
else
DH_IP[ip].timeout = time_out;
}
//5.向发送主机发送 Ack 报文
printf("%s %s ACK %d %d\n", H, send, ip, DH_IP[ip].timeout);
return;
}
int main(void)
{
scanf("%d%d%d%d%s%d", &N, &Tdef, &Tmax, &Tmin, H, &n);
initial_dhcp(N);
char send[20], recv[20], type[10];
int ip, time_out, time;
for (int i = 0; i < n; i++)
{
scanf("%d %s %s %s %d %d", &time, send, recv, type, &ip, &time_out);
//若类型不是 Discover、Request 之一,则不处理
if (strcmp(type, "DIS") != 0 && strcmp(type, "REQ") != 0)
continue;
//判断接收主机是否为本机,或者为* ,若不是,则判断类型是否为 Request,若不是,则不处理;
if (strcmp(recv, H) != 0 && strcmp(recv, "*") != 0)
{
if (strcmp(type, "REQ") != 0)
continue;
}
//或接收主机是本机,但类型是 Discover,则不处理。
if (strcmp(recv, H) == 0 && strcmp(type, "DIS") == 0)
continue;
//若接收主机为* ,但类型不是 Discover,则不处理。
if (strcmp(recv, "*") == 0 && strcmp(type, "DIS") != 0)
continue;
//正常处理的情况
if (strcmp(type, "DIS") == 0)
deal_discover(time, send, recv, ip, time_out);
else
deal_req(time, send, recv, ip, time_out);
}
return 0;
}