win socket编程摘要

摘自:罗云彬的《Win32汇编教程》

       最主要的两种是流套接字(stream socket)和数据报套接字(datagram socket)。流套接字使用传输层的TCP协议进行通信,所以它具有TCP协议所拥有的各种特征,比如它是面向连接的、稳定的,及数据包是顺序发送的等;而数据报套接字使用UDP协议进行通信,所以它的特征同样来自于UDP协议,如数据包可能丢失,可能重复,及可能不按顺序到达等。
       也存在其他一些不常用的套接字类型,如原始套接字(raw socket)、可靠信息分递套接字(rdm socket)和连续小分包套接字(seqpacket socket)等,其中值得一提的是原始套接字,使用这种套接字可以直接在Internet层上处理IP数据包的首部,所以可以用它来实现各种特殊的功能,如伪造发送者地址等。


使用WinSock接口编程的一般步骤是:
(1)装入并初始化WinSock动态链接库。
(2)创建一个套接字。
(3)指定这个套接字的工作方式:使用阻塞模式还是非阻塞模式。
(4)如果使用TCP协议,则将套接字连接到对端主机的套接字(不使用TCP协议则不要这一步)。
(5)如果需要手工指定套接字使用的IP地址和端口号,那么将套接字绑定到指定的地址和端口(如果由系统自动分配地址和端口则不需要这一步)。
(6)通过套接字收发数据。
(7)关闭套接字。
(8)释放WinSock动态链接库。


invoke  WSAStartup,wVersionRequested,lpWSAData
    .if     eax
            ;初始化库出现错误
    .endif
wVersionRequested是一个16位的参数,用来指定动态链接库将支持哪个版本的WinSock函数,其中的低8位指定主版本号,高8位用来指定副版本号,假如要使用1.1版的,可以在这里使用0101h,假如需要使用2.0版本函数,则可以将参数指定为0002h。
lpWSAData参数指向一个WSADATA结构,用来返回动态链接库的详细信息,结构的定义为:
WSADATA STRUCT
  Wversion      WORD      ?      ;库文件建议应用程序使用的版本
  wHighVersion   WORD      ?      ;库文件支持的最高WinSock版本
  szDescription BYTE WSADESCRIPTION_LEN + 1 dup (?) ;库描述字符串
  szSystemStatus  BYTE WSASYS_STATUS_LEN + 1 dup (?) ;系统状态字符串
  iMaxSockets   WORD      ?      ;同时支持的最大套接字数量
  iMaxUdpDg     WORD      ?      ;2.0版中已废弃的参数
  lpVendorInfo  DWORD     ?      ;2.0版中已废弃的参数
WSADATA ENDS
szDescription字段中返回的字符串一般是“WinSock 2.0”之类的库描述串,szSystemStatus字段中返回的是类似于“Running”一类的运行状态字符串。

如果库装入成功,函数将返回0,否则将返回下面的出错代码:
●   WSASYSNOTREADY——网络子系统未准备好。
●   WSAVERNOTSUPPORTED——不支持指定的版本。
●   WSAEINPROGRESS——另一个阻塞方式的WinSock 1.1操作正在进行中。
●   WSAEPROCLIM——WinSock接口已经到达了所支持的最大任务数。
●   WSAEFAULT——输入参数lpWSAData指定的指针无效。
如果不再需要使用WinSock函数了,那么必须使用WSACleanup 函数将库释放:


套接字是通信的一端,当装载WinSock库以后,在开始通信之前必须为通信进程建立一个套接字,如果在一个程序中同时使用多个通信进程(比如用TCP协议同时连接到几个不同的主机),那就必须为每个通信连接创建一个套接字,创建套接字使用socket函数:
    invoke  socket,af,type,protocol
    .if     eax !=  INVALID_SOCKET
            mov hSocket,eax
    .endif
函数的第一个参数af用来指定套接字使用的地址格式。在不同操作系统下,这个参数可以指定为AF_UNSPEC,AF_UNIX或AF_OSI等不同的值,但是在WinSock中只能使用AF_INET(也可以使用PF_INET,在Windows.inc中PF_INET被定义为与AF_INET等效)。

第二个参数type用来指定套接字的类型。正如前面介绍的,套接字有流套接字、数据报套接字和原始套接字等,下面是最常用的几种套接字类型定义:
●   SOCK_STREAM——流套接字,使用TCP协议提供有连接的和可靠的传输。
●   SOCK_DGRAM——数据报套接字,使用UDP协议提供无连接的和不可靠的传输。
●   SOCK_RAW——原始套接字,WinSock接口并不使用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部。

