现在的位置: 首页 > 综合 > 正文

windows网络编程第二版 第一章 winsock简介 读书笔记(转载)

2013年08月22日 ⁄ 综合 ⁄ 共 7374字 ⁄ 字号 评论关闭
Network Programming for Microsoft Windows, 2nd Edition Chapter 1 Winsock介绍 


1. Winsock是微软做的网络通讯库,以前winsock是1.1版本,现在winsock有了winsock 2.2版本,winsock2版本变动比较大,做了很多工作。Winsock的接口设计在很大程度上参考了UNIX平台上的BSD的socket实现,在 Winsock 2里面接口做了一些变动,目的是做到winsock真正和协议无关,是一个通用的开发平台。 


2. 本章介绍了TCP和UPD的两个最简单的winsock例子,实际上,这样的例子对于一般的网络通讯程序来说已经够用了。这些例子都是block的网络通讯方式,第五章会讲解non-block的winsock编程。 


3. Winsock 1和Winsock 2的函数的命名方式。一般来说,Winsock 2的函数都以WSA开头,比如创建一个socket在winsock 1中就是调用socket函数,在winsock 2中我们就可以使用WSASocket,相比socket,WSASocket提供了更多的特性。Winsock 2兼容Winsock 1的所有函数。上述的命名方式有一些例外,他们是:WSAStartup, WSACleanup, WSARecvEx, and WSAGetLastError,这四个函数在Winsock
1中就定义了。 


4. winsock的头文件和lib文件。这是开发winsock程序必须的了。在目前大部分的windows平台下,winsock 2都是ready的。Windows CE只支持winsock 1。在开发winsock 2的程序的时候,我们需要include winsock2.h,在开发winsock 1的程序的时候,我们需要include winsock.h。还有一个叫做mswsock.h,这里面定义的函数是只有在微软平台上运行的函数,这些函数能提供高性能,在我们书写需要高性能的网 络通讯程序的时候,我们使用这些函数,具体内容在第六章中描述。 


lib文件。winsock 2的程序需要链接ws2_32.lib, winsock 1的程序需要链接wsock32.lib,如果使用mswsock,需要链接mswsock.dll 


5. Winsock初始化。调用WSAStartup可以初始化winsock,也就是程序load winsock的dll文件。如果没有初始化winsock就调用了winsock中的函数,函数会返回 SOCKET_ERROR,SOCKET_ERROR是一个generic的返回值表示winsock操作失败,详细的错误信息可以通过调用 WSAGetLastError来获得,对于上述描述的错误,得到的错误码是WSANOTINITIALISED。WSAStartup函数的原型如下: 

Code: Select
all
int WSAStartup(

    WORD wVersionRequested,

    LPWSADATA lpWSAData

);




wVersionRequested 参数用来指定我们要使用的winsock版本。WORD中高字节部分用来指定使用的winsock的minor版本,低字节用来指定使用winsock的 major版本。我们可以使用MAKEWORD(x,y)宏来生成这样一个WORD, x是minor version,y是major version。


lpWSAData参数是一个struct指针,WSAStartup函数会填写这个struct中的内容,这个struct的定义是:

Code: Select
all
typedef struct WSAData 

{

    WORD           wVersion;

    WORD           wHighVersion;

    char           szDescription[WSADESCRIPTION_LEN + 1]; 

    char           szSystemStatus[WSASYS_STATUS_LEN + 1];

    unsigned short iMaxSockets;

    unsigned short iMaxUdpDg;

    char FAR *     lpVendorInfo;

} WSADATA, * LPWSADATA;




wVersion -- 使用的winsock版本

wHighVersion -- 目前平台上可用的winsock的最高版本

szDescription, szSystemStatus -- 用作特殊用途,一般不常用

iMaxSockets, iMaxUdpDg -- 不用使用这两个字段。他们定义了最大的并发连接数和最大的udp datagram size。然而,要找出正确的这些值,可能还要参考协议本身中的一些限制。

lpVendorInfo -- 保留用,用来存放vendor-specific information.不用使用这个字段。


6. 各种windows平台支持的winsock版本:


附件1


可见,大部分windows都支持winsock 2


7. winsock 1的程序可以在支持winsock 2的windows上良好运行。实际上,在winsock 2的windows上,所有的winsock 1的请求都会被映射到winsock 2的dll中。微软的兼容工作做的还是不错的。如果我们在WSAStartup中申请了一个高于目前平台支持的winsock版本,WSAStartup 会失败,同时WSADATA中的wHighVersion中会存放该平台上支持的winsock的最高版本。


