Wireless Tools包含了一下工具:

    iwconfig:设置基本无线参数iwlist:扫描、列出频率,比特率,密钥等iwspy:获取每个节点链接的质量(只能查阅与之相连的节点)iwpriv:操作Wireless Extensions 特定驱动ifrename: 基于各种静态标准命名接口

大多数 Linux 发行版本都在其网络初始化脚本中集成Wireless Extension,以便启动时配置无线接口。

ifconfig

iwconfig是Linux Wireless Extensions(LWE)的用户层配置工具之一。LWE是Linux下对无线网络配置的工具,包括内核的支持、用户层配置工具和驱动接口的支持三部分
ifconfig用法:

wireless-tools中iwconfig的main函数,内容如下:

/******************************* MAIN ********************************//*------------------------------------------------------------------*/
/** The main !*/
int
main(int    argc,char **    argv)
{int skfd;     /* generic raw socket desc. */int goterr = 0;/* Create a channel to the NET kernel. */if((skfd = iw_sockets_open()) < 0){perror("socket");exit(-1);}/* No argument : show the list of all device + info */if(argc == 1)iw_enum_devices(skfd, &print_info, NULL, 0);else/* Special case for help... */if((!strcmp(argv[1], "-h")) || (!strcmp(argv[1], "--help")))iw_usage();else/* Special case for version... */if(!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version"))goterr = iw_print_version_info("iwconfig");else{/* '--' escape device name */if((argc > 2) && !strcmp(argv[1], "--")){argv++;argc--;}/* The device name must be the first argument */if(argc == 2)print_info(skfd, argv[1], NULL, 0);else/* The other args on the line specify options to be set... */goterr = set_info(skfd, argv + 2, argc - 2, argv[1]);}/* Close the socket. */iw_sockets_close(skfd);return(goterr);
}

iw_sockets_open 函数根据不同的协议创建对应的socket,以便和无线设备驱动进行交互。
iw_enum_devices函数,当输入的一个参入时,如果是网络接口,则输出网络接口状态信息。
print_info函数,获取参数信息传入后,打印出与参数对应的需求信息。
set_info 函数,设置参数输入后,进行ioctl操作,向无线设备驱动传参,并生效。


/*------------------------------------------------------------------*/
/** Enumerate devices and call specified routine* The new way just use /proc/net/wireless, so get all wireless interfaces,* whether configured or not. This is the default if available.* The old way use SIOCGIFCONF, so get only configured interfaces (wireless* or not).*/
void
iw_enum_devices(int     skfd,iw_enum_handler fn,char *      args[],int     count)
{char      buff[1024];FILE *    fh;struct ifconf ifc;struct ifreq *ifr;int       i;#ifndef IW_RESTRIC_ENUM/* Check if /proc/net/dev is available */fh = fopen(PROC_NET_DEV, "r");
#else/* Check if /proc/net/wireless is available */fh = fopen(PROC_NET_WIRELESS, "r");
#endifif(fh != NULL){/* Success : use data from /proc/net/wireless *//* Eat 2 lines of header */fgets(buff, sizeof(buff), fh);fgets(buff, sizeof(buff), fh);/* Read each device line */while(fgets(buff, sizeof(buff), fh)){char  name[IFNAMSIZ + 1];char *s;/* Skip empty or almost empty lines. It seems that in some* cases fgets return a line with only a newline. */if((buff[0] == '\0') || (buff[1] == '\0'))continue;/* Extract interface name */s = iw_get_ifname(name, sizeof(name), buff);if(!s){/* Failed to parse, complain and continue */
#ifndef IW_RESTRIC_ENUMfprintf(stderr, "Cannot parse " PROC_NET_DEV "\n");
#elsefprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n");
#endif}else/* Got it, print info about this interface */(*fn)(skfd, name, args, count);}fclose(fh);}else{/* Get list of configured devices using "traditional" way */ifc.ifc_len = sizeof(buff);ifc.ifc_buf = buff;if(ioctl(skfd, SIOCGIFCONF, &ifc) < 0){fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno));return;}ifr = ifc.ifc_req;/* Print them */for(i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++)(*fn)(skfd, ifr->ifr_name, args, count);}
}

其中iw_enum_handler是函数参数,传入了print_info函数:

/* Prototype for handling display of each single interface on the* system - see iw_enum_devices() */
typedef int (*iw_enum_handler)(int  skfd,char *   ifname,char *   args[],int  count);

iw_enum_devices中根据相应的情况,可能执行print_info来打印收集到的设备信息:


/*------------------------------------------------------------------*/
/** Print on the screen in a neat fashion all the info we have collected* on a device.*/
static int
print_info(int      skfd,char *   ifname,char *   args[],int      count)
{struct wireless_info  info;int           rc;/* Avoid "Unused parameter" warning */args = args; count = count;rc = get_info(skfd, ifname, &info);switch(rc){case 0: /* Success *//* Display it ! */display_info(&info, ifname);break;case -ENOTSUP:fprintf(stderr, "%-8.16s  no wireless extensions.\n\n",ifname);break;default:fprintf(stderr, "%-8.16s  %s\n\n", ifname, strerror(-rc));}return(rc);
}

get_info函数,可以从无线设备驱动中获取无线的配置参数和信息。
display_info函数,从get_info获取的无线配置参数和信息在display_info中进行选择输出。
其中wireless_info结构体定义如下

/* Structure for storing all wireless information for each device* This is pretty exhaustive... */
typedef struct wireless_info
{struct wireless_config    b;  /* Basic information */int       has_sens;iwparam   sens;           /* sensitivity */int       has_nickname;char      nickname[IW_ESSID_MAX_SIZE + 1]; /* NickName */int       has_ap_addr;sockaddr  ap_addr;        /* Access point address */int       has_bitrate;iwparam   bitrate;        /* Bit rate in bps */int       has_rts;iwparam   rts;            /* RTS threshold in bytes */int       has_frag;iwparam   frag;           /* Fragmentation threshold in bytes */int       has_power;iwparam   power;          /* Power management parameters */int       has_txpower;iwparam   txpower;        /* Transmit Power in dBm */int       has_retry;iwparam   retry;          /* Retry limit or lifetime *//* Stats */iwstats   stats;int       has_stats;iwrange   range;int       has_range;/* Auth params for WPA/802.1x/802.11i */int       auth_key_mgmt;int       has_auth_key_mgmt;int       auth_cipher_pairwise;int       has_auth_cipher_pairwise;int       auth_cipher_group;int       has_auth_cipher_group;
} wireless_info;

get_info函数的内容如下:


/************************* DISPLAY ROUTINES **************************//*------------------------------------------------------------------*/
/** Get wireless informations & config from the device driver* We will call all the classical wireless ioctl on the driver through* the socket to know what is supported and to get the settings...*/
static int
get_info(int            skfd,char *         ifname,struct wireless_info * info)
{struct iwreq      wrq;memset((char *) info, 0, sizeof(struct wireless_info));/* Get basic information */if(iw_get_basic_config(skfd, ifname, &(info->b)) < 0){/* If no wireless name : no wireless extensions *//* But let's check if the interface exists at all */struct ifreq ifr;strncpy(ifr.ifr_name, ifname, IFNAMSIZ);if(ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0)return(-ENODEV);elsereturn(-ENOTSUP);}/* Get ranges */if(iw_get_range_info(skfd, ifname, &(info->range)) >= 0)info->has_range = 1;/* Get AP address */if(iw_get_ext(skfd, ifname, SIOCGIWAP, &wrq) >= 0){info->has_ap_addr = 1;memcpy(&(info->ap_addr), &(wrq.u.ap_addr), sizeof (sockaddr));}/* Get bit rate */if(iw_get_ext(skfd, ifname, SIOCGIWRATE, &wrq) >= 0){info->has_bitrate = 1;memcpy(&(info->bitrate), &(wrq.u.bitrate), sizeof(iwparam));}/* Get Power Management settings */wrq.u.power.flags = 0;if(iw_get_ext(skfd, ifname, SIOCGIWPOWER, &wrq) >= 0){info->has_power = 1;memcpy(&(info->power), &(wrq.u.power), sizeof(iwparam));}/* Get stats */if(iw_get_stats(skfd, ifname, &(info->stats),&info->range, info->has_range) >= 0){info->has_stats = 1;}#ifndef WE_ESSENTIAL/* Get NickName */wrq.u.essid.pointer = (caddr_t) info->nickname;wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;wrq.u.essid.flags = 0;if(iw_get_ext(skfd, ifname, SIOCGIWNICKN, &wrq) >= 0)if(wrq.u.data.length > 1)info->has_nickname = 1;if((info->has_range) && (info->range.we_version_compiled > 9)){/* Get Transmit Power */if(iw_get_ext(skfd, ifname, SIOCGIWTXPOW, &wrq) >= 0){info->has_txpower = 1;memcpy(&(info->txpower), &(wrq.u.txpower), sizeof(iwparam));}}/* Get sensitivity */if(iw_get_ext(skfd, ifname, SIOCGIWSENS, &wrq) >= 0){info->has_sens = 1;memcpy(&(info->sens), &(wrq.u.sens), sizeof(iwparam));}if((info->has_range) && (info->range.we_version_compiled > 10)){/* Get retry limit/lifetime */if(iw_get_ext(skfd, ifname, SIOCGIWRETRY, &wrq) >= 0){info->has_retry = 1;memcpy(&(info->retry), &(wrq.u.retry), sizeof(iwparam));}}/* Get RTS threshold */if(iw_get_ext(skfd, ifname, SIOCGIWRTS, &wrq) >= 0){info->has_rts = 1;memcpy(&(info->rts), &(wrq.u.rts), sizeof(iwparam));}/* Get fragmentation threshold */if(iw_get_ext(skfd, ifname, SIOCGIWFRAG, &wrq) >= 0){info->has_frag = 1;memcpy(&(info->frag), &(wrq.u.frag), sizeof(iwparam));}
#endif  /* WE_ESSENTIAL */return(0);
}

可以看到通过iw_get_ext进行ioctl操作,并将获取的无线信息放入结构体iwreq,该结构体定义如下:

/** The structure to exchange data for ioctl.* This structure is the same as 'struct ifreq', but (re)defined for* convenience...* Do I need to remind you about structure size (32 octets) ?*/
struct  iwreq
{union{char    ifrn_name[IFNAMSIZ];    /* if name, e.g. "eth0" */} ifr_ifrn;/* Data part (defined just above) */union   iwreq_data  u;
};

iwconfig的无线设置,通过set_info函数来实现,该函数内容如下:


/*------------------------------------------------------------------*/
/** Set the wireless options requested on command line* Find the individual commands and call the appropriate subroutine*/
static int
set_info(int        skfd,       /* The socket */char *     args[],     /* Command line args */int        count,      /* Args count */char *     ifname)     /* Dev name */
{const iwconfig_cmd *  iwcmd;int           ret;/* Loop until we run out of args... */while(count > 0){/* find the command matching the keyword */iwcmd = find_command(args[0]);if(iwcmd == NULL){/* Here we have an unrecognised arg... Error already printed out. */return(-1);}/* One arg is consumed (the command name) */args++;count--;/* Check arg numbers */if(count < iwcmd->min_count)ret = IWERR_ARG_NUM;elseret = 0;/* Call the command */if(!ret)ret = (*iwcmd->fn)(skfd, ifname, args, count);/* Deal with various errors */if(ret < 0){int   request = iwcmd->request;if(ret == IWERR_GET_EXT)request++;  /* Transform the SET into GET */fprintf(stderr, "Error for wireless request \"%s\" (%X) :\n",iwcmd->name, request);switch(ret){case IWERR_ARG_NUM:fprintf(stderr, "    too few arguments.\n");break;case IWERR_ARG_TYPE:if(errarg < 0)errarg = 0;if(errarg >= count)errarg = count - 1;fprintf(stderr, "    invalid argument \"%s\".\n", args[errarg]);break;case IWERR_ARG_SIZE:fprintf(stderr, "    argument too big (max %d)\n", errmax);break;case IWERR_ARG_CONFLICT:if(errarg < 0)errarg = 0;if(errarg >= count)errarg = count - 1;fprintf(stderr, "    conflicting argument \"%s\".\n", args[errarg]);break;case IWERR_SET_EXT:fprintf(stderr, "    SET failed on device %-1.16s ; %s.\n",ifname, strerror(errno));break;case IWERR_GET_EXT:fprintf(stderr, "    GET failed on device %-1.16s ; %s.\n",ifname, strerror(errno));break;}/* Stop processing, we don't know if we are in a consistent state* in reading the command line */return(ret);}/* Substract consumed args from command line */args += ret;count -= ret;/* Loop back */}/* Done, all done */return(0);
}

iwconfig_cmd结构体定义了一个数组,用来存放了无线设置项的固定参数名,以及参数类型,和设置调用函数。
find_command函数通过对比已存的iwconfig_cmd结构体数组,来匹配需要设置的无线参数。

iwconfig_cmd定义的数组如下:

static const struct iwconfig_entry iwconfig_cmds[] = {{ "essid",        set_essid_info,     1,  SIOCSIWESSID,"Set ESSID",            "{NNN|any|on|off}" },{ "mode",     set_mode_info,      1,  SIOCSIWMODE,"Set Mode",         "{managed|ad-hoc|master|...}" },{ "freq",     set_freq_info,      1,  SIOCSIWFREQ,"Set Frequency",        "N.NNN[k|M|G]" },{ "channel",      set_freq_info,      1,  SIOCSIWFREQ,"Set Frequency",        "N" },{ "bit",      set_bitrate_info,   1,  SIOCSIWRATE,"Set Bit Rate",         "{N[k|M|G]|auto|fixed}" },{ "rate",     set_bitrate_info,   1,  SIOCSIWRATE,"Set Bit Rate",         "{N[k|M|G]|auto|fixed}" },{ "enc",      set_enc_info,       1,  SIOCSIWENCODE,"Set Encode",           "{NNNN-NNNN|off}" },{ "key",      set_enc_info,       1,  SIOCSIWENCODE,"Set Encode",           "{NNNN-NNNN|off}"  },{ "power",        set_power_info,     1,  SIOCSIWPOWER,"Set Power Management",     "{period N|timeout N|saving N|off}" },
#ifndef WE_ESSENTIAL{ "nickname",     set_nick_info,      1,  SIOCSIWNICKN,"Set Nickname",         "NNN" },{ "nwid",     set_nwid_info,      1,  SIOCSIWNWID,"Set NWID",         "{NN|on|off}" },{ "ap",       set_apaddr_info,    1,  SIOCSIWAP,"Set AP Address",       "{N|off|auto}" },{ "txpower",      set_txpower_info,   1,  SIOCSIWTXPOW,"Set Tx Power",         "{NmW|NdBm|off|auto}" },{ "sens",     set_sens_info,      1,  SIOCSIWSENS,"Set Sensitivity",      "N" },{ "retry",        set_retry_info,     1,  SIOCSIWRETRY,"Set Retry Limit",      "{limit N|lifetime N}" },{ "rts",      set_rts_info,       1,  SIOCSIWRTS,"Set RTS Threshold",        "{N|auto|fixed|off}" },{ "frag",     set_frag_info,      1,  SIOCSIWFRAG,"Set Fragmentation Threshold",  "{N|auto|fixed|off}" },{ "modulation",   set_modulation_info,    1,  SIOCGIWMODUL,"Set Modulation",       "{11g|11a|CCK|OFDMg|...}" },
#endif  /* WE_ESSENTIAL */{ "commit",       set_commit_info,    0,  SIOCSIWCOMMIT,"Commit changes",       "" },{ NULL, NULL, 0, 0, NULL, NULL },
};

以设置ESSID为例,最后调用了set_essid_info函数,并导入了参数SIOCSIWESSID,内容如下:

/*********************** SETTING SUB-ROUTINES ***********************/
/** The following functions are use to set some wireless parameters and* are called by the set dispatcher set_info().* They take as arguments the remaining of the command line, with* arguments processed already removed.* An error is indicated by a negative return value.* 0 and positive return values indicate the number of args consumed.*//*------------------------------------------------------------------*/
/** Set ESSID*/
static int
set_essid_info(int      skfd,char *       ifname,char *       args[],     /* Command line args */int      count)      /* Args count */
{struct iwreq      wrq;int           i = 1;char          essid[IW_ESSID_MAX_SIZE + 1];int           we_kernel_version;if((!strcasecmp(args[0], "off")) ||(!strcasecmp(args[0], "any"))){wrq.u.essid.flags = 0;essid[0] = '\0';}elseif(!strcasecmp(args[0], "on")){/* Get old essid */memset(essid, '\0', sizeof(essid));wrq.u.essid.pointer = (caddr_t) essid;wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;wrq.u.essid.flags = 0;if(iw_get_ext(skfd, ifname, SIOCGIWESSID, &wrq) < 0)
      return(IWERR_GET_EXT);wrq.u.essid.flags = 1;}else{i = 0;/* '-' or '--' allow to escape the ESSID string, allowing* to set it to the string "any" or "off".* This is a big ugly, but it will do for now */if((!strcmp(args[0], "-")) || (!strcmp(args[0], "--"))){if(++i >= count)
          return(IWERR_ARG_NUM);}/* Check the size of what the user passed us to avoid* buffer overflows */if(strlen(args[i]) > IW_ESSID_MAX_SIZE){errmax = IW_ESSID_MAX_SIZE;
        return(IWERR_ARG_SIZE);}else{int     temp;wrq.u.essid.flags = 1;strcpy(essid, args[i]); /* Size checked, all clear */i++;/* Check for ESSID index */if((i < count) &&(sscanf(args[i], "[%i]", &temp) == 1) &&(temp > 0) && (temp < IW_ENCODE_INDEX)){wrq.u.essid.flags = temp;++i;}}}/* Get version from kernel, device may not have range... */we_kernel_version = iw_get_kernel_we_version();/* Finally set the ESSID value */wrq.u.essid.pointer = (caddr_t) essid;wrq.u.essid.length = strlen(essid);if(we_kernel_version < 21)wrq.u.essid.length++;if(iw_set_ext(skfd, ifname, SIOCSIWESSID, &wrq) < 0)
    return(IWERR_SET_EXT);/* Var args */
  return(i);
}

可以看到最后调用iw_set_ext函数,通过iwreq结构,实现对无线设备驱动的ioctl操作。

iwreq结构体中的成员u,是联合体iwreq_data,存放了获取和设置的参数,定义如下:


/* ------------------------ IOCTL REQUEST ------------------------ */
/** This structure defines the payload of an ioctl, and is used * below.** Note that this structure should fit on the memory footprint* of iwreq (which is the same as ifreq), which mean a max size of* 16 octets = 128 bits. Warning, pointers might be 64 bits wide...* You should check this when increasing the structures defined* above in this file...*/
union   iwreq_data
{/* Config - generic */char        name[IFNAMSIZ];/* Name : used to verify the presence of  wireless extensions.* Name of the protocol/provider... */struct iw_point essid;      /* Extended network name */struct iw_param nwid;       /* network id (or domain - the cell) */struct iw_freq  freq;       /* frequency or channel :* 0-1000 = channel* > 1000 = frequency in Hz */struct iw_param sens;       /* signal level threshold */struct iw_param bitrate;    /* default bit rate */struct iw_param txpower;    /* default transmit power */struct iw_param rts;        /* RTS threshold threshold */struct iw_param frag;       /* Fragmentation threshold */__u32       mode;       /* Operation mode */struct iw_param retry;      /* Retry limits & lifetime */struct iw_point encoding;   /* Encoding stuff : tokens */struct iw_param power;      /* PM duration/timeout */struct iw_quality qual;     /* Quality part of statistics */struct sockaddr ap_addr;    /* Access point address */struct sockaddr addr;       /* Destination address (hw/mac) */struct iw_param param;      /* Other small parameters */struct iw_point data;       /* Other large parameters */
};

联合体iwreq_data中,结构体iw_point用来存放16位的参数信息,结构体iw_quality用来存放连接质量信息,其他信息使用结构体iw_param

wireless-tools源码分析-iwconfig相关推荐

  1. Spring Developer Tools 源码分析:二、类路径监控

    在 Spring Developer Tools 源码分析一中介绍了 devtools 提供的文件监控实现,在第二部分中,我们将会使用第一部分提供的目录监控功能,实现对开发环境中 classpath ...

  2. kazoo源码分析:Zookeeper客户端start概述

    kazoo源码分析 kazoo-2.6.1 kazoo客户端 kazoo是一个由Python编写的zookeeper客户端,实现了zookeeper协议,从而提供了Python与zookeeper服务 ...

  3. kubeadm源码分析(内含kubernetes离线包,三步安装)

    k8s离线安装包 三步安装,简单到难以置信 kubeadm源码分析 说句实在话,kubeadm的代码写的真心一般,质量不是很高. 几个关键点来先说一下kubeadm干的几个核心的事: kubeadm ...

  4. 《深入理解Spark:核心思想与源码分析》——1.2节Spark初体验

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章,第1.2节Spark初体验,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 ...

  5. soundtouch源码分析__based on csdn :

    1. soundtouch介绍和相关资源 The SoundTouch Library Copyright © Olli Parviainen 2001-2014 SoundTouch is an o ...

  6. linux nDPI 协议检测 源码分析

    关于nDPI的基本功能就不在这介绍了,有兴趣了解的读者可以阅读官方的快速入门指南:https://github.com/ntop/nDPI/blob/dev/doc/nDPI_QuickStartGu ...

  7. java 源码分析_Java 源代码编译成 Class 文件的过程分析

    原标题:Java 源代码编译成 Class 文件的过程分析 在上篇文章< Java三种编译方式:前端编译 JIT编译 AOT编译 >中了解到了它们各有什么优点和缺点,以及前端编译+JIT编 ...

  8. 【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )

    文章目录 一.添加应用源码分析 1.LaunchpadAdapter 适配器 2.适配器添加元素 3.PackageAppData 元素 一.添加应用源码分析 1.LaunchpadAdapter 适 ...

  9. zg手册 之 python2.7.7源码分析(1)-- python中的对象

    为什么80%的码农都做不了架构师?>>>    源代码主要目录结构 Demo: python 的示例程序 Doc: 文档 Grammar: 用BNF的语法定义了Python的全部语法 ...

