在 UNIX 系统发展应用程式入门
王志祥
序言:
同学们可能或多或少在网路上抓过UNIX 或 X 视窗的应用程式,
你是否曾想过这些程式是如何写的?或者更进一步,你是否也想自己写一个
大家都有兴趣去用的程式?不但可出名又可训练自己的程式设计能力.
在 DOS 之下,可能大部分的人写应用程式都没问题.可是换到 UNIX
之後可能就因不熟悉而不会写了.本文即是以笔者上学期所写之 UNIX
应用程式: FindMan 作为□例,期收抛砖引玉之效.
关於 FindMan:
FindMan 是一集合 FindMan 、ScanGirl 、CScanGirl 三项 Utility
为一体的程式. 当初发展 FindMan 的动机是由於上机人数太多,往往为了
找一个人而伤眼睛地用力看 rusers 的输出.於是开始写 FindMan . 而当时正逢
HuntGirl(u8011093) 、Hunt(u7917043) 相继推出,由於正合交大大多数男生
的需求,因此造成轰动.本程式为了顺应潮流,加入了 ScanGirl
及 CScangirl ( ScanGirl 中文版 ). 由於处理程序类似, 因此合并成一个程式,
而以 link(UNIX Command: ln) 连结各项程式,程式再以第一个字母判断该执行何项程序.
FindMan 自开放使用以来,目前每个月皆被使用了 6000 次以上. 其中有一个
有趣现象, 最初一个月, ScanGirl 的使用率是 FindMan 的两倍,後来 FindMan 逐
渐追上 ScanGirl, 现在 FindMan : ScanGirl 的使用率是 5:1 (FindMan 5xxx 次).
这现象似乎显示交大人已不在如以前那样喜欢和女孩子 talk 了.
有关在 UNIX 上写应用程式的技巧和注意事项:
在 UNIX 上写应用程式与在 DOS 下写程式不一样的地方有几点:
1. UNIX 的 Signal 相当於 DOS 的 Interrupt.
2. UNIX 有许多系统呼叫,可以做低阶的动作.
3. UNIX 是多工的作业系统,我们可善用 UNIX 的这项能力.
4. UNIX 有程序间通讯的能力, 它可做各多工程序间、甚至各工作站间的沟通.
当然, UNIX 的功能不只这 4 点, 读者以後可以後慢慢体会.
FindMan 的 Initial 部份:
由於 FindMan 主程式太长(共 10 页),而且其中有些部份如不了解就不要使用,
否则系统可能会 ...,而你的 account 就可能会 ...,(当然,此一部份我会特别注明.)
所以,我只会附上重要部份的程式.
以下我会列出程式片段, 与比较奇特的变数宣告. 读者们最好遇到没看
过的命令时, 在 UNIX 里用 man 查.
struct passwd *p;
uid_t uid;
uid = getuid();
if (!(p = getpwuid(uid))) {
printf(\"Can\"t get user name !!\\n\");
exit(1);
}
strcpy(uname,p->pw_name);
gethostname(hsnm,sizeof(hsnm));
if ( getlogin() == NULL ) {
printf(\"This program is avai ....... is program..... \\n\");
exit(1);
}
以上此部份是取得使用者的帐号(account) 与执行 FindMan 时的工作站名称.
并判断使用者是否真的在线上(OnLine), 以 getlogin() 判断使用者若不在线上,
则不让不在线上的使用者执行. 此法可防止使用者使用 rsh、at、cron 等命令.
再来是以执行档第一个英文字判断该 show 哪一种讯息、该执行哪一种程
式(记录在一个变数里,以此变数判断). 此部份简单,不再赘述.
接著是程式後的参数判断,程式如下:
while ((n = getopt(argc,argv,\"af:h\")) != EOF)
switch(n) {
case \"a\":
aflg++;
break;
case \"f\":
fflg++;
hostfl=optarg;
break;
case \"h\":
if (aflg || fflg || lflg || Hflg)
errflg++;
else
hflg++;
break;
default:
errflg++;
}
if (errflg) {
printf(\"\\nUsage: %s [-a] [-h] [-H] [-f FindHostName\"s FILE]\\n\",argv[0]);
printf(\" -h : short help\\n\");
printf(\" -H : detail help\\n\");
exit(1);
}
if (hflg) {
printf(\"\\nUsage: %s [-a] [-h] [-H] [-f FindHostName\"s FILE]\\n\",argv[0]);
exit(0);
}
程式中的 getopt() 之中的 \"af:h\" 是参数的字. 其中的 \":\" 表示参数
之後还要加的东东. 例如 -f 之後还要加 FindHostName\"s FILE, 所以 f 之後要有 \":\".
附带一提,这里所介绍的功能,虽然 FindMan 也会 Show 出,但大部分皆为保留功能,
并不能使用, 必须等我有空实在加上.届时我会 announce.
这部份所用的技巧可使程式能提供使用者多样的选择.程式中的 aflg、fflg
等皆为旗标变数. 它可记录参数的状况.
再下来是根据变数决定程式要读取的 database(有 .look 及我的中英文
girl database), 并记录到我的记录档里.此部份程式亦略去不提.
接下来是 UNIX 的 Signal 之运用和子程序的建立. 当初之所以使用 fork
建立许多子程序并使用复杂且吃 Load 的程序间通讯, 是因为如此可以家快速度.
由於有时会有一些工作站当掉或无法回应 rusers 的 request,这些工作站即会
延迟 rusers □集资料的时间. 我的程式即可部份解决这个问题. 由主程序 fork 出
去的各子程序各自 rusers 一个工作站,先得到资料的程序先向主程序传送.
(程序间传送需用程序间通讯的技巧) 如此就可不管那些不传资料的工作站了.
所以我的 FindMan 常不按顺序 show 各工作站的资讯後,停了很久才结束便是
这个道理. 知道原理後,下次看到讯息出完停下时,不必苦等而可以 Ctrl-C 取
而代之. 现在由於计中的 rusers 改良过而不必再苦等.因此利用此技巧取代
复杂且耗 Load 的程序间通讯便成为我的下一个课题.
□话少说,看下面的程式:
signal(SIGCLD, SIG_IGN);
signal(SIGINT, timeout);
for (i=0; i
if ((pid = fork()) == 0) {
pitch(hosts[i]);
break;
}
}
if (pid != 0)
{
tmpp[0]=(char)0;
tmpp[1]=\"\\0\";
catch(tmpp);
if ((short)tmpp[0]) {
printf(\"There are %d workstation responding fail...\\n\",tmpp[0]);
printf(\" Fail workstation: %s\\n\",&tmpp[1]);
}
前两个 Signal,第一个的意义是忽略子程序结束的讯号.而第二个的意义是
遇到 Ctrl-C 之类的中断时要执行 timeout() 函数.而底下则是用 fork() 产生
子程序,有关 fork() 的意义与用法,请自行参考资料. 程式中的 catch() 是我定义
的函数,下次再说.而 tmpp[0] 则是用来判断工作站是否有 respond.
结语:
由於篇幅及时间因素,本文在此告一段落 ,下期再继续.若有任何建议,欢迎用
E-Mail 寄到 u7911093@cc.nctu.edu.tw .
在 UNIX 系统发展应用程式入门 (二) 王志祥
取得 User 资料的 pitch 函数:
上其的 FindMan 的 Initial 部份提到两个 User Define 的函数,
一个是 catch, 另一个即是 pitch. Initial 部份 fork 的二十个程序都呼叫
pitch 函数向二十台工作站收集 User login 的资料并筛选出使用者所需的
资料, 先整理出来的程序便先向主程序要求传送资料, 传送以 Socket 完成.
有关 Socket 的部份以後再谈. 以下是 pitch 函数的部份程式:
pitch(host)
char *host;
{
int bufsize;
int ps, j, k, l;
char buffer[BUFSIZE];
int count;
struct utmpidlearr up;
int tmp, gcount, gidx[25];
char gname[25][80];
char line[25][9];
char name[25][9];
char hhost[25][17];
long time[25];
unsigned idle[25];
char temp[80];
up.uia_arr=(struct utmpidle **) malloc(25*sizeof(struct utmpidle *));
: : :
: : :
/* 此处省略要求 Connect 的 Socket 之 Initial 部份. */
: : :
: : :
strcpy(buffer,host);
k=strlen(host);
strcpy(&buffer[k],\":\\n\");
k+=2;
if ( rusers(host, &up) == 0 ) {
count = up.uia_cnt;
buffer[k++] = (char) count;
for ( j=0; j
strncpy(line[j],up.uia_arr[j]->ui_utmp.ut_line,8);
line[j][8]=\"\\0\";
strncpy(name[j],up.uia_arr[j]->ui_utmp.ut_name,8);
name[j][8]=\"\\0\";
strncpy(hhost[j],up.uia_arr[j]->ui_utmp.ut_host,16);
hhost[j][16]=\"\\0\";
time[j]=up.uia_arr[j]->ui_utmp.ut_time;
idle[j]=up.uia_arr[j]->ui_idle;
}
if (count != 0) {
gcount=check(count,name,gname,gidx);
buffer[k++] = (char) gcount;
if (gcount !=0)
for (j=0; j
strcpy(&buffer[k],gname[j]);
k+=strlen(gname[j]);
do {
tmp=gidx[j++];
if ( j > gcount )
break;
buffer[k++]=\"\\t\";
strcpy(&buffer[k],line[tmp]);
k+=strlen(line[tmp]);
buffer[k++]=\"\\t\";
if ((l=idle[tmp]/60) != 0)
sprintf(temp,\"%d:%d\\t\",l,(idle[tmp]%60));
else
sprintf(temp,\"%d\\t\",(idle[tmp]%60));
strcpy(&buffer[k],temp);
k+=strlen(temp);
strcpy(temp,asctime(localtime(&time[tmp])));
strcpy(&buffer[k],temp);
k+=(strlen(temp) - 6);
buffer[k++]=\"\\t\";
strcpy(&buffer[k],hhost[tmp]);
k+=strlen(hhost[tmp]);
buffer[k++]=\"\\n\";
} while (strcmp(name[tmp],\"repeat\") == 0);
}
}
buffer[k++]=\"\\0\";
}
else {
count = -1;
buffer[k++] = (char) count;
}
: : :
: : :
/* 此处省略要求 Connect 的 Socket 之传送资料部份. */
: : :
: : :
}
其中 rusers 的部份可在 CCSUN 用 \" man 3 rusers \" 查, 兹摘录
如下:
==================> man 3 rusers 的摘录 <==============
NAME
rnusers, rusers - return information about users on remote
machines
DESCRIPTION
rnusers() returns the number of users logged on to host (-1
if it cannot determine that number). rusers() fills the
utmpidlearr structure with data about host, and returns 0 if
successful.
PROGRAMMING
#include <=== 查 struct 的地方
rnusers(host)
char *host
rusers(host, up)
char *host
struct utmpidlearr *up;
==========================================================
由於以上的 manual 中并没有指出 utmpidlearr 的结构型态如何,
因此我们必须列出我们所需要的 head file 资料以取得我们要的
utmpidlearr 结构. 以下是我的搜寻所得:
==================> utmpidlearr 重要资料 <=================
struct utmpidle {
struct utmp ui_utmp;
unsigned ui_idle;
};
struct utmpidlearr {
struct utmpidle **uia_arr;
int uia_cnt;
};
struct utmp {
char ut_line[8]; /* tty name */
char ut_name[8]; /* user id */
char ut_host[16]; /* host name, if remote */
long ut_time; /* time on */
};
============================================================
有了这些资料以後, 再看前面的程式就比较易懂了. ( 若因程式的
变数命名太差, 或流程太混乱, 请见谅. 因这一部份在完成之初又不断加新
的功能, 而不是一次完成的. )
程式中的 buffer 这个变数储存欲送出的资料, 其格式为:
==================================================================
工作站名 login 人数 符合资料的人数 帐号及其上机状态
any bytes 1 bytes 1 bytes any bytes
==================================================================
以下面 FindMan 的输出为例说明:
=====================> FindMan 部份输出 <=====================
ccsun7:
u813xx07 -----> xxxxxxxxxxx
ttyq0 0 Tue Apr 27 23:56:53 xxxuling.Dorm10.
ttyp1 0 Wed Apr 28 01:46:29 xxxuling.Dorm10.
u802xx23 Chen2 xxxxxxxx
ttyr6 31:47 Mon Apr 26 18:47:17 140.113.1xx.177:
ccsun16:
u813xx16 -----> xxxxxxxxxxx
ttyp1 0 Wed Apr 28 01:46:29 xxxmy.DormChu.NC
==============================================================
其中工作站名不限 byte 数, 接收端以 \":\\n\" 判断此部份的资料
结束与否. 当第二部份为 0 时即填入 -1 表示无人, 与资料比对後找不到
人亦如此. check 函数即是处理与资料比对的工作, FindMan 是与 .look
比对, ScanGirl 与 girl 的资料档比对. check 的函数传回 gcount、
gname、gidx 三个变数. gcount 即是 match 的人数, gname 是从 rusers
传回的资料档稍作修改 ( 重复的帐号改成 \"repeat\" ) 而成. 而 gidx 是
rusers 传回资料的排序後的 index.
从 check 函数传回的 gname、gidx 构成 buffer 阵列最後的资料.
即 \"帐号及其上机状态\" 部份. 全部完成後即将资料送出. 由於 check 函数
并不难写, 故不列出.
由於篇幅及时间所限, 这次只讲 pitch 函数, 其它以後再继续,
下期见!! 8-)
—————————————————————————————————
FindMan 关闭启示:
近来 CCSUN 的使用率大幅增加 , 使得 CCSUN
的 Load 高居不下, 我的 FindMan 公用程式只有在 Load 很轻的
SUN 上测试过 ( 0 到 2 ), 没想到最近 Load 重的 SUN ( 7 以上 )
上执行时, 我程式中加快搜寻速度的部份竟成为致命伤. 由於一
次开二十个程序, 若不幸因电脑异常, 则大部分程序会停住而无法离开,
使电脑 Load 更形加重, 也令系统管理者困扰. 在我花了十数小时不断
改程式後( 包括加上 Alarm Signal 控制执行时间 ), 终於还是不能克服
Load 重时的异常状况. 看来, 我的程式理念 ( 以多个程序平行处理来增
快速度 ) 只能在人数少的工作站或特殊的电脑才能用. 有志於发展应用程
式的同学, 切记这个教训.
由於旧的 FindMan 程式有时会出状况, 我已经将它去掉,
以後有空时再写新的单一程序之 FindMan. 虽然程式已撤掉, 但本文可当
成纯为讨论程式设计, 故仍继续下去.
发布人:netbull 来自:嵌入式Linux