8. 调用WSACleanup用来结束一个winsock程序。


int WSACleanup(void);


这个函数会释放所有资源,关闭所有pending的请求等。实际上,就算我们的程序不调用这个函数,操作系统也会把这些资源回收,但是,一个良好的程序,必须调用这个函数。


9. 错误处理。前面说过了,大多数的winsock函数失败的时候,都会返回SOCKET_ERROR,使用WSAGetLastError能获得详细的错误 码,这些错误码都定义在winsock2.h, winsock.h中。有些函数例外,他们不返回SOCKET_ERROR而是其他的返回值。下面是一个使用WSAGetLastError函数的例子:

Code: Select
all
#include <winsock2.h>


void main(void)

{

   WSADATA wsaData;


   // Initialize Winsock version 2.2


   if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)

   {

      // NOTE: Since Winsock failed to load we cannot use 

      // WSAGetLastError to determine the specific error for

      // why it failed. Instead we can rely on the return 

      // status of WSAStartup.


      printf("WSAStartup failed with error %d\n", Ret);

      return;

   }


   // Setup Winsock communication code here 


   // When your application is finished call WSACleanup

   if (WSACleanup() == SOCKET_ERROR)

   {

      printf("WSACleanup failed with error %d\n", WSAGetLastError());

   }

}




10. Addressing a Protocol. 本节只介绍基于IP的协议,参考第三章有很多关于网络协议的东西。本节只介绍在winsock中定义一个IPv4的信息结构。定义这样的结构对于winsock中的bind等这样的函数来说是必须的。

Code: Select
all
struct sockaddr_in

{

    short           sin_family;

    u_short         sin_port;

    struct in_addr  sin_addr;

    char            sin_zero[8];

};




sockaddr_in其实是针对Internet,也就是TCP/IP的一个结构。在后面我们会看到,任何结构将来都会转换成SOCKADDR这个结构,有点类似SOCKADDR是基类,sockaddr_in这些都是SOCKADDR的派生类一样。


sin_family -- 必须设成AF_INET,表示我们要使用IP地址family

sin_port -- 指定端口,不过要考虑网络次序和主机次序的问题,下面会介绍

sin_addr -- 指定IP地址。其实是指定这个struct中的一个字段。IP地址也是以long的类型存放的,也要处理网络次序和主机次序的问题。微软提供了一个function来让我们把一个IP字符串转换成一个long型的数值。这个函数是:

Code: Select
all
unsigned long inet_addr(

    const char FAR *cp 

);



注意,调用了inet_addr函数之后生成的long,就不需要再做主机次序,网络次序的转换了。但是后面会看到,如果没用inet_addr,比如给出的IP地址是INADDR_ANY的话,还是要调用htonl函数的。


sin_zero -- 没有用,完全是放在这里为了和SOCKADDR的大小兼容


11. 主机次序和网络次序。这个问题的起源是因为在上述的结构中,我们需要指定一个数字类型的变量,比如port和long型的IP地址。由于数字是多字节的一 块内容,所以就有字节摆放顺序的问题。众所周知,数字类型的变量在不同的计算机(主机)中存放的次序是不一样的,有把高位字节存在前面,低位字节存在后面 的,也有反过来的。对于网络通讯来说,必需要把这种情况统一起来,否则这些关键的信息就会解析错误了。于是就有了所谓的主机次序和网络次序的相互转换了。


winsock中提供了一组函数用来做这个事情,下面的四个函数用来将主机次序转成网络次序:

Code: Select
all
u_long htonl(u_long hostlong);


int WSAHtonl(

    SOCKET s,

    u_long hostlong,

    u_long FAR * lpnetlong

);


u_short htons(u_short hostshort);


int WSAHtons(

    SOCKET s,

    u_short hostshort,

    u_short FAR * lpnetshort

);




带 l的是处理4字节的数,带s的是处理2字节的数。不带WSA的函数传入主机次序的数值,返回网络次序的数;带WSA的是把主机次序的数传入 hostlong, hostshort参数,处理后的网络次序的数被填充在lpnetlong, lpnetshort指针指向的变量中。


以下四个函数从网络次序转成主机次序:

Code: Select
all
u_long ntohl(u_long netlong);


int WSANtohl(

    SOCKET s,

    u_long netlong,

    u_long FAR * lphostlong

);


u_short ntohs(u_short netshort);


int WSANtohs(

    SOCKET s,

    u_short netshort,

    u_short FAR * lphostshort

);




12. 给出一个例子用来示范怎么创建这么一个sockadd_in的东西:

Code: Select
all
SOCKADDR_IN InternetAddr;