最新文章

  1. 【实用快捷键】设置WebStorm中Show in Explorer(在资源管理器中打开)快捷键Alt+Shift+R(类似VSCode)
  2. 服务器ping你可以ping通,你ping服务器ping不同的解决方案!!
  3. centos7 搭建FastDFS服务器
  4. Windows页目录自映射方案
  5. Java 语言中 Enum 类型的使用介绍
  6. java 语言概述(零)
  7. 批处理命令 / rem :: :
  8. Kafka端到端审计
  9. Servlet到底是个什么东西???【【博采众长】】
  10. 雅可比行列式_夏七八写:关于“斜二测画法”与雅可比行列式的关系的想法
  11. dbnetlib sqlserver不存在或拒绝访问_SQL Server数据库损坏和修复
  12. 泛泰A860(高通公司8064 cpu 1080p) 拂4.4中国民营recovery TWRP2.7.1.2文本(通过刷第三版)...
  13. English trip -- VC(情景课)2 D Reading
  14. RabbitMQ之五种消息模型
  15. Lattice FPGA 开发工具Diamond使用流程总结——工具使用
  16. 使用jwPlayer播放视频中的某一段
  17. 【有限元分析】有限元仿真分析与解析解的结果对比——以阶梯轴的静力分析为例
  18. java手机验证码注册_Java手机验证码注册
  19. java抖音字符视频_java爬取新版抖音无水印视频教程(2020/09/14附带java代码)
  20. safari 插件(如Xmarks)的设置、登陆、禁用等

热门文章

  1. 赵小楼《天道》《遥远的救世主》深度解析(131)权利不是支配别人,而是可以选择自己不想做的
  2. X32C语言专项练习29
  3. 喝茶让你降三高之“桑玉茶”
  4. 中医药文化进校园进课堂之河南省实验小学
  5. Debian/Ubuntu下的网卡绑定-四网卡双bond
  6. 浅谈电气防火限流式保护器在养老福利单位的应用
  7. 汇编显示ASCII码表
  8. 雷达视频信号采集回放系统
  9. Codeforces 742A Arpa’s hard exam and Mehrdad’s naive cheat 打表+水题
  10. 什么是服务器信息,什么是服务器地址信息