Internet Sockets 网络编程指南Word格式文档下载.docx
《Internet Sockets 网络编程指南Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《Internet Sockets 网络编程指南Word格式文档下载.docx(30页珍藏版)》请在冰豆网上搜索。
∙数据报Socket
∙阻塞
∙select()--多路同步I/O,酷!
∙参考资料
∙DisclaimerandCallforHelp
什么是socket?
你始终听到人们谈论着"
socket"
,而你不知道他的确切含义。
那么,现在我告诉你:
他是使用Unix文件描述符(fieldescriptor)和其他程序通讯的方式。
什么?
Ok--你也许听到一些Unix高手(hacker)这样说:
“呀,Unix中所有的东西就是文件!
”那个家伙也许正在说到一个事实:
Unix程序在执行任何形式的I/O的时候,程序是在读或者写一个文件描述符。
一个文件描述符只是一个和打开的文件相关联的整数。
但是(注意后面的话),这个文件可能是一个网络连接,FIFO,管道,终端,磁盘上的文件或者什么其他的东西。
Unix中所有的东西是文件!
因此,你想和Internet上别的程序通讯的时候,你将要通过文件描述符。
最好相信刚才的话。
现在你脑海中或许冒出这样的念头:
“那么我从哪里得到网络通讯的文件描述符呢,聪明人?
”无论如何,我要回答这个问题:
你利用系统调用socket()。
他返回套接口描述符(socketdescriptor),然后你再通过他来调用send()和recv()。
“但是...”,你可能现在叫起来,“如果他是个文件描述符,那么为什么不用一般的调用read()和write()来通过套接口通讯?
”简单的答案是:
“你可以使用一般的函数!
”。
详细的答案是:
“你可以,但是使用send()和recv()让你更好的控制数据传输。
”
有这样一个事实:
在我们的世界上,有很多种套接口。
有DARPAInternet地址(Internet套接口),本地节点的路径名(Unix套接口),CCITTX.25地址(你可以完全忽略X.25套接口)。
也许在你的Unix机器上还有其他的。
我们在这里只讲第一种:
Internet套接口。
Internet套接口的两种类型
什么意思?
有两种Internet套接口?
是的。
不,我在撒谎。
其实还有很多,但是我可不想吓着你。
我们这里只讲两种。
Exceptforthissentence,whereI'
mgoingtotellyouthat"
RawSockets"
arealsoverypowerfulandyoushouldlookthemup.
好了,好了。
那两种类型是什么呢?
一种是"
StreamSockets"
,另外一种是"
DatagramSockets"
。
我们以后谈到他们的时候也会用到"
SOCK_STREAM"
和"
SOCK_DGRAM"
数据报套接口有时也叫“无连接套接口”(如果你确实要连接的时候用connect()。
)
流式套接口是可靠的双向通讯的数据流。
如果你向套接口安顺序输出“1,2”,那么他们将安顺序“1,2”到达另一边。
他们也是无错误的传递的,有自己的错误控制。
有谁在使用流式套接口?
你可能听说过telnet,不是吗?
他就使用流式套接口。
你需要你所输入的字符按顺序到达,不是吗?
同样,WWW浏览器使用的HTTP协议也使用他们。
实际上,当你通过端口80telnet到一个WWW站点,然后输入“GETpagename”的时候,你也可以得到HTML的内容。
为什么流式套接口可以达到高质量的数据传输?
他使用了“传输控制协议(TheTransmissionControlProtocol)”,也叫“TCP”(请参考RFC-793获得详细资料。
)TCP控制你的数据按顺序到达并且没有错误。
你也许听到“TCP”是因为听到过“TCP/IP”。
这里的IP是指“Internet协议”(请参考RFC-791.)IP只是处理Internet路由而已。
那么数据报套接口呢?
为什么他叫无连接呢?
为什么他是不可靠的呢?
恩,有这样的事实:
如果你发送一个数据报,他可能到达,他可能次序颠倒了。
如果他到达,那么在这个包的内部是无错误的。
数据报也使用IP作路由,但是他不选择TCP。
他使用“用户数据报协议(UserDatagramProtocol)”,也叫“UDP”(请参考RFC-768.)
为什么他们是无连接的呢?
主要原因是因为他并不象流式套接口那样维持一个连接。
你只要建立一个包,在目标信息中构造一个IP头,然后发出去。
不需要连接。
应用程序有:
tftp,bootp等等。
“够了!
”你也许会想,“如果数据丢失了这些程序如何正常工作?
”我的朋友,每个程序在UDP上有自己的协议。
例如,tftp协议每发出一个包,收到者发回一个包来说“我收到了!
”(一个“命令正确应答”也叫“ACK”包)。
如果在一定时间内(例如5秒),发送方没有收到应答,他将重新发送,直到得到ACK。
这一点在实现SOCK_DGRAM应用程序的时候非常重要。
网络理论
既然我刚才提到了协议层,那么现在是讨论网络究竟如何工作和演示SOCK_DGRAM的工作。
当然,你也可以跳过这一段,如果你认为已经熟悉的话。
朋友们,现在是学习数据封装(DataEncapsulation)的时候了!
这非常非常重要。
It'
ssoimportantthatyoumightjustlearnaboutitifyoutakethenetworkscoursehereatChicoState;
-).主要的内容是:
一个包,先是被第一个协议(在这里是TFTP)包装(“封装”),然后,整个数据(包括TFTP头)被另外一个协议(在这里是UDP)封装,然后下一个(IP),一直重复下去,直到硬件(物理)层(Ethernet)。
当另外一台机器接收到包,硬件先剥去Ethernet头,内核剥去IP和UDP头,TFTP程序再剥去TFTP头,最后得到数据。
现在我们终于讲到臭名远播的网络分层模型(LayeredNetworkModel)。
这种网络模型在描述网络系统上相对其他模型有很多优点。
例如,你可以写一个套接口程序而不用关心数据的物理传输(串行口,以太网,连接单元接口(AUI)还是其他介质。
因为底层的程序为你处理他们。
实际的网络硬件和拓扑对于程序员来说是透明的。
不说其他废话了,我现在列出整个层次模型。
如果你要参加网络考试,可一定要记住:
∙应用层(Application)
∙表示层(Presentation)
∙会话层(Session)
∙传输层(Transport)
∙网络层(Network)
∙数据链路层(DataLink)
∙物理层(Physical)
物理层是硬件(串口,以太网等等)。
应用层是和硬件层相隔最远的--他是用户和网络交互的地方。
这个模型如此通用,如果你想,你可以把他作为修车指南。
把他应用到Unix,结果是:
∙应用层(ApplicationLayer)(telnet,ftp,等等)
∙传输层(Host-to-HostTransportLayer)(TCP,UDP)
∙Internet层(InternetLayer)(IP和路由)
∙网络访问层(NetworkAccessLayer)(网络层,数据链路层和物理层)
现在,你可能看到这些层次如何协调来封装原始的数据了。
看看建立一个简单的数据包有多少工作?
哎呀,你将不得不使用"
cat"
来完成他们!
简直是笑话。
对于流式套接口你要作的是send()发送数据。
对于数据报式套接口你按照你选择的方式封装数据然后用sendto()。
内核将为你建立传输层和Internet层,硬件完成网络访问层。
这就是现代科技。
现在结束我们的网络理论速成班。
哦,忘记告诉你关于路由的事情了。
但是我不准备谈他。
如果你真的想知道,那么参考IPRFC。
如果你从来不曾了解他,也没有关系,你还活着不是吗。
structs
终于到达这里了,终于谈到编程了。
在这章,我将谈到被套接口用到的各种数据类型。
因为他们中的一些太重要了。
首先是简单的一个:
socketdescriptor。
他是下面的类型:
int
仅仅是一个常见的int。
从现在起,事情变得不可思议了。
请跟我一起忍受苦恼吧。
注意这样的事实:
有两种字节排列顺序:
重要的字节在前面(有时叫"
octet"
),或者不重要的字节在前面。
前一种叫“网络字节顺序(NetworkByteOrder)”。
有些机器在内部是按照这个顺序储存数据,而另外一些则不然。
当我说某数据必须按照NBO顺序,那么你要调用函数(例如htons())来将他从本机字节顺序(HostByteOrder)转换过来。
如果我没有提到NBO,那么就让他是本机字节顺序吧。
我的第一个结构(TM)--structsockaddr.这个数据结构为许多类型的套接口储存套接口地址信息:
structsockaddr{
unsignedshortsa_family;
/*addressfamily,AF_xxx*/
charsa_data[14];
/*14bytesofprotocoladdress*/
};
sa_family能够是各种各样的事情,但是在这篇文章中是"
AF_INET"
sa_data为套接口储存目标地址和端口信息。
看上去很笨拙,不是吗。
为了对付structsockaddr,程序员创造了一个并列的结构:
structsockaddr_in("
in"
代表"
Internet"
.)
structsockaddr_in{
shortintsin_family;
/*Addressfamily*/
unsignedshortintsin_port;
/*Portnumber*/
structin_addrsin_addr;
/*Internetaddress*/
unsignedcharsin_zero[8];
/*Samesizeasstructsockaddr*/
这个数据结构让可以轻松处理套接口地址的基本元素。
注意sin_zero(他被加入到这个结构,并且长度和structsockaddr一样)应该使用函数bzero()或memset()来全部置零。
Also,andthisistheimportantbit,apointertoastructsockaddr_incanbecasttoapointertoastructsockaddrandvice-versa.这样的话即使socket()想要的是structsockaddr*,你仍然可以使用structsockaddr_in,andcastitatthelastminute!
同时,注意sin_family和structsockaddr中的sa_family一致并能够设置为"
最后,sin_port和sin_addr必须是网络字节顺序(NetworkByteOrder)!
你也许会反对道:
"
但是,怎么让整个数据结构structin_addrsin_addr按照网络字节顺序呢?
要知道这个问题的答案,我们就要仔细的看一看这个数据结构:
structin_addr,有这样一个联合(unions):
/*Internetaddress(astructureforhistoricalreasons)*/
structin_addr{
unsignedlongs_addr;
他曾经是个最坏的联合,但是现在那些日子过去了。
如果你声明"
ina"
是数据结构structsockaddr_in的实例,那么"
ina.sin_addr.s_addr"
就储存4字节的IP地址(网络字节顺序)。
如果你不幸的系统使用的还是恐怖的联合structin_addr,你还是可以放心4字节的IP地址是和上面我说的一样(这是因为#define。
ConverttheNatives!
我们现在到达下个章节。
我们曾经讲了很多网络到本机字节顺序,现在是采取行动的时刻了!
你能够转换两种类型:
short(两个字节)和long(四个字节)。
这个函数对于变量类型unsigned也适用。
假设你想将short从本机字节顺序转换为网络字节顺序。
用"
h"
表示"
本机(host)"
,接着是"
to"
,然后用"
n"
网络(network)"
,最后用"
s"
short"
:
h-to-n-s,或者htons()("
HosttoNetworkShort"
)。
太简单了...
如果不是太傻的话,你一定想到了组合"
,"
,和"
l"
但是这里没有stolh()("
ShorttoLongHost"
)函数,但是这里有:
∙htons()--"
∙htonl()--"
HosttoNetworkLong"
∙ntohs()--"
NetworktoHostShort"
∙ntohl()--"
NetworktoHostLong"
现在,你可能想你已经知道他们了。
你也可能想:
如果我改变char的顺序会怎么样呢?
我的68000机器已经使用了网络字节顺序,我没有必要去调用htonl()转换IP地址。
你可能是对的,但是当你移植你的程序到别的机器上的时候,你的程序将失败。
可移植性!
这里是Unix世界!
记住:
在你将数据放到网络上的时候,确信他们是网络字节顺序。
最后一点:
为什么在数据结构structsockaddr_in中,sin_addr和sin_port需要转换为网络字节顺序,而sin_family不需要呢?
答案是:
sin_addr和sin_port分别封装在包的IP和UDP层。
因此,他们必须要是网络字节顺序。
但是sin_family域只是被内核(kernel)使用来决定在数据结构中包含什么类型的地址,所以他应该是本机字节顺序。
也即sin_family没有发送到网络上,他们可以是本机字节顺序。
IP地址和如何处理他们
现在我们很幸运,因为我们有很多的函数来方便地操作IP地址。
没有必要用手工计算他们,也没有必要用<
<
操作符来操作long。
首先,假设你用structsockaddr_inina,你想将IP地址"
132.241.5.10"
储存到其中。
你要用的函数是inet_addr(),转换numbers-and-dots格式的IP地址到unsignedlong。
这个工作可以这样来做:
ina.sin_addr.s_addr=inet_addr("
);
注意:
inet_addr()返回的地址已经是按照网络字节顺序的,你没有必要再去调用htonl()。
上面的代码可不是很健壮(robust),因为没有错误检查。
inet_addr()在发生错误的时候返回-1。
记得二进制数吗?
在IP地址为255.255.255.255的时候返回的是(unsigned)-1!
这是个广播地址!
记住正确的使用错误检查。
好了,你现在可以转换字符串形式的IP地址为long了。
那么你有一个数据结构structin_addr,该如何按照numbers-and-dots格式打印呢?
在这个时候,也许你要用函数inet_ntoa()("
ntoa"
意思是"
networktoascii"
):
printf("
%s"
inet_ntoa(ina.sin_addr));
他将打印IP地址。
注意的是:
函数inet_ntoa()的参数是structin_addr,而不是long。
同时要注意的是他返回的是一个指向字符的指针。
在inet_ntoa内部的指针静态地储存字符数组,因此每次你调用inet_ntoa()的时候他将覆盖以前的内容。
例如:
char*a1,*a2;
.
a1=inet_ntoa(ina1.sin_addr);
/*thisis198.92.129.1*/
a2=inet_ntoa(ina2.sin_addr);
/*thisis132.241.5.10*/
address1:
%s\n"
a1);
address2:
a2);
运行结果是:
address1:
132.241.5.10
address2:
如果你想保存地址,那么用strcpy()保存到自己的字符数组中。
这就是这章的内容了。
以后,我们将学习转换"
whitehouse.gov"
形式的字符串到正确的IP地址(请看后面的DNS一章。
socket()--得到文件描述符!
我猜我不会再扯远了--我必须讲socket()这个系统调用了。
这里是详细的定义:
#include<
sys/types.h>
sys/socket.h>
intsocket(intdomain,inttype,intprotocol);
但是他们的参数怎么用?
首先,domain应该设置成"
,就象上面的数据结构structsockaddr_in中一样。
然后,参数type告诉内核是SOCK_STREAM类型还是SOCK_DGRAM类型。
最后,把protocol设置为"
0"
(注意:
有很多种domain、type,我不可能一一列出了,请看socket()的manpage。
当然,还有一个"
更好"
的方式去得到protocol。
请看getprotobyname()的manpage。
socket()只是返回你以后在系统调用种可能用到的socket描述符,或者在错误的时候返回-1。
全局变量errno中储存错误值。
(请参考perror()的manpage。
bind()--我在哪个端口?
一旦你得到套接口,你可能要将套接口和机器上的一定的端口关联起来。
(如果你想用listen()来侦听一定端口的数据,这是必要一步--MUD经常告诉你说用命令"
telnetx.y.z6969"
.)如果你只想用connect(),那么这个步骤没有必要。
但是无论如何,请继续读下去。
这里是系统调用bind()的大略:
intbind(intsockfd,structsockaddr*my_addr,intaddrlen);
sockfd是调用socket返回的文件描述符。
my_addr是指向数据结构structsockaddr的指针,他保存你的地址(即端口和IP地址)信息。
addrlen设置为sizeof(structsockaddr)。
简单得很不是吗?
再看看例子:
string.h>
#defineMYPORT3490
main()
{
intsockfd;
structsockaddr_inmy_addr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
/*dosomeerrorchecking!
*/
my_addr.sin_family=AF_INET;
/*hostbyteorder*/
my_addr.sin_port=htons(MYPORT);
/*short,networkbyteorder*/
my_addr.sin_addr.s_addr=inet_addr("
bzero(&
(my_addr.sin_zero),8);
/*zerotherestofthestruct*/
/*don'
tforgetyourerrorcheckingforbind():
bind(sockfd,(structsockaddr*)&
my_addr,sizeof(structsockaddr));
这里也有要注意的几件事情。
my_addr.sin_port是网络字节顺序,my_addr.sin_addr.s_addr也