INT nPortId = 5150;


InternetAddr.sin_family = AF_INET;


// Convert the proposed dotted Internet address 136.149.3.29

// to a four-byte integer, and assign it to sin_addr


InternetAddr.sin_addr.s_addr = inet_addr("136.149.3.29");


// The nPortId variable is stored in host-byte order. Convert

// nPortId to network-byte order, and assign it to sin_port.


InternetAddr.sin_port = htons(nPortId);




第三章会讲述如何在填写IP地址的时候,填写一个域名,以及winsock中包含的一些域名和IP地址互转换的函数。


13. 创建一个socket。socket是网络通讯程序中必须的一个数据结构,他不同于文件描述符,他有单独的数据类型定义-SOCKET。创建一个 socket可以调用socket方法或WSASocket。这里我们用socket函数来举例,后面会详细介绍这些方法。

Code: Select
all
SOCKET socket (

    int af,

    int type,

    int protocol

);




af -- 定义协议的address family,对于IPv4,当然是AF_INET 

type -- 定义socket类型。对于TCP,填写SOCK_STREAM,对于UDP,填写SOCK_DGRAM 

protocol -- 定义协议。如果我们在af或type中定义了多种类型的话,这里可以定义一个协议的组合。对于TCP,设置此项为IPPROTO_TCP,对于UDP,设置为IPPROTO_UDP


 

 

14. 基于TCP的winsock代码编写例子(包含server端和client端程序)。服务器端的程序需要依次调用如下函 数:socket/WSASocket, bind, listen, accept/WSAAccept,注意,本节中没有说bind, listen有对应的WSA的函数 


bind: 
Code: Select
all
int bind(

    SOCKET                     s, 

    const struct sockaddr FAR* name, 

    int                        namelen

);




示例代码:

Code: Select
all
SOCKET               s;    

SOCKADDR_IN          tcpaddr;

int                  port = 5150;

s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


tcpaddr.sin_family = AF_INET;

tcpaddr.sin_port = htons(port);    

tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);


bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));




这里IP地址指定成INADDR_ANY,表示绑定在本机,也就是说,如果本机有多块网卡的话,往任何一块网卡上指定的端口上发送的数据都能被上述程序得到。


bind 的错误处理。bind失败,返回SOCKET_ERROR,错误码是WSAEADDRINUSE,表示本机有另外一个进程已经使用了我们指定的IP地址和 端口或者是这个IP地址或端口处于TIME_WAIT状态(TIME_WAIT状态在下面会描述);此外,如果我们bind了一次之后又再次往同样的端 口,IP bind,那错误码是WSAEFAULT。


listen:

Code: Select
all
int listen(

    SOCKET s, 

    int    backlog

);




backlog -- 处理并发请求的数量。比如设成2,如果同时有三个并发请求过来,那么前两个会放入排队队列,第三个请求就会被拒绝,同时返回 WSAECONNREFUSED错误。一旦一个请求被accept处理之后,排队队列中就会清除这个请求,新的请求就可以进来了。backlog的最大值 和协议有关系,没有一个确切的方法可以给出一个具体的最大值,如果我们指定的backlog值超过了允许的范围,函数会帮我们把这个值设成最大允许的值。


listen的错误处理,一般返回WSAEINVAL,表示在listen之前没有调用bind。


accept:

Code: Select
all
SOCKET accept(

    SOCKET s, 

    struct sockaddr FAR* addr, 

    int FAR* addrlen

);




accept 中除了socket之外的两个参数是OUT类型的,也就是accept会填写这两个参数。accept会取出排队队列中的第一个request,然后处 理,addr中会存放client的IP地址,端口等信息,addrlen中存放的是addr结构的大小。此外,accept返回一个SOCKET变量, 利用这个socket变量,server端程序就能和client端程序进行send/recv这样的操作了。


accept的错误处理, 出错时,accept的返回值是INVALID_SOCKET,错误码有WSAEWOULDBLOCK,当我们使用非阻塞的non-block的 listen方法,而当前队列中没有可服务的request的时候,会产生这个错误码。WSAAccept方法是winsock 2中的方法,这个方法比accept增强了一些特性,比如可以给定一个condition,这个condition返回true的时候才accept一个 connection。具体看第10章


下面给出示例代码,这段代码是核心代码演示,不一定完整,而且这段代码只会accept一次就退出了,正常程序应该有个while循环:

Code: Select
all
#include <winsock2.h>


void main(void)

{

   WSADATA              wsaData;

   SOCKET               ListeningSocket;

   SOCKET               NewConnection;

抱歉!评论已关闭.