Coding Poet, Coding Science

Keep Stupid, Keep Hungry


  • 归档

  • 分类

  • 标签

  • 资源

  • 关于

  • 搜索
  • 简体中文
  • English (US)
close
Coding Poet, Coding Science

套接字的基本概念与编程

发表于 2013-03-30 | 更新于 2016-12-14 | 分类于 计算机网络 , 编程 |

笔记说明: 本笔记属于linux程序设计第四版。第15章关于套接字的内容。貌似之前对于TCP的工作方式有些误解。现在了解了套接字之后才开始明白自己错在哪里了。

套接字是一种通信机制。在这种机制下面C/S系统的开发工作即可以在本地单位工作,也可以跨网络进行。

这个定义并不确切的原因是我从Windows下面开始接触。把UNIX套接字与网络套接字放在一起的做法还是觉得难以接受。毕竟在用途上相差太多了。但是我想事物自有它存在的道理吧。虽然我们都想给用的十分普遍的事物起一个恰当的名称,但是很多时候是不能如愿的。套接字服务就是这样。我们不知道它们可以用到哪些方面,只是觉得它是一种通信方法,至于为什么要这么设计,就不清楚了。

套接字的创建过程

首先服务器使用系统调用socket来创建一个套接字。它是一个系统分配给该服务器进程的一个类似于文件描述符的资源,它不能与其他进程共享。

在实现当中由于都是文件,所以套接字实际上相当于创建一个特殊文件并打开它,这与虚拟设备文件从创建到使用是一个道理。

其中,本地套接字是linux文件系统当中的文件名。一般放在/tmp目录下面。对于网络套接字,则是与客户连接的特定网络有关的服务标识符(端口号或者访问点)。这个标识符允许我们进入针对特殊端口号的连接转到正确的服务器进程。

系统调用bind用于给套接字命名。然后服务器进程就开始等待客户连接这个命名的套接字。系统调用listen的作用是创建一个队列并将其用于存放来自客户的进入连接。服务器通过accept系统调用来接受客户连接。

当服务器调用accept的时候,它就会创建一个与原来的命名套接字不同的新的套接字。这个新的套接字只用于同这个客户进行通信。而命名套接字则留下来继续处理来自其它客户的连接。一旦连接建立,我们就可以像底层的文件描述符一样利用套接字实现双向通信。

第二次创建新的套接字是自己以前没有想到的。现在想来,大概在网络环境下创建套接字的时候,由套接字函数库对于连接进行了封装,以致于在服务器调用accept的时候,只能得到连接到它的套接字,这也就是说,在内核当中已经处理好来自不同IP+PORT的请求。从而自己实现了数据的分流,最后才把数据交到socket函数库。

创建一个套接字的函数原型是:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

其中domain是套接字的类型,它可以是AF_UNIX,AF_INET,AF_INTE6,AF_IPX,AF_PACKET等等。type则是套接字的封包格式,比如SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等。我们使用的IP中TCP封包格式为AF_INET加SOCK_STREAM.而UNIX套接字则选择AF_UNIX加SOCK_STREAM.最后一个protocol通常取0.而返回的整型值是套接字描述符。

绑定一个套接字的函数原型是:

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的作用是将一个名称与套接字相绑定。之所以要进行绑定,是因为刚开始创建套接字的时候只是指定了在套接字命名空间中注册了这个事物。但是还没有被注册到一个确定的地址。所以我们通过bind函数向该套接字命名。名称由结构体addr指定,后面的addrlen是结构体的长度。在sockaddr当中,包含有关于套接字的一系列说明。

连接一个套接字的函数原型是:

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中的各项参数应当与前面所创建的套接字相匹配。返回动作的状态。

接受一个套接字的函数原型是:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

它返回一个新的针对于该客户端的套接字。

之后对套接字的读写使用read,write或者close调用就可以了。(因为套接字描述符属于文件描述符。)

Coding Poet, Coding Science

Lapack矩阵运算库

发表于 2013-03-18 | 更新于 2016-12-15 | 分类于 科学计算 |

所打印的《the lapacke interface to lapack》原来来自于大二时候学习使用LAPACK软件包,但是FORTRAN语言本身又比较复杂,因而寻找一个外在的接口函数库而找到的。不过事实上它没有发挥过什么作用,仅仅被看过一遍就被遗忘在角落里面了。现在为了把无用的东西都扔掉,整理一本笔记。