protocol参数配合type参数使用,用来指定使用的协议类型,当type参数指定为SOCK_STREAM或者SOCK_DGRAM的时候,系统已经明确确定使用TCP和UDP协议来工作,所以这时这个参数并没有什么意义,可以指定为0,但是当type参数指定为SOCK_RAW类型的时候,使用protocol参数可以更详细地指定原始套接字的工作方式。

当type参数指定为SOCK_RAW类型时,可以将protocol参数指定为以下的数值:
●   IPPROTO_IP,IPPROTO_ICMP,IPPROTO_TCP和IPPROTO_UDP——分别指定使用IP,ICMP,TCP和UDP协议,这时系统会自动为数据加上IP首部,并且将IP首部中的上层协议字段设置为指定的这些协议名称。但是使用这个套接字接收数据时,系统却不会将IP首部自动去除,需要程序自行分析处理(如果在以后将套接字的属性设置上IP_HDRINCL选项的话,那么发送时系统将不
自动添加IP首部,这时需要自己封装数据包)。
●   IPPROTO_RAW——系统将数据包直接送到网络访问层发送,程序需要自己添加IP首部以及其他协议首部,并且需要自己计算和填充协议首部中的校验和字段。当使用IPPROTO_RAW协议类型的原始套接字时,这个套接字只能用来发送数据包而无法接收数据包。这是因为所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送这样一个原始数据包的时候,核心并不知道,也没有这个数据被发送或者连接建立的记录,因此当远端主机回应的时候,系统核心就把这些包给丢弃而不是送到应用程序中。
当套接字被成功创建的时候,函数将返回一个套接字句柄,否则函数将返回INVALID_SOCKET,这时可以继续调用WSAGetLastError函数获取更详细的出错信息。当套接字被成功创建的时候,函数将返回一个套接字句柄,否则函数将返回INVALID_SOCKET,这时可以继续调用WSAGetLastError函数获取更详细的出错信息。

当不再需要使用套接字的时候,需要使用closesocket函数将它关闭:
    invoke  closesocket,s
参数s就是创建套接字时返回的套接字句柄。


当一个套接字被创建的时候,它默认工作在阻塞模式下,所以如果不需要改变它的工作模式,可以直接跳过“设置为非阻塞模式”这一步,但是因为Windows程序的工作特点,WinSock强烈建议程序员使用非阻塞模式。有两个函数可以用来改变一个套接字的模式:ioctlsocket函数和WSAAsyncSelect函数。


ioctlsocket函数从BSD UNIX Socket规范中延续过来,它的用法是:
    invoke  ioctlsocket,s,cmd,argp
参数s指定需要被设置模式的套接字句柄,cmd为命令参数,argp是一个指针,指向一个被cmd命令使用的参数。当cmd被指定为FIONBIO时,函数被用来改变套接字模式,这时如果argp指向的变量值为1,那么套接字的工作模式被设置为非阻塞模式;如果变量中的值为0,那么套接字被设置为阻塞模式。如下面的代码将一个套接字设置为非阻塞模式:
            invoke  ioctlsocket,hSocket,FIONBIO,addr dwArg
            .if     eax ==  SOCKET_ERROR
                    ;发生错误
            .endif
但是,这个函数用起来并不方便,因为用这种方法将套接字设置为非阻塞模式后,其结果就是对这个套接字的操作会马上返回,而在操作最终完成以后,函数并不会通过某种机制通知程序操作已经完成,还需要程序去不停地查询操作结果,所以建议使用WinSock接口的WSAAsyncSelect函数来设置工作模式。

WSAAsyncSelect函数是WinSock接口中的特有函数,它仅针对Windows的消息体系工作,所以BSD UNIX Socket中并没有这个函数。这个函数将套接字设置为非阻塞模式,并且打开操作完成后的通知机制,通知消息可以被绑定到某个窗口句柄中,这样程序就不必不停地去查询套接字的操作是否已经完成,而是在窗口过程中等收到通知消息后再进行处理。
WSAAsyncSelect函数的用法是:
    invoke  WSAAsyncSelect,s,hWnd,wMsg,lEvent
