简单的 Winsock 应用程式设计3Word文档格式.docx
《简单的 Winsock 应用程式设计3Word文档格式.docx》由会员分享,可在线阅读,更多相关《简单的 Winsock 应用程式设计3Word文档格式.docx(13页珍藏版)》请在冰豆网上搜索。
失败-SOCKET_ERROR(呼叫WSAGetLastError()可得知原因)
说明:
此函式用来获取localhost的名称。
在程式中我们呼叫的方法如下:
gethostname((charFAR*)hname,sizeof(hname))
读者们如果使用过TrumpetWinsock的话,可能知道Trumpet的环境设定中
并没有让我们设定localhost名称的栏位,所以在执行一些PublicDomain的
Winsock应用程式(如ws_ping、wintalk)时,在呼叫gethostname()时会产生错
误;
解决的方法是在Trumpet的「hosts」档中加上您的主机IP位址及名称,
那麽呼叫这个函式时就不会再产生错误了。
【如何得知系统主动指定给我们的IP位址及portnumber】
以前的文章中,笔者曾提到Client端的TCPSocket在呼叫connect()函式去
连接Server端之前,可以呼叫bind()函式来指定Client端Socket所用的IP位址
及portnumber;
但是一般而言,我们Client端并不需要呼叫bind()来指定特定
的IP位址及portnumber的,而是交由系统主动帮我们的Socket设定IP位址及
portnumber(呼叫connect()函式时)。
但是我们如何得知系统指定了什麽IP
位址及portnumber给我们呢?
这就要借助getsockname()这个函式了。
◎getsockname():
获取Socket的Local位址及portnumber资料。
格式:
intPASCALFARgetsockname(SOCKETs,
structsockaddrFAR*name,intFAR*namelen);
sSocket的识别码
name存放此Socket的Local位址的暂存区
namelenname的长度
此函式是用来取得已设定位址或已连接之Socket的本端位址资料。
若
是此Socket被设定为INADDR_ANY,则需等真正建立连接成功後才会传回正确
的位址。
在程式中呼叫的方法为:
structsockaddr_insa;
intsalen=sizeof(sa);
getsockname(sd,(structsockaddrFAR*)&
sa,&
salen)
【如何知道和我们的Socket连接的对方是谁】
连接的Socket是有两端的,所以相对於getsockname()函式,Winsock也提
供了一个getpeername()函式,来让我们获得与我们连接的对方的IP位址与port
number。
◎getpeername():
获取连接成功之Socket的对方IP位址及portnumber。
intPASCALFARgetpeername(SOCKETs,
name储存与此Socket连接的对方IP位址的暂存区
此函式可用来取得已连接成功的Socket的彼端之位址资料。
呼叫的方式如下:
getpeername(sd,(structsockaddrFAR*)&
现在我们仍然利用WinKing来当我们的WinsockStack,并利用它所提供的
工具来观察Sockets的连结及资料是否正确。
由图1,我们可以由WinKing的视窗看到我们设定这台主机的名称是
「vincent」,IP位址是「140.92.61.24」。
我们并利用两个hello程式,一个当
成Client(画面右边打开者),一个当成Server(画面左边最小化者)。
Server
所用的portnumber是「7016」;
Client并没有呼叫bind()来指定port
number,而是呼叫connect()时由系统指定。
我们呼叫gethostname(),得到的答案是「vincent」;
而Client呼叫
getsockname()得到自己的IP位址是「140.92.61.24」,portnumber是「2110」
(笔者以前曾提过,由系统主动指定的portnumber会介於1024到5000间);
再呼叫getpeername()得到与Client连接的Server端IP位址是「140.92.61.24」
(因为我们的Client和Server都在同一台主机),portnumber是「7016」。
果
然没错!
(由WinKing的Sockets'
Status视窗亦可观察到相互连接的Sockets资
料,与我们呼叫函式所得结果相同)
(图1)利用hello程式来模拟Client和Server
读者必须注意一点,getsockname()及getpeername()所取得的IP位址及port
number都是networkbyteorder,而不是hostbyteorder;
如果您想转成hostbyte
order,就必须借助ntohl()及ntohs()两个函式。
而我们能看到IP位址以「字
串」方式表达出来,则又是利用了inet_ntoa()函式;
相对地,我们也可利用
inet_addr()函式将字串方式的IP位址转换成in_addr格式(networkbyteorder的
unsignedlong)。
◎inet_ntoa():
将一网路位址转换成「点格式」字串。
charFAR*PASCALFARinet_ntoa(structin_addrin);
in一个代表Internethost位址的结构
成功-一个代表位址的「点格式」(dotted)字串
失败-NULL
此函式将一Internet位址转换成「a.b.c.d」字串格式。
◎inet_addr():
将字串格式的位址转换成32位元in_addr的格式。
unsignedlongPASCALFARinet_addr(constcharFAR*cp);
cp一个代表IP位址的「点格式」(dotted)字串
成功-一个代表Internet位址的unsignedlong
失败-INADDR_NONE
此函式将一「点格式」的位址字串转换成适用之Intenet位址。
「点格式」字串可为以下四种方式之任一:
(i)a.b.c.d(ii)a.b.c(iii)a.b(iv)a
图1的hello程式中,我们将Local资料写到dispmsg中,再显示出来;
其
用法如下:
wsprintf((LPSTR)dispmsg,"
OK!
localip=%s,localport=%d"
inet_ntoa(sa.sin_addr),ntohs(sa.sin_port));
【Winsock提供的资料库函式】
Winsock也提供了同步与非同步的网路资料库函式;
不过读者们要知道,此
处的资料库指的并非如Informix,Oracle等商业用途的资料库系统,而是指主机
IP位址及名称、well-known服务的名称及Socket型态及所用的portnumber、
以及协定(protocol)名称及代码等。
【同步资料库函式】
首先我们来看一下第一组:
gethostbyname()及gethostbyaddr()函式
这两个函式的用途是让我们可以由某个主机名称求得它的IP位址,或是由
它的IP位址求得它的名称。
一般我们经常会用到的是由名称求得IP位址;
因
为很少人会去记某台机器的IP位址的,另外TCP/IP封包的IPheader上也必须
记载送、收主机的IP位址,而不是主机名称。
◎gethostbyname():
利用某一host的名称来获取该host的资料。
structhostentFAR*PASCALFAR
gethostbyname(constcharFAR*name);
namehost的名称
成功-指向一个hostent结构的指标
失败-NULL(呼叫WSAGetLastError()可得知原因)
此函式是利用host名称来获取该主机的其他资料,如host的位址、
别名,位址的型态、长度等。
◎gethostbyaddr():
利用某一host的IP位址来获取该host的资料。
gethostbyaddr(constcharFAR*addr,intlen,inttype);
addrnetwork排列方式的位址
lenaddr的长度
typePF_INET(AF_INET)
此函式是利用IP位址来获取该主机的其他资料,如host的名称、别
名,位址的型态、长度等。
程式中呼叫的方式分别如下:
charhost_name[30];
structhostentfar*htptr;
/*假设host_name的值已先设定为我们要求得资料的主机名称*/
htptr=(structhostentFAR*)gethostbyname((charfar*)host_name)
structin_addrhost_addr;
/*假设host_addr的值已先设定为我们要求得资料的主机的networkbyte
order方式的IP位址*/
htptr=(structhostentFAR*)gethostbyaddr((charfar*)&
host_addr,4,
PF_INET)
一般言,程式中呼叫到gethostbyname()及gethostbyaddr()时,Winsock
Stack会先在local的「hosts」档中找看看是否有这个主机的资料;
如果没有,
则可能再透过「领域名称服务」(DomainNameService)的功能,向「名称伺
服器」(NameServer)查询;
所以呼叫这两个函式时,有时会等一下子才获得
答覆。
如果您想让程式执行快一些的话,可将常用主机的资料放在hosts档中,
这样就不必透过DNS去查询了。
接下来我们来看getservbyname()及getservbyport()这两个函式。
大部份的读者应该都用过telnet、mail、ftp、news等服务应用程式;
这些应
用程式的协定,比如服务名称、伺服器端所用的portnumber、以及Socket的型
态,都是固定的;
这些资料,我们就可以利用getservbyname()或getservbyport()
来取得,而不必刻意去记颂它们。
◎getservbyname():
依照服务(service)名称及通讯协定(tcp/udp)来获取该
服务的其他资料。
structservent*PASCALFAR
getservbyname(constcharFAR*name,constcharFAR*proto);
name服务名称
proto通讯协定名称
成功-一指向servent结构的指标
利用服务名称及通讯协定来获得该服务的别名、使用的port号码
等。
◎getservbyport():
依照服务(service)的port号码及通讯协定(tcp/udp)来
获取该服务的其他资料。
getservbyport(intport,constcharFAR*proto);
port服务的port编号
利用port编号及通讯协定来获得该服务的名称、别名等。
程式中的使用方法分别为:
charserv_name[20];
charproto[10];
structserventfar*svptr;
/*假设serv_name及proto已先设好服务名称及通讯协定*/
svptr=(structserventFAR*)getservbyname((charfar*)serv_name,(charfar
*)proto)
intserv_port;
/*假设serv_port及proto已先设好服务所用的portnumber及通讯协定*/
svptr=(structserventFAR*)getservbyport(htons(serv_port),(charfar
*)proto))
Winsock环境下,我们能够查询到的服务资料都是存放在local的
「services」档中;
这个档所存放的都是well-known的服务,基本上我们是不需
去更改它的。
读者也可以将自己提供的服务加到这个档中,不过您所用的服务
资料要公诸於世,不然别人的services档中可是没有您的服务的资料哟。
最後的这组getprotobyname()及getprotobynumber()函式是用来取得一些
「协定」的资料,比如tcp、udp、igmp等。
一般而言,我们是不太会用到的。
◎getprotobyname():
依照通讯协定(protocol)的名称来获取该通讯协定的其
他资料。
structprotoentFAR*PASCALFAR
getprotobyname(constcharFAR*name);
name通讯协定名称
成功-一指向protoent结构的指标
利用通讯协定的名称来得知该通讯协定的别名、编号等资料。
◎getprotobynumber():
依照通讯协定的编号来获取该通讯协定的其他资料。
getprotobynumber(intnumber);
number以hostorder排列方式的通讯协定编号
利用通讯协定的编号来得知该通讯协定的名称、别名等资料。
程式中呼叫方式分别如下:
structprotoentfar*ptptr;
charproto_name[20];
/*假设proto_name已先设好协定名称*/
ptptr=(structprotoentFAR*)getprotobyname((charfar*)proto_name)
intproto_num;
/*假设proto_num已先设好协定编号*/
ptptr=(structprotoentFAR*)getprotobynumber(proto_num)
WinsockStack对於应用程式呼叫getprotobyname()及getprotobynumber()的
资料,是取自於local的「protocol」档;
如无需要,我们也不用去变更这个档
案的内容。
(图2)hello程式呼叫同步资料库函式
【非同步资料库函式】
Winsock1.1针对前面笔者所描述的6个同步资料库函式,也提供了相对的
6个非同步资料库函式,它们分别是WSAAsyncGetHostByName()、
WSAAsyncGetHostByAddr()、WSAAsyncGetServByName()、
WSAAsyncGetServByPort()、WSAAsyncGetProtoByName()、
WSAAsyncGetProtoByNumber()。
由於它们取得的资料与同步资料库函式相同,所以笔者仅以
WSAAsyncGetHostByName()为例,说明这些非同步函式,并告诉各位读者,同
步和非同步资料库函式不同的地方。
由字面来看,「非同步」的意思就是我们发出问题时,并不会马上得到答
覆,而等到系统取到资料时再告知我们。
没错,这些非同步资料库函式的作用
就是这样。
和WSAAsyncSelect()函式一样,我们要告诉Winsock系统一个接受
通知讯息的视窗及讯息代码,以便系统通知我们。
我们呼叫同步资料库函式时,return值是一个指到相对资料的暂存区,而这
个资料暂存区是由系统所提供的;
但是呼叫非同步资料库函式时,我们必须自
己准备资料暂存区,并将此暂存区的位址当成参数,传给系统,以便系统用来
储存取到的资料。
读者们必须特别注意一点:
在系统通知资料取得成功或失败
前,千万不可将传给系统的资料暂存区删除释放,不然当系统取得资料要写入
时,资料区已不见了,会导至当机的。
除此之外,资料暂存区的大小一定要够
大,才足够让系统用来存放取得的资料。
(Winsock规格中的建议值是
MAXGETHOSTSTRUCT1024bytes大小的暂存区,笔者认为太大了,100byets
差不多就太够了?
呼叫非同步资料库函式时,得到的return值是一个代码,此代码代表的就
是此项呼叫在系统内的编号;
由於是非同步,所以我们在得到答案前,仍可呼
叫WSACancelAsyncRequest()函式来取消原先的呼叫,这个取消的动作就要利
用到该代码了。
另外,当我们收到结果通知时,wParam的值也是这个代码;
我
们此时可以利用WSAGETASYNCERROR(lParam)来得知资料取得是成功或失
败;
如果失败的原因是原先传入的暂存区太小的话,我们亦可利用
WSAASYNCGETBUFLEN(lParam)来得知至少要多大的暂存区才够。
◎WSAAsyncGetHostByName():
利用某一host的名称来获取该host的资
料。
(非同步方式)
HANDLEPASCALFARWSAAsyncGetHostByName(HWNDhWnd,
unsignedintwMsg,constcharFAR*name,charFAR*buf,int
buflen);
hWnd动作完成後,接受讯息的视窗handle
wMsg传回视窗的讯息
namehost名称
buf存放hostent资料的暂存区
buflenbuf的大小
成功-代表此非同步动作的handle代码
失败-0(呼叫WSAG