lapack原来是用FORTRAN语言写成的,后来C语言得到了广泛的应用,所以创建了面向C语言的接口,尽管可能完全重写更有可能得到更高的效率。于是到现在我们就得为了使用lapack而学习如何使用接口。

面向C语言的接口还分成两个层次。对于开发者来说,两个层次的最重要的区别是内存管理是否需要手工进行,对于调用者来说,则转化成命名的前缀与后缀的问题。其实较低级的程序员一直都很难避免陷于各种移植,各种前缀当中。

阅读全文 »
Coding Poet, Coding Science

Cups打印服务介绍

发表于 2013-03-03 | 更新于 2016-12-14 | 分类于 操作系统 |

摘自鸟哥的linux私房菜基础篇,来源自第二十一章,除此之外也参考了cups的官方手册。

发行版一般自带了相关的配置工具,不过随着unix上服务的标准化,现在unix的打印服务都只需要cups来管理了。现在新版的发行版,都是由cups负责打印。

unix上的打印一个是本地打印,另一个是网络打印。unix的支持都是很好的,并且都能通过cups简单地完成。

unix打印支持

要使用打印机,不仅要BIOS的支持,而且还需要unix系统的支持。其中的原因是,老式的打印机使用的是25针串口,此接口必须的BIOS中设为开启状态。至于系统,则是打印机制造商得提供相应的打印机驱动,unix才能使用该型号的打印机。

HP打印机对unix的支持程度很好,所以多选择HP品牌的打印机。

系统对于打印机的支持情况可以参考<www.linuxfoundation.org/en/OpenPrinting>里面的一个支持列表。

阅读全文 »
Coding Poet, Coding Science

周期性进程与Crontab

发表于 2013-02-17 | 更新于 2016-12-14 | 分类于 操作系统 |

周期性进程这一部分的内容需要很多的实践才能看出来。所以这里也就仅仅是学习周期性进程的管理方式,做一个理论上的理解。实际以后当然还是需要很多的锻练的。

系统管理员应当让更多的任务能够自动完成以减轻系统管理的压力。为此应当选择适当的辅助工具。首先是能提高管理效率的系统命令。比如使用adduser来代替手动向系统中添加用户。其次是使用各种各样的自动脚本完成管理任务。这种情况下就要建立所谓的系统管理工具集或脚本集之类的工具了。然后我们也应该使用自动运行或者计划任务,以让我们有一个充分的休息机会,让那些经常重复的工作变成计算机自动会完成的工作。

关于cron工具

unix下周期执行的任务一般由cron这个守护进程来处理。在系统工作的时候它通常都会保持运行。cron在工作的时修读取一个或多个配置文件,并启动sh来完成其中的任务。因此差不多所有能够手工从shell完成的任务都能通过cron完成。

cron最初出现在20世纪70年代的UNIX家族中。linux发行版本所带的cron称为ISC cron或者Vixie-cron.它是由Paul Vixie重写的,并提供了许多新的功能,减少了使用cron遇到的麻烦。

cron的配置文件称为crontab.cron在三个地方查找crontab文件。/var/spool/cron,在SUSE上是/var/spool/cron/tabs./etc/cron.d和/etc/crontab.每个用户的crontab文件都保存在/var/spool/cron目录下,以用户的名称命名。一般每个用户只能有一个crontab文件。cron会使用文件名作为运行命令时所使用的UID.

计划任务需要权限的限制,以使得普通用户多数时候只在其登录期间有对于计算机的使用权限。更多时候,计划任务作为系统管理的手段。

在/etc/cron.d以及/etc/crontab里的文件格式与用户的crontab文件略有不同,因为前者允许以任何身份执行命令。/etc/crontab供系统管理员手工维护,而/etc/cron.d里常存放软件包所需要的crontab项目。

cron启动时检查它的所有配置文件(但cron一般随系统的启动而启动),并把它们保存到内存当中。每一分钟cron就会醒来一次,检查crontab的修改时间,并重新载入有修改的文件。然后在返回睡眠状态前执行这一分钟安排要执行的所有任务。