s参数指定需要设置的套接字句柄,hWnd指定一个窗口句柄,套接字的通知消息将被发送到与其对应的窗口过程中。
通知消息的ID可以由程序自己定义,当不同的动作完成以后,不同的通知码将被包含在消息的参数中传递给指定的窗口过程,wMsg参数用来定义通知消息使用的ID,可以在WM_USER以上数值中任意选择一个用做ID,最后的参数lEvent指定哪些通知码需要发送,它可以被指定为几个通知码的组合,一般常用的通知码有下面这些:
●   FD_READ——套接字收到对端发送过来的数据包,表明这时可以去读套接字。
●   FD_WRITE——当短时间内向一个套接字发送太多数据造成缓冲区满以后,发送函数会返回出错信息,当缓冲区再次变空的时候,WinSock接口通过这个通知码通知应用程序,表示可以继续发送数据了。但是缓冲区未溢出的情况下数据被发送完毕的时候并不会发送这个通知码。
●   FD_ACCEPT——监听中的套接字检测到有连接进入(适用于流套接字)。
●   FD_CONNECT——如果用一个套接字去连接对方主机,当连接动作完成以后将收到这个通知码(适用于流套接字)。
●   FD_CLOSE——检测到套接字对应的连接被关闭(适用于流套接字)。
在使用中并不需要指定全部这些通知码。例如,对于数据报套接字来说,它并不需要一个连接的过程,所以FD_CONNECT,FD_CLOSE和FD_ACCEPT等通知码是没有意义的,而在流套接字中,如果一个套接字不是用于监听,那么FD_ACCEPT也是没有意义的,所以要根据套接字的类型选用合适的通知码。


使用bind函数可以为套接字指定IP地址和端口,这个过程称为绑定。函数用法如下:
    invoke  bind,s,lpsockaddr,len
参数s指定套接字句柄,lpsockaddr参数是一个指向sockaddr_in结构的指针,len参数指定sockaddr_in结构的长度。sockaddr_in的定义是:
sockaddr_in STRUCT
  sin_family    WORD      ?       ;地址格式
  sin_port      WORD      ?       ;端口号(使用网络字节顺序)
  sin_addr      in_addr <>       ;IP地址(使用网络字节顺序)
  sin_zero      BYTE 8 dup (?)   ;空字节
sockaddr_in ENDS
结构中的sin_family 字段用来指定地址格式,这个字段和socket函数中af参数的含义是相同的,所以惟一可以使用的值就是AF_INET。sin_port字段和sin_addr字段分别指定套接字需要绑定的端口号和IP地址,放入这两个字段的数据的字节顺序必须是Internet顺序,由于字节顺序和Intel CPU的字节顺序刚好相反,所以必须首先经过转换,比如当端口号为9 999时,转换成16进制是270Fh,那么放入sin_port字段的数值就应该是转换以后的0F27h。
 sockaddr_in 结构是WinSock编程中最常用的结构,凡是涉及通信地址的时候都会用到这个结构,比如使用connect发起连接和使用sendto发送数据时的目标地址都是使用这个结构指定的。


读者可能会问一个问题,结构中存在一个定义IP地址的sin_addr字段,这样的话岂不是必须先获取本机的IP地址后才能绑定,否则如何填写这个字段呢?实际上仅在当前计算机配置有多个IP地址,而程序又只想绑定到其中一个IP地址的时候才需要具体指定使用哪一个地址,否则可以使用预定义值ADDR_ANY,这样系统会自动使用当前主机配置的所有IP地址。
使用bind函数绑定IP地址和端口的时候,端口号可以指定为任意一个16位值,也可以使用小于1 024的值,惟一的限制就是不能去绑定已经在使用中的端口号。

如果绑定成功,函数返回0,否则函数返回SOCKET_ERROR,这时程序可以继续调用WSAGetLastError函数获取进一步的出错代码。一般来说,绑定错误是由下面几个原因引起的,对应的出错代码如下:
●   WSAEADDRINUSE——指定的IP地址或端口已经在使用中。
●   WSAEFAULT——指定的结构、地址和端口等数据无效。
●   WSAEINVAL——套接字已经被绑定到某个地址,绑定只能进行一次,对一个已经绑定过的套接字再次调用bind函数就会出现这个错误。


当完成了前面的库初始化,创建套接字,设置套接字模式以及绑定工作后,就可以使用套接字收发数据了。流套接字和数据报套接字数据收发时适用的函数有所不同,一般对流套接字使用send和recv函数来收发数据,对数据报套接字使用sendto和recvfrom函数。对于流套接字来说,开始收发数据之前还需要有一个监听或连接的过程,这时还要用到listen,accept和connect函数。另外,WinSock接口还提供一些与主机名解析有关的函数,如gethostbyname和gethostname等。








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值