Socket编程基础本章以Berkeley Socket为主,主要介绍网络编程时常用的调用和程序使用它们的方法及基本结构。网络编程有两种主要的编程接口,一种是Berkeley UNIX(BSD UNIX)的socket编程接口,另一种是AT&T的TLI接口(用于UNIXSYSV)。计算机网络的发展归功于DoD(U.S. Department of Defense)赞助研究的ARPANET网络。随着网络的发展而成熟的网络参考模型是TCP/IP参考模型,我们将在本章介绍该模型。八十年代早期,APRP在加利福尼亚大学的伯克利分校将TCP/IP第一次实现在UNIX系统上,这就是广为人知的socket接口。在UNIX System V R4.0中增加了socket接口和DARPA协议的支持程序作为与BSDUNIX统一的部分。所以,本书只介绍socket接口,不再介绍TLI接口。而且由于Internet越来越广泛的普及,所以这一章的重点将基于TCP/IP,对于socket虽然可以使用在UNIX域、XNS域中,但这里对于其他通信域不作过多的介绍。在进行本章阅读之前,我们希望读者对网络的基本知识有所了解,这样才能有利于理解和编程。读者还应该了解Internet网络的概念性知识,要了解TCP(传输控制协议)、UDP(用户数据报协议)、ICMP(网间控制报文协议)和IP(Internet协议族)等的作用。我们推荐Andrew S. Tanenbaum的《Computer Networks》第三版作为对网络概念学习的参考书。
1 TCP/IP 基础知识
这里先假定读者对ISO的OSI七层模型已有了一定的了解,下面我们来看看TCP/IP模型。ISO的OSI对服务、接口和协议的概念区别十分明了,但它却没有真正的用户群。TCP/IP模型对服务、接口和协议的概念区别不象OSI模型那样明晰,但很实用。TCP/IP模型分为四层,对应于OSI七层模型如下图所示:图6-1 TCP/IP参考模型与OSI模型的近似对应关系在TCP/IP模型中,互联网层是基于无连接互联网络层的分组交换网络。在这一层中主机可以把报文(Packet)发往任何网络,报文独立地传向目标。互联网层定义了报文的格式和协议,这就是IP协议族(Internet Protocol)。互联网层的功能是将报文发送到目的地,主要的设计问题是报文路由和避免阻塞。互联网层上面是传输层,该层的主要功能和OSI模型的该层一样,主要使源和目的主机之间可以进行会话。该层定义了两个端到端的协议,一个是面向连接的传输控制协议TCP,另一个是无连接的用户数据报协议UDP。TCP/IP协议模型中没有会话层和表示层。传输层之上是应用层,它包含所有的高层协议,如远程虚拟终端协议TELNET、文件传输协议FTP、简单邮件传输协议SMTP等。这些高层协议中常见的如TELNET协议,用来允许用户远程登录到另一台UNIX机器;FTP协议用来传输文件,常见的有WU-FTP(Washington University的FTP服务器端程序,是一个免费程序);SMTP协议用来传送email,常见的服务器端程序有netscape等公司制作的程序,也有免费使用的sendmail程序;还有域名系统服务DNS协议,新闻组传送协议NNTP,用于WWW的超文本传输协议HTTP等。主机到网络这一层,在TCP/IP模型中没有详细定义,这里不作介绍。如需要学习更多的网络知识及TCP/IP的详细描述,请参考专门的书籍,这里不再深入探讨。
2 Socket一般描述
由于越来越多的计算机厂商,特别是工作站制造商如Sun等公司采用了Berkeley UNIX,socket接口被广泛采用,以至于现在,socket接口被广泛认可并成为了事实上的工业标准。目前的SYSV、BSD、OSF都将socket接口作为系统的一部分。当时设计如何支持TCP/IP协议时,有两种加入函数的方法,一种是直接加入支持TCP/IP协议的调用,另一种是加入支持一般网络协议的函数,而用参数来指定支持TCP/IP协议。Berkeley采用了后者,这样可以支持多协议族,TCP/IP是协议族之一(PF_INET)。
2.1 socket 描述符
前面已经提到过,在UNIX中,进程要对文件进行操作,一般使用open调用打开一个文件进行访问,每个进程都有一个文件描述符表,该表中存放打开的文件描述符。用户使用open等调用得到的文件描述符其实是文件描述符在该表中的索引号,该表项的内容是一个指向文件表的指针。应用程序只要使用该描述符就可以对指定文件进行操作。同样,socket接口增加了网络通信操作的抽象定义,与文件操作一样,每个打开的socket都对应一个整数,我们称它为socket描述符,该整数也是socket描述符在文件描述符表中的索引值。但socket描述符在描述符表中的表项并不指向文件表,而是指向一个与该socket有关的数据结构。BSD UNIX中新增加了一个socket调用,应用程序可以调用它来新建一个socket描述符,注意进程用open只能产生文件描述符,而不能产生socket描述符。socket调用只能完成建立通信的部分工作,一旦建立了一个socket,应用程序可以使用其他特定的调用来为它添加其他详细信息,以完成建立通信的过程。
2.2 从概念上理解socket的使用网络编程中最常见的是客户/服务器模式。以该模式编程时,服务端有一个进程(或多个进程)在指定的端口等待客户来连接,服务程序等待客户的连接信息,一旦连接上之后,就可以按设计的数据交换方法和格式进行数据传输。客户端在需要的时刻发出向服务端的连接请求。下面讲述中所用到的调用,将在下一节进行详细的阐述,这里为了便于理解,提到了这些调用及其大致的功能。使用socket调用后,仅产生了一个可以使用的socket描述符,这时还不能进行通信,还要使用其他的调用,以使得socket所指的结构中使用的信息被填写完。在使用TCP协议时,一般服务端进程先使用socket调用得到一个描述符,然后使用bind调用将一个名字与socket描述符连接起来,对于Internet域就是将Internet地址联编到socket。之后,服务端使用listen调用指出等待服务请求队列的长度。然后就可以使用accept调用等待客户端发起连接(一般是阻塞等待连接,后面章节会讲到非阻塞的方式),一旦有客户端发出连接,accept返回客户的地址信息,并返回一个新的socket描述符,该描述符与原先的socket有相同的特性,这时服务端就可以使用这个新的socket进行读写操作了。一般服务端可能在accept返回后创建一个新的进程进行与客户的通信,父进程则再到accept调用处等待另一个连接。客户端进程一般先使用socket调用得到一个socket描述符,然后使用connect向指定的服务器上的指定端口发起连接,一旦连接成功返回,就说明已经建立了与服务器的连接,这时就可以通过socket描述符进行读写操作了。下面是在客户和服务端使用TCP时,客户进程和服务进程使用系统调用的该程。
使用TCP的客户和服务端使用系统调用的图示使用无连接的UDP协议时,服务端进程创建一个socket,之后调用recvfrom接收客户端的数据报,然后调用sendto将要返回客户端的消息发送给客户进程。客户端也要先创建一个socket,再使用sendto向服务端进程发出请求,使用recvfrom得到返回的消息。
下面我们先编写一个非常简单的套接口客户端程序client,这个程序较为简单,它演示了一个无名的套接口连接,
以及如何与一个服务器套接口连接,假设服务器套接口的名字是色server_socket.
/*
client.c
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int sockfd;
int len;
struct sockaddr_un address;
int result;
char ch=A;
sockfd=socket(AF_UNIX,SOCK_STREAM,0);
/*以上建立客户端的套接口,采用AF_UNIX的unix域协议*/
address.sun_family=AF_UNIX;
strcpy(address.sun_path,"server_socket");
len=sizeof(address);
/*以上创建服务器套接口的地址,其中包括套接口类型,名称*/
result=connect(sockfd,(struct sockaddr *)&address,len);
if(result==-1){
perror("oops:client1");
exit(1);
}
/*以上我们试图与服务器套接口建立连接*/
write(sockfd,&ch,1);
read(sockfd,&ch,1);
/*如果成功,将向服务器端发送一个字符,然后读取服务器的回答*/
printf("char from server=%c\n",ch);
close(sockfd);
exit(0);
}
/*
server.c
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int server_sockfd,client_sockfd;
int server_len,client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
unlink("server_socket");
/*如果存在同名的套接口,则先删除*/
server_sockfd=socket(AF_UNIX,SOCK_STREAM,0);
/*以上建立套接口,这时候无名*/
server_address.sun_family=AF_UNIX;
strcpy(server_address.sun_path,"server_socket");
server_len=sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);
listen(server_sockfd,5);
/*以上创建监听队列.等待用户的连接请求*/
while(1)
{
char ch;
printf("server waiting\n");
client_sockfd=accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);
/*以上接受一个客户的请求*/
read(client_sockfd,&ch,1);
/*因为连接一旦建立,客户就会先发消息过来,所以服务器先读*/
ch++;
write(client_sockfd,&ch,1);
/*把读取的字符串做简单处理,回送*/
close(client_sockfd);
}
}
posted on 2005-06-24 00:41
【Z&Y】幸福小筑 阅读(188)
评论(0) 编辑 收藏 引用 所属分类:
Linux使用及编程技巧