cron通过syslog为它的活动做日志。

crontab文件的格式

crontab文件中以#开头的行代表注释。每个非注释行包括6或7个字段,它代表的含义为:

minute hour day month weekday [username] command

前六个字段都会用空白分开,但是到了command后,所有空格都算在command里面。用户自己的crontab文件中没有username项。

日期与时间格式会在很多地方用到,所以得有一个全局的思维才行。一般来说,首等应清楚表示的区间在一天以内还是一个比较长的日期。其次要确定所表示的地区。另外就是从大到小还是从小到大。我们可以这样进行分类:

  1. 时间内有时分秒,表达一天内的时间。次要的标准有时辰,早中晚等;
  2. 日期内有年月日。次要的还有星期,公元;
  3. 时区则是要遵守国际相关的规范了;
  4. 按表示顺序,有从大到小,也有从小到大,crontab里就是从小到大。

crontab每的时间字段中可以采用数值表示,可以使用通配符,可以用逗号,也可以用范围,表示的是“或”的关系。因为cron的含义即是这一分钟要不要运行该程序。crontab中的命令可以使用百分号作为换行。不过为了简便,用一个shell命令还是很好的。

更多细节见手册页crontab(5).

crontab的管理

crontab filename用于把文件安装为crontab文件。它将替换用户原来的版本。更多的用法参考crontab命令帮助。

特别提醒的是,直接使用crontab命令将会要求用户输入crontab.如果这时使用C-D会覆盖掉原来的配置。因此一般要使用C-C来终止crontab程序,以免用户覆盖。

/etc/cron.allow与/etc/cron.deny这两个配置文件用于限制用户提交crontab文件的权限。有allow的时候先通过allow里的用户,未通过则禁止用户登录。无allow文件时查deny里的用户。如果都没有,就会采用cron默认的设置。在大多数系统下,默认只允许root提交crontab.因此既不在allow又不在deny里的用户会被禁止提交crontab文件。

cron的这种设置与acl还不同。在acl中,中间过程只有肯定权,没有否定权。

分析crontab程序的权限也比较有意思。可以知道它是一个setuid的程序。实际运行的时候用户是root.

为了方便管理,通常还在/etc下建立了cron.daily,cron.weekly等脚本。里面的文件会自动每天或者每周运行一次(只需写好相应命令即可)。

cron的通常用法

使用cron非常要求对系统有某种程度的理解。在unix下文件的删除工作往往得自己完成。此外,在网络环境中分发配置文件也常通过cron完成。然后,有些日志文件得自己完成分割。

其它日程安排程序

其实日程安排要比日程安排程序更好理解一些。一个好的日程安排程序关键是能不能表达日程安排的某种逻辑。相关的替代软件有anacron和fcron等。

日程安排思考

计划任务在Windows下是和开机启动混合在一起的。但是unix经常很长时间不关机的,于是就采用了开机脚本和计划任务相分隔的做法。不过因为cron守护进程一直在运行,用户当然也能够很容易地编写一个脚本运行相关的任务。比如使用一个咨询文件就可以办到只在开机的时候运行一次。我们很显然能发现。一旦计划任务程序开始运行,只要相关的脚本能根据系统状态作出某种反应,把日程安排改造成开机启动也不是一件困难的事。

习题与思考

在此次学习中,我们并没有接触到cron的配置。因此关于性能的很多问题都没有涉及到。实际上如果同一网络多台主机同时运行cron时可能会造成一些问题。所以建议cron通过调用脚本间接调用程序。而脚本里有诸如sleep 5这样的代码。

关于管理crontab的权限

一般来说一个用户因为使用不合理的crontab文件。系统管理员应当删除其crontab文件并禁止它的crontab权限。

盲目地复制一个文件夹下以.开头的文件安全么

这个当然是不安全的。一个目录的inode里包含自己和它的父目录。如果我们这样复制文件,就会使得所复制的文件追溯到它的同级目录里。

系统分析的一个应用

cron经常会和邮件系统结合在一起。除了在完成计划任务时把输出发到用户的邮箱,它还可以让用户自己通过shell发送自己感兴趣的内容。

Coding Poet, Coding Science

Linux下的日志记录及其策略

