Linux网络编程

如果你想进入Linux神奇的网络编程世界,请跟笔者来,在学习之前,笔者只需要你拥有一定的C语言编程知识,就足够了。笔者会讲述编写网络程序需要的基本知识。

  所谓网络,在软件人员的观点来看,就是很多的用物理链路(比如,以太网,无线网络)连在一起的计算机,并且安装有网络程序。就像打电话,我们需要知道对方的号码一样,网络程序也需要知道要和那台计算机通讯,在这里,就是计算机的网络接口所拥有的IP地址。其实,一个完整的网络程序拥有两个独立的程序,他们分别运行在两个计算机上,一个是网络通讯的服务器端,一个是网络通讯的客户端,就像打电话,需要一个打电话的,还要一个接电话的是一样的,所以,我们需要编写两个程序,才是完整的网络通讯程序,我们熟悉的OutLook,FOXMAIL等都是网络程序中的客户端程序,而Apache,QMail等便是服务器端程序。只是往往,网络程序的客户端和服务器端之间有一定的通讯交互协议,比如SMTP,POP3,HTTP,FTP等,对于我们网络程序的编写者来说,他们就规定了通用的交互协议,这些协议规定了客户端,服务器端应该完成的工作,所以,编写好网络程序还需要理解好协议,不过,一般说来,我们用不着自己写协议,有很多的协议,我们可以使用的,都有RFC文档来说明了,你可以在网络上找到各种协议的RFC. 
  当然,我们现在将要开始编写的第一个网络程序,虽然非常简单,但是却可以很清楚的说明大部分编写网络程序需要的基本概念,好了先让我们看看网络程序的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! "; /定义要发送的数据缓冲区; 
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); /建立通讯的套接字,accept函数,等待客户端程序使用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 ",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 ", count); 
  if(count > 30){ 
  sprintf(buf,"over "); 
  flag = 0; 
  } 
  sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&address, sizeof(address)); // 发现了吗使用的函数不一样,它也是发送数据 
  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(" Data come from server: %s ",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编程到底是怎样体现在代码上的,其实如果你仔细分析过我前面给出的两个程序,你也许已经明白,除了他们使用的发送数据和接收数据的函数不怎样一样之外,最重要的差别在于当你使用socket()函数 建立套接字的时候,你需要的指定的三个参数中,中间的那个参数。如果你要使用TCP编程,你要使用SOCK_STREAM,而如果你要使用UDP编程,使用SOCK_DGRAM,关于TCP和UDP的更加深入的区别和介绍,我会在以后讲述。 
  下次,我会给大家讲述一下,服务器和客户机的概念和区别:) 
服务器/客户机模式是网络通讯交互的最常用模式,我们必须要深刻的理解这种交互模式。 
  其实,网络软件在很大程度上是对各种网络协议的实现,不管这种协议是官方的,还是你自己定义的。所以,网络软件的好坏也和协议的好坏有着直接的关系。当然,服务器/客户机模式在某种程度上就规定了这样一种机制: 
1. 服务器方的程序首先启动,开始等待。 
2. 客户机的程序启动,向服务器提出通讯连接的请求。 
3. 服务器决定是否接受客户机的连接请求。并且,给客户机一个回答。 
4. 如果,服务器和客户机建立了连接,通常的协议,会给出那一方应该在这个时候首先发送数据,还有发送数据的内容,格式等等。 
5. 这样,一方发送,另一方接受,然后回答。 
  服务器和客户机就是在这样的一来一往的情况下,进行通讯的,但是为什么要选择这种依次发送的顺序了,这些都是因为要解决,在网络上传输数据时,可能出现的死锁和饿死等现象。 对于这两种现象,我们可以这样理解,比如,我向你发了消息,等你的回答,然后才进行下一步,可是这时候,你可能也在等待我的数据到来,然后发回答给我,可是问题出现了,如果你没有得到我的消息(掉了包),那么我和你可能一直等待下去。当然,常用的各种协议, 比如SMTP,POP3,FTP,WWW都很好的定义了发送和接收数据的顺序问题。这也是我们要很好理解的。刚才我说的那个例子,其实就是一种死锁,两方都不能继续通讯了。至于饿死现象,我们可以这样想,一个服务器每次只能处理一个用户的通讯请求,那么当他和一个客户机通讯时,他接收了客户机的连接请求后,等待客户机的数据发送过来,可客户机的数据迟迟不到(可能失掉了,可能客户机更本没发送数据),这种情况下,服务器将不能和别的客户机通讯,资源都被这个客户机用了,那么对于其他的客户机用户,就处于一种饿死状态。 
  我们当然可以通过,规定很多的东西来保证我们通讯的顺利进行。比如一方发送,一方等待。 发送方在没有得到回答前重复多次发送数据。发送方还可以使用定时器等方法,来保证不出现饿死和死锁现象。如果你想学习更多的方法和思想,你可以学学TCP/IP协议和各种应用协议,他们在不同的层次上解决了各种可能遇到的问题。 
  当然,在网络上会出现的障碍是多种多样的,你所用的协议及你所写的代码,一起决定了你的网络程序的性能和安全。所以,现实生活中的网络程序往往是很复杂的.

你可能感兴趣的:(linux)