linux下Client/Server 计算的乐趣

铁中棠_191
By David Nelson
嗨!想要找点乐是吗?试试客户/服务器计算吧。就象是通过
两个中间拉紧了绳子的罐头合子说话一般,只不过升级到了计算
机上。LINUX提供了你需要的所有工具。其实你早就使用了客户/
服务器计算了,列如:Netscape,Telnet,Ftp等等。要编写你自
己的Client/Server应用也是很容易的,可能也是很有用的。
Client/Server 计算通过网络把两个不同的程序(客户和服务
器)连在一起。如果你没有连到网上的话,练习的时候你可以抛开
网络,只让LINUX和自己说话。(但是你的LINUX安装需要配置网络)
Client/Server计算的一种通用模式是使用BSD Sockets。BSD
就是伯克利软件分发,UNIX的一个早期版本。理论上讲BSD
Socket是一种IP地址和端口号的组合。IP地址定义了计算机,端
口号定义了计算机的逻辑通信信道。(在这里端口不是一种物理
设备。一个物理设备,如以太网卡,可以访问一台计算机的所有端
口。)
Ivan Griffin和John Nelson在1998年的2月、3月和4月为
Linux日志提供了优秀的三方网络编程的系列文章。2月的文章包
含用BSD建立一个基础Client/Server组的代码;它包括了所有开始
时特别需要的东西,你可以从SSC下载这些代码,然后从这篇文章
开始实践更多的内容。
把2333.tgz下载后,用tar -xzvf 2333.tgz命令展开它,把
2332l1.txt改名为server.c,2333l2.txt改为client.c。编辑
server.c把第一行开始处无关的字符"@cx:"删掉,还要把最后一
行也删掉或用/*和*/把它注释掉。同样,还须把client.c的最后
一行注释掉。用"gcc -oserver server.c"编译server.c用"gcc
-oclient client.c"编译client.c.server运行在本地计算机上,
所以它需要知道端口号码来定义一个socket.client运行在任意一
台计算机上,所以它需要知道目标server和server的端口号,你可
以使用成千的端口号。但不要用那些已经被占用了的。你的
etc/services文件列出了大部分被用掉的端口。我发现1024端口
工作得较好。
我曾说过你不必联到网络上,但是你必须配置你的计算机网络来
作测试。事实上,这些代码不能使用localhost这样的通用名来运
行,你必须给出计算机的确切的名字。假定你已经设好了你的网
络,输入:
server 1024 &
来启动你的Server,然后输入:
client hostname 1024
来运行你的client,hostname是你计算机的名字或ip.如果一切正
常,你应该能够类似于下列文字的输出:
Connection request from 192.168.1.1
14: Hello, World!
第一行给出了client的ip,第二行是server给client的信息。考虑
到大量代码的包围,那句世界著名的"Hello,World"程序宣言是一
个很好的入手处。确认你的Server一直在运行,直到你用fg命令
和^C(ctrl-C)来关闭它。
一个查询--响应Client/Server程序的例子
现在让我们做些更有用的事吧。同时调试两个程序是很无聊的,
所以让我们先来点简单的,模拟Client/Server对的单一程序。当
你明白了其中的奥秘之后,我们再把代码分成Client和Server两
部分。在下列的程序中,client用一个叫client的函数来模拟,
主程序模拟Server:
/* local test of client-server code */
#include
#include
#include
char name[256] = "";
char buffer[256] = "";
void client(char *buffer)
{
printf("%s", buffer);
fgets(buffer, 256, stdin);
}
int main(int argc, char *argv[])
{
int year, age;
sprintf(buffer, "Please enter your name: ");
client(buffer);
strcpy(name, buffer);
sprintf(buffer, "Hi, %sPlease enter your year of birth: ", name);
client(buffer);
year = atoi(buffer);
age = 1998 - year;
sprintf(buffer, "Your approximate age is %d.\nEnter q to quit: ", age);
client(buffer);
return(0);
}
你无须成为一个C语言专家来看懂以上代码,模拟的
Server(main)通过数组缓冲送出一条字符串“please enter you
name”给模拟的client(client),client输出字符串,同时从键
盘读入"name",然后通过缓冲区返回。然后server问出生年,当
client把输入的字符串收集来后,server 把它换成数字并和1998
相减,并把年龄作为结果返回给client.一切都做完了,但client
需要一个键盘输入来控制退出,server把"q"作为退出的命令,更
老练的写法可以把这个都给省了。这个模拟的client/server演示
了在客户和服务器间传递字符串。提出和回答问题,并进行计
算。
把上面的码保存为localtest.c。用"gcc -olocaltest
localtest.c来编译它,当你运行它时可以看到下面的输出:
Please enter your name: joe
Hi, joe
Please enter your year of birth: 1960
Your approximate age is 38.
Enter q to quit: q
现在让我们把它变成一个真的client/server对。把以下申明插入
到server.c的开头部分:
int main(int argc, char *argv[])
{
int i, year, age;
char name[256] = "";
char buffer[256] = "";
char null_buffer[256] = "";
int serverSocket = 0,
把server.c后部的程序特征代码用如下的代码替代:
/*
* Server application specific code goes here,(这是Server应用程序特征代码)
* e.g. perform some action, respond to client etc.(执行一些工作,响应client)
*/
sprintf(buffer, "Please enter your name: ");
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer = 0;
/* get name (取得姓名)*/
read(slaveSocket, buffer, sizeof(buffer));
strcpy(name, buffer);
sprintf(buffer, "Hi, %sPlease enter your year of birth: ", name);
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer = 0;
/* get year of birth (取得出生年代)*/
read(slaveSocket, buffer, sizeof(buffer));
year = atoi(buffer);
age = 1998 - year;
sprintf(buffer, "Your approximate age is %d.\nEnter q to quit: ", age);
write(slaveSocket, buffer, strlen(buffer));
close(slaveSocket);
exit(0);
这些代码几乎和模拟的client/server中的是一样的,只不过我们
用读写slaveSocket来替代调用程序。你可以把slaveSocket看作
客户和服务器通过socket来连接。
客户的代码很简单。把下列申明插入到client.c的main函数的申
明部分:
int main(int argc, char *argv[])
{
int i;
int clientSocket,
把client.c后部的程序特征代码用如下的代码替代:
/*
* Client application specific code goes here
* e.g. receive messages from server, respond, etc.
* Receive and respond until server stops sending messages
*/
while (0 < (status = read(clientSocket, buffer, sizeof(buffer))))
{
printf("%s", buffer);
for (i = 0; i <= 255; i++) buffer = 0;
fgets(buffer, 256, stdin);
write(clientSocket, buffer, strlen(buffer));
}
close(clientSocket);
return 0;
}
同样,这些代码几乎和模拟的client/server中的是一样的。
主要的区别是对clientSocket的使用,另一端是server中的
slaveSocket,和用来控制程序的while循环。server停止传送信息
时while循环就会关闭client.
像刚才一样重新编译server.c和client.c,并运行它们。这次的输
出应该是这样的:
Connection request from 192.168.1.1
Please enter your name: joe
Hi, joe.
Please enter your year of birth: 1960
Your approximate age is 38.
Enter q to quit: q
现在你可以来真的了:试试用多个client来呼叫同一个server。
server会用为每一个client会话启动一个进程的方法来处理并发
的多个请求。这项工作由server.c的fork调用来完成。读读fork
的man帮助可以学到更多东西。
Client/Server方式的chat程序
下面是最后一个例子,一个在用户间传送信息的chat程序。这
是一个早期的程序,它只允许每个用户轮流使用,同时它还要求
server保持开放一个端口。但它说明了一个client/server对怎样
运行无限制的对话,它也可以被改编成实用的程序。
把以下申明插入到server.c的开头部分
int main(int argc, char *argv[])
{
char buffer[256] = "";
int i, serverquit = 1, clientquit = 1;
int serverSocket = 0,
把server.c后部的程序特征代码用如下的代码替代:
/*
* Server application specific code goes here,
* e.g. perform some action, respond to client etc.
*/
printf("Send q to quit.\n");
sprintf(buffer, "Hi, %s\nS: Please start chat. Send q to quit.\n", inet_ntoa(cl
ntName.sin_addr));
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer = 0;
while (serverquit != 0 && clientquit != 0)
{
status = 0;
while (status == 0)
status = read(slaveSocket, buffer, sizeof(buffer));
clientquit = strcmp(buffer, "q\n");
if (clientquit != 0)
{
printf("C: %s", buffer);
for (i = 0; i <= 255; i++) buffer = 0;
printf("S: ");
fgets(buffer, 256, stdin);
serverquit = strcmp(buffer, "q\n");
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer = 0;
}
}
printf("Goodbye\n");
close(slaveSocket);
exit(0);
把下列申明插入到client.c的main函数的申明部分。
int main(int argc, char *argv[])
{
int i, serverquit = 1, clientquit = 1;
int clientSocket,
把client.c后部的程序特征代码用如下的代码替代:
/*
* Client application specific code goes here
* e.g. receive messages from server, respond, etc.
*/
while (serverquit != 0 && clientquit != 0)
{
status = 0;
while (status == 0)
status = read(clientSocket, buffer, sizeof(buffer));
serverquit = strcmp(buffer, "q\n");
if (serverquit != 0)
{
printf("S: %s", buffer);
for (i = 0; i <= 255; i++) buffer = 0;
printf("C: ");
fgets(buffer, 256, stdin);
clientquit = strcmp(buffer, "q\n");
write(clientSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer = 0;
}
}
printf("Goodbye\n");
close(clientSocket);
return 0;
}
重编译server.c和client.c。为了模拟两台计算机,打开两个X的
窗口或使用两个不同的终端。(alt-1和alt-2)用
server 1024
在一个窗口中运行server,用
client hostname 1024
在另一个窗口中运行client,hostname用你实际的hostname或IP
address来替代。server和client的代码和前面的例子都非常相
似。只有两点主要的区别。首先是检测双方谁输入了"q"来退出程
序。serverquit和clientquit用来作为标志。第二是等待另一方
的回应的循环。read函数返回从socket读来的字符的数量;这些存
储到status中。一个非零的字符数表示另一方有消息送到。
下面是server打印的一个例子:
Connection request from 192.168.1.1
Send q to quit.
C: Hi server
S: Hi client
C: Bye server
S: Bye client
Goodbye
下面是client打印的一个例子:
S: Hi, 192.168.1.1
S: Please start chat. Send q to quit.
C: Hi server
S: Hi client
C: Bye server
S: Bye client
C: q
Goodbye
我希望这些例子显示了建立一个client/server计算有多简单。也
许你试试自己的应用的胃口被掉起来了。如果你作出了什么可口
的东西,不要忘了让我们都知道,还有就是一定要保持语句简
洁。
--

我心。。。