发表于 2013-02-17 | 更新于 2016-12-14 | 分类于 操作系统 , 系统管理 |

本章标题是《系统日志与系统文件》。因为日志在系统当中的确占有非常重的地位。原来自己没有接触过关于syslog与logrotate等工具,所以在查看日志的时候用的只是非常基本的功能。但是按照自己的需求。以后自己也要学习更多的日志分析技巧,如果要学习网络的话。

日志通常如一条一条的概要,由某个应用程序产生并提交到特定的日志系统去处理。有的日志系统比较简单,或是由应用程序服务自动管理。但是更多的时候,需要在系统范围内才能分析出日志包含的重要信息。

日志文件因为它所反映的信息类型的不同而不同。因此需要各自对待。

虽然在操作系统编程与应用程序编程,或者网络编程中也常用到日志,但是日志的最核心的功能还是在于为系统或者应用管理员提供足够的信息,以便支持某种决策。这样一来,在编程中使用的日志只是提供一种基础设施。只在站在系统管理的水平上,我们才能利用好日志记录。

几种对待日志的策略

一般情况下应用程序已经配置好所产生的日志级别。一旦日志产生就进入到日志管理这一主题之下。通常情况下,日志可以分成保存的日志与立即的日志。后者又有立即扔掉与在内存区域中缓存,或者在终端上显示。前者则可以选择转发到另一地点或者在本地保存。

计算机管理系统环境下,一般产生意义的动作只是立刻扔掉,或者定期复位,或者采取轮换,或者将日志压缩或保存到永久介质上。它们的不同完全可以由日志信息存在与时间的函数关系决定:前者总是取零,定期复位使得可追溯的日志像周期函数一样变化;轮换则是一个单调递增,而后保持不变的函数;后者则是一个不断增加的函数。

在unix下无论何种方案,都必须采用cron自动维护日志文件。

关于扔掉日志

一般来说不应当扔掉所有的日志信息。因为日志文件首先为站点提供了非法入侵的重要证据。其次还有助于分析有关硬件或者软件方面的问题。《手册》中给出的意见是所有日志到少保存一个月的时间。特别是站点环境下,可能需要很长时间才能发现入侵。此时需要退回很长时间才可以找到入侵时间。并且还需要以压缩形式保存更长时间的日志。

不过有些时候保存日志的策略有一定的政治,法律或者商业上的原因。这就得具体问题具体分析了。不过留下日志总能够提供某种分析手段,至少能够使得对事物的认识更加具体。

轮换日志文件

这种做法通常是把每周或者每月的日志信息保留在一个单独文件里,就像维护一个长度有限的队列一样。

对于一个日志文件来说,其属主通常很重要,决定着日志管理时的权限使用。日志文件的命名可以使用序号区分,也可以使用日期。在unix上可以通过date命令的+%Y.%m.%d 格式就是通常的做法。

在轮换日志的时候可能会遇到日志正在使用的情况。这种情况下正在使用的日志文件会因为文件位置的改变而自动改变。通常的解决办法是在移动日志后重新启动守护进程,以让守护进程使用新的,空白的日志文件。

比如向syslogd发送HUP信号就会导至syslogd守护进程挂起然后重新读取日志。

存档日志文件

“除非就是要避免留下书面记录,否则就应该在常规备份中包含日志文件”。并且在转储频度允许的情况下以最高频度备份日志文件。此外。我们可以为日志文件专门设计一个备份脚本以直接对日志备份进行控制。

linux日志文件

linux系统的软件包一般将其日志记录到/var/log下的文件里。

/var目录是相当重要的。在正常运行的系统里应该都存在。因为里面还会随时写入很多信息,因此应当以读写方式挂载文件系统。

linux下大多数程序将其日志发送到syslog中央系统。由syslog负责将日志写入文件或者进行转发。总之,应用程序只需要把产生的日志信息提交到syslog就可以了。

日志权限需要管理员仔细衡量。这些权限中查看权限或许最为关键。一般而言黑客会特别照顾日志文件。大多数文件因此应当设置成权限600.

/var目录应当是所有用户都有权限访问的。但是用户不能在/var下删除log/目录。用户查看log/里面的内容也是允许的。但是用户不一定能查看具体某个应用程序的日志。

