分类: LINUX
2009-07-24 14:27:01
如果你想进入LINUX神奇的网络编程世界,请跟我来,在学习之前
,我只需要你拥有一定的C语言编程知识,就足够了。我会讲述编写网络程序需要的基
本知识。好,今天,让我们一起来看看,网络编程的基本模型--客户/服务器模型。
所谓网络,在软件人员的观点来看,就是很多的用物理链路(比如,以太网,无线
网络)连在一起的计算机,并且安装有网络程序。就像打电话,我们需要知道对方的号
码一样,网络程序也需要知道要和那台计算机通讯,在这里,就是计算机的网络接口所
拥有的IP地址。其实,一个完整的网络程序拥有两个独立的程序,他们分别运行在两个
计算机上,一个是网络通讯的服务器端,一个是网络通讯的客户端,就像打电话,需要
一个打电话的,还要一个接电话的是一样的,所以,我们需要编写两个程序,才是完整
的网络通讯程序,我们熟悉的OutLook,FOXMAIL等都是网络程序中的客户端程序,而A
pache,QMail等便是服务器端程序。只是往往,网络程序的客户端和服务器端之间有一
定的通讯交互协议,比如SMTP,POP3,HTTP,FTP等,对于我们网络程序的编写者来说,他
们就规定了通用的交互协议,这些协议规定了客户端,服务器端应该完成的工作,所以
,编写好网络程序还需要理解好协议,不过,一般说来,我们用不着自己写协议,有很
多的协议,我们可以使用的,都有RFC文档来说明了,你可以在网络上找到各种协议的RF
C.
当然,我们现在,将要开始编写的第一个网络程序,虽然非常简单,但是却可以很
清楚的说明大部分编写网络程序需要的基本概念,好了先让我们看看网络程序的TCP服
务器端的编写步骤:
1. 首先,你需要创建一个用于通讯的套接口,一般使用socket调用来实现。这等
于你有了一个用于通讯的电话:)
2. 然后,你需要给你的套接口设定端口,相当于,你有了电话号码。这一步 一般
通过设置网络套接口地址和调用bind函数来实现。
3. 调用listen函数使你的套接口成为一个监听套接字。 以上三个步骤是TCP服务
器的常用步骤。
4. 调用accept函数来启动你的套接字,这时你的程序就可以等待客户端的连接了
。
5. 处理客户端的连接请求。
6. 终止连接。
现在让我们来看看网络程序客户端的编程步骤:
1. 和服务器的步骤一样。
2. 通过设置套接口地址结构,我们说明,客户端要与之通讯的服务器的IP地址和
端口。
3. 调用connect函数来连接服务器。
4. 发送或者接收数据,一般使用send和recv函数调用来实现。
5. 终止连接。
以上的步骤,是比较普遍的,我们可以从中看出,编写网络程序是很有意思的,同
时,也不是非常复杂,当然,这不包括,高难度的东西:-),下次,我会给出一个简
单的服务器和一个客户机程序。
今天,我给出的代码包括一个服务器,和一个客户机程序,但是省
略了很多代码, 比如说错误处理代码,这样做主要是为了使网络编程的主线清楚,所
以,这两个 程序谈不上网络安全性,和稳定性,这些是以后的话题。但是,对于基本
理解 网络编程已经足够了。我会在下次给出一个完整可运行的程序。下面我会详细 解
释程序的步骤:
先要包括一部分网络编程必须的头部文件:
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int listensock,connsock; /定义两个socket套接字,一个用于监听,一个用于通讯
struct sockaddr_in serveraddr; /定义网络套接字地址结构
const char buff[] = "Hello! Welcome here!\r\n"; /定义要发送的数据缓冲区;
listensock = socket(AF_INET,SOCK_STREAM,0); /创建一个套接字,用于监听
bzero(&serveraddr,sizeof(servaddr)); /地址结构清零
serveraddr.sin_family = AF_INET; /指定使用的通讯协议族
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /指定接受任何连接,因为服务
器不知道谁会要求连接
serveraddr.sin_port = htons(5000); /指定监听的端口
bind(listensock,(sockaddr *)&serveraddr,sizeof(serveraddr)); /给套接口邦定地
址
listen(listensock,1024); /开始监听
connsock = accept(listensock,(sockaddr *)NULL, NULL); /建立通讯的套接字,ac
cept函数,等待客户端程序使用connect函数的连接
send(connsock,buff,sizeof(buff), 0); /向客户端发送数据
close(connsock); /关闭通讯套接字
close(listensock); /关闭监听套接字
}
这是客户端的程序:
int main(int argc,char **argv)
{
char recvbuff[100]; /定义要接收的数据缓冲区
int sockfd; /定义一个socket套接字,用于通讯
struct sockaddr_in serveraddr;/定义网络套接字地址结构
if(argc != 2){
printf("Usage: echo ip地址");
exit(0);
}
sockfd = socket(AF_INET,SOCK_STREAM,0); /创建一个套接字
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family = AF_INET; /指定使用的通讯协议族
serveraddr.sin_port = htons(5000);/指定要连接的服务器的端口
inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);
connect(sockfd,(sockaddr *)&serveraddr,sizeof(serveraddr)); /连接服务器
recv(sockfd,recvbuff,sizeof(recvbuff),0); /接收服务器的数据
printf("%s\n",recvbuff); /打印接收到的数据
close(sockfd); /关闭套接字
}
这两个程序运行后,当客户端连接到服务器后,将接收到服务器传来的字符串Hello!
Welcome here!,不过,程序调试的任务还得又你自己要完成。
你想知道著名的oicq使用的是什么技术来收发消息的吗?今天,我
给出的两个程序,一个服务器 和一个客户端程序,便能告诉你,其中的基本技术,当
然,我指的不包括,它的界面是怎样做的:)
这两个程序使用的是UDP套接字编程,上一次给出的其实是使用TCP套接字编程,你
自己可以先分析 一下,他们的异同点,我会在下一次,分析这两种编程的区别。
这是发送数据的程序:
/*头部文件包括网络需要的和基本输入输出需要的*/
#include #include
#include
#include
#include
int port = 5500;
void main()
{
int sockfd;
int count = 0;
int flag;
char buf[80];
struct sockaddr_in address;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //看到不同的地方了吗?
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(port);
flag = 1;
do{
sprintf(buf,"Packet %d\n", count);
if(count > 30){
sprintf(buf,"over\n");
flag = 0;
}
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&address, sizeof(add
ress)); // 发现了吗使用的函数不一样,它也是发送数据
count++;
}while (flag);
}
这是接收数据的程序:
#include
#include
#include
#include
#include
char *hostname = "127.0.0.1"; //这个特殊的ip表示本的计算机
void main()
{
int sinlen;
int port = 8080;
char message[256];
int sockfd;
struct sockaddr_in sin;
struct hostent *server_host_name; // hostent结构有着机器的名字等信息
server_host_name = gethostbyname("127.0.0.1"); // 这个函数用来得到“127
.0.0.1”的主机名字,也就是本机的名字
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(port);
sockfd = socket(PF_INET,SOCK_DGRAM,0); //这里也不一样
bind(sockfd,(struct sockaddr *)&sin,sizeof(sin));
while(1){
sinlen = sizeof(sin);
recvfrom(sockfd,message,256,0, (struct sockaddr *)&sin,&sinlen);/
/ 它是接受数据的函数
printf("\nData come from server:\n%s\n",message);
if(strncmp(message,"over",4) ==0)break;
}
close(sockfd);
}
当你编译调试通过了后,在一个窗口,运行接收程序,一个窗口运行发送程序,你
就可以看到数据被创送了。网络程序是可以在本地机器上,使用两个不同的窗口来运行
,模拟客户端和服务器的。
今天,我会解释网络编程中非常重要的两个概念:TCP编程和UDP编
程,这是真真进入网络编程的灿烂世界必须深入理解的部分。
首先,我们必须明白,一般操作系统,包括Windows,Linux,UNIX,他们提供的供应用
程序员使用的编程接口,一般的函数名字都差不多。不同的是,他们的操作系统对这些
函数(也可以说是系统调用)的实现细节不尽相同,因此,各种操作系统,在提供网络
服 务的时候,就存在着,诸如,速度,效率,稳定性的差别。
那么,就网络编程的套接口字的选则来看,一样存在着以上的差别。也就是说,你
选择TCP套接字和选择UDP编程,在传输数据时, 一样有着,速度,效率,稳定性的差别
。首先,明白这一点,对于开始网络编程是非常重要的。
TCP套接字,操作系统向你提供的是一个稳定的数据通路,从程序员的角度来看,你
只需要明白,当你使用TCP编程时,如果你调用 的发送数据函数,比如send()函数,它
的返回成功,那么表示,系统发送出的数据肯定被通讯的对方准确接收到了。而UDP套接
字, 操作系统给你提供的是一个不稳定的,无连接的数据通路,所以,当你使用UDP编
程时,如果你调用的发送数据的函数,如sendto() 函数,它的返回成功,那么表示,你
要发送的数据已经发送到了网上,而这些数据是否到达了你要发送数据的对方,那时不
一定的。所以,对于UDP编程,我们如果要保证数据的发送的准确,及时,我们需要自己
建立起,一些数据传送的控制机制,来确保,我们的数据成功发送到对方,而不仅仅是
把数据发送到了网络上,我们就不过问了。当然,对于TCP编程,操作系统,已经,帮我
们做了一 系列控制功能,所以,我们不需要考虑太多的东西;-)
从以上的分析,你应该可以看出,对于初学者或者说,TCP编程是非常好的入门点,
也是很容易的。当然,UDP有他自己的好处,不如 ,他虽然不能保证你调用一次发送函
数就把数据发送到对方那里,但是只要你进行适当的处理,你会发现,UDP发送数据的速
度,比TCP编程要快!天下没有十全十美的!
TCP编程拥有了可靠的数据连接,UDP不具有,但是在速度方面,UDP编程缺优于TCP
编程,特别是对于传输短消息,所以,我认为OICQ 所以选择UDP编程,这个原因是其中
很重要的一个^-^。
现在,我已经说明了,TCP和UDP编程的重要差别,虽然,这些差别是由协议本身引
入的,但是对于编程来说,理解了是很有好处的。
当然,那对于一个程序员来说还的明白,选择TCP编程,和UDP编程到底是怎样体现
在代码上的,其实如果你仔细分析过我前面给出的 两个程序,你也许已经明白,除了他
们使用的发送数据和接收数据的函数不怎样一样之外,最重要的差别在于当你使用socke
t()函数 建立套接字的时候,你需要的指定的三个参数中,中间的那个参数。如果,你
要使用TCP编程,你要使用SOCK_STREAM,而如果你要使用 UDP编程,使用SOCK_DGRAM,关
于TCP和UDP的更加深入的区别和介绍,我会在以后讲述。
服务器/客户机模式是网络通讯交互的最常用模式,我们必须要深刻的
理解这种交互模式。
其实,网络软件在很大程度上是对各种网络协议的实现,不管这种协议是官方的,还
是你自己定义的。所以,网络软件的好坏也和协议的好坏有着直接的关系。当然,服务器
/客户机模式在某种程度上就规定了这样一种机制:
服务器方的程序首先启动,开始等待。
客户机的程序启动,向服务器提出通讯连接的请求。
服务器决定是否接受客户机的连接请求。并且,给客户机一个回答。
如果,服务器和客户机建立了连接,通常的协议,会给出那一方应该在这个时候首先发送
数据,还有发送数据的内容,格式等等。
这样,一方发送,另一方接受,然后回答。
服务器和客户机就是在这样的一来一往的情况下,进行通讯的,但是为什么要选择这
种依次发送的顺序了,这些都是因为要解决,在网络上传输数据时,可能出现的死锁和饿
死等现象。 对于这两种现象,我们可以这样理解,比如,我向你发了消息,等你的回答,
然后才进行下一步,可是这时候,你可能也在等待我的数据到来,然后发回答给我,可是
问题出现了,如果你没有得到我的消息(掉了包),那么我和你可能一直等待下去。当然
,常用的各种协议, 比如SMTP,POP3,FTP,WWW都很好的定义了发送和接收数据的顺序问题
。这也是我们要很好理解的。刚才我说的那个例子,其实就是一种死锁,两方都不能继续
通讯了。至于饿死现象,我们可以这样想,一个服务器每次只能处理一个用户的通讯请求
,那么当他和一个客户机通讯时,他接收了客户机的连接请求后,等待客户机的数据发送
过来,可客户机的数据迟迟不到(可能失掉了,可能客户机更本没发送数据),这种情况
下,服务器将不能和别的客户机通讯,资源都被这个客户机用了,那么对于其他的客户机
用户,就处于一种饿死状态。
我们当然可以通过,规定很多的东西来保证我们通讯的顺利进行。比如一方发送,一
方等待。 发送方在没有得到回答前重复多次发送数据。发送方还可以使用定时器等方法,
来保证不出现饿死和死锁现象。如果你想学习更多的方法和思想,你可以学学TCP/IP协议
和各种应用协议,他们在不同的层次上解决了各种可能遇到的问题。
当然,在网络上会出现的障碍是多种多样的,你所用的协议及你所写的代码,一起决
定了你的网络程序的性能和安全。所以,现实生活中的网络程序往往是很复杂的。