专门为日志文件建立一个组,以使得组里的其它成员有查看它的权限也是不错的。注意这里只是说日志属于某个组,而不是日志属于某个特殊用户。一个用户可以属于多个组,通常从组中得到的权限是各个组之和。

从一个具体的应用程序到日志文件的过程大致是,应用程序首先得使用syslog程序,这样以来就可以把日志提交到syslog系统。这一过程中得保证运行应用程序的当前用户或者进程有向syslog提交日志的权利。然后就是由syslog负责整理日志了。由于syslog是以root的身份执行,因此它总能创建出日志文件,后续的行为只需要chgrp命令修改日志所属组就可以了。特别是修改日志文件所属组的行为可以通过syslog完成。

系统日志的层次安排

/var/log下的常见日志

一般来说secure,auth.log与sudo.log文件都不应当由一般用户查看。除了读限制,应当禁止除了属主外的任何人对日志文件的写权限。

一般来说:

auth.log 由su等程序产生,负责授权
boot.log 系统启动脚本的输出
boot.msg 内核消息缓冲的存储
cron cron的执行情况与出错信息
cups 与打印有关的信息
daemon.log 所有与守护进程相关的信息
debug 调试输出
dmesg 内核输出消息的缓冲
dpkg.log 软件包管理日志
faillog 不成功的登录企图
messages 经常是系统日志文件
secure sshd,sudo等,保密的授权信息
syslog 主要的系统日志文件
warn 所有的警告级与出错级消息
wtmp 二进制记录的所有登录记录
yum.log 软件包管理日志

具体文件因系统不同而有差异,并且因使用的系统日志守护进程的不同而有异。

内核和启动日志

内核的日志机制是通过让内核把它的日志项保存在一个大小有限的内部缓冲区来做到的。缓冲区具有适当的大小,既足以容纳内核引导是产生的所有消息,又要节省内存用量。一旦系统启动完成,用户进程应当能够访问内核的日志缓冲,最终处理其内容。各个发行版本一般使用dmesg命令并将其输出重定向到/var/log/dmesg.(因发行版而异)。

内核当前运行的日志是通过klogd内核线程处理的。在正常运行方式下,klogd将内核产生的日志发送到一个文件或者syslog守护进程。一般情况下syslog依照kern中的规则处理这些日志,并将其发送到/var/log/messages文件。

dmesg与klogd都有一个选项可以设置内核控制台的日志级别。这实际上是同内核进行对话。如dmesg就使用了-n选项。内核的日志级别分为7级(第7级称为调试级,输出最多)。

启动脚本的日志可以没有内核日志机制做得好。

日志管理与分析工具

logrotate

logrotate用于管理日志文件,它的基本功能也就是作为一个过护进程,定期检测日志文件并对日志文件进行转储的操作。一般情况下在/etc/cron.daily下都可以发现logrotate这一个文件。也就是logrotate的计划任务。

原则上说logrotate应该在syslog之后讲述。不过我们可以把logrotate仅看成是一个定期分隔文件的程序而不去管它分隔的是怎样的文件类型,这才是它的功能的完整的应用领域。

前面我们说了logrotate在cron.daily下面定期运行。实际上在crontab中运行它的命令以及参数是

/usr/bin/logrotate /etc/logrotate.conf 2>&1 | tee $TMPF

所以我们知道它的功能是读取logrotate.conf文件并执行。

在该crontab文件下还执行的功能是,如果logrotate异常退出,就向syslog发送一个logrotate运行失败的cron.warning消息。

所以接下来就要学习logrotate.conf的配置了。

logrotate的主配置文件是logrotate.conf,但是在该文件中有一条命令是

include /etc/logrotate.d

所以在目录下才是针对每个程序的配置。主配置文件一般参考logrotate(8)。

针对某些文件的配置格式为:

${path_match} {
    ${rotate_command_list}
}

表示对某一个配置文件执行哪些操作。

实在不想再学习logrotate的配置文件了。又得学习一种配置语言。想写的时候参考实时手册页就可以了。

syslog日志分发程序

系统事件日志程序

syslog最初是由Eric Allman编写的一个综合日志记录系统。syslog具有两个重要的特性。首先它能让消息按照其来源和严重级别排序;其次是能够把消息送到各种目的地,包括日志文件,用户终端,甚至是其它计算机。这给予syslog以极大的灵活性。

尽管如此syslog也有一些缺点。所以已经开发出几种替代方案。比如syslog-ng.以及圣地亚哥超级计算中心的sdscsyslog.除此之外,SUSE上使用的是rsyslog.

syslog的体系结构

syslog由三个部分组成。其一是syslog守护进程,其二是库调用,其三是logger提交日志的命令。

syslog在系统启动后连续运行。懂得syslog的应用程序把日志发送到/dev/log文件,然后由syslog守护进程读取消自息并根据配置文件分发信息到目的地。syslog接受挂起信号并解释为重新打开配置文件;同时接受TERM信号并使守护进程退出。

syslog守护进程还会把它的进程PID写入到/var/run/syslogd.pid文件里。这使得向它发送信号变得比较容易,如: kill -HUP $(/bin/cat /var/run/syslogd.pid)

因为logrotate就经常要求syslog重新读取相关的日志文件。

守护进程使用/etc/syslog.conf配置文件。

其它的syslog程序通常也支持类似于syslog.conf配置文件里的语法。

配置文件的基本格式是:

$selector   $action

其中$selector的基本格式为$facility.$level.表示的含义是属于这一类别的某个等级的日志将会采取$action所指定的行为处理。$facility与$level都是由syslog规定好了的取值范围。一般情况下,比如cron代表来自cron的日志,daemon代表来自系统守护进程,而kern代表来自内核,local0-7代表来自本地的8种类型,user代表来自用户进程,syslog代表syslog内部消息。级别从低到高依次是debug, info, notice, warning, err, crit, alert, emerg.这些级别的具体含义由用户安排。

facility支持使用星号通配。

$action可以是一个本地文件名,或者@hostname,或者@ip,或者用户列表,或者fifoname,或者*(代表所有用户)。在$action前面加一个短划线代表写入文件后不执行sync命令。syslog对待一条日志的行为类似于一个匹配器。前面说到$selector的格式。实际上syslogd还支持语法糖的形式,允许一条匹配应用多个规则。分号表示取并区间。

具体来说$f.$l表示$l及以上,$f.=$l表示只匹配该级别, s1;s2表示选择器取并,在s1及s2之间。

syslog守护进程在启动时默认是不接收其它机器的消息的,除非是以-r选项启动的。并且syslog如果从网络接收到一条日志,无论符合哪一$selector都不会转发,除非使用了-h选项。开机时候的syslog选项可以从/etc/init.d/syslog里配置。

站点设计方案

在一个小型站点中重要的系统错误和警告保存在每台计算机的文件当中就可以了。但是在在个大型的网络中必须有中央日志记录。有了中央日志记录的话,大量的日志信息就会处于可管理的状态,而且运气好的话还能使破坏计算机安全的人无法访问到审计数据。(这要求站点级的防火墙禁止从外部向syslog提交信息。)

一般来说选择一台稳定的机器作为日志记录服务器,并且最好有很好的安全措施而且登录用户不多的一台机器。其他的计算机可以使用保存在中央主机上的通用配置文件。这样一来只需要维护两份不同的syslog.conf文件。

syslog服务器应当具有足够的安全性。其安全性可通过防火墙对于连接的控制,以及系统管理员访问的途径体现。

syslog应用

有许多程序或守护进程使用了syslog系统。并且通过logger命令,一般脚本也可以向syslog发送某些信息。如果要了解可详细的信息,可能需要查找POSIX相关规范。

logger的通常用法是

logger -p $selector "${messages}"

该命令可以用于调试syslog.方法是先在配置文件中添加一个指令选择器的地址,然后使用logger向该选择器写入指定的信息。

在应用的时候都忽略了权限的问题。但是我们应该知道让任何人都有发送日志的权利也是不应该的。

日志分析工具

比如swatch, logcheck, logwatch.

1…91011
Istyasna

Istyasna

GO FORTH now and create masterpieces of the publishing art!

55 日志
36 分类
189 标签
RSS
GitHub Aliyun
Creative Commons
Links
  • Homepage
  • Hainan University
  • Qi Qi
© 2016 - 2017 Istyasna
由 Hexo 强力驱动
主题 - NexT.Mist