【转】windows socket编程 (概要)

来源:互联网 发布:网络监控防雷器 编辑:程序博客网 时间:2024/05/16 15:09

 socket编程是所有协议实现的底层,任何协议都可以用socket来实现。

Winsock启动

      winsock服务是以动态链接库Winsock DLL形式实现的,所以必须先对Winsock DLL进行初始化,协商Winsock的版本支持,并分配必要的资源,函数原型为:

      int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);

      参数wVersionRequested用于指定准备加载的Winsock库的版本,高位字节表示副版本,低位字节表示主版本。

      lpWSAData是指向WSADATA类型的指针,该结构中包含了加载的库版本相关信息。WSADATA结构如下:

      typedef struct WSAData {

       WORD wVersion;

       WORD wHighVersion;

       char szDescription[WSADESCRIPTION_LEN+1];

       char szSystemStatus[WSASYSSTATUS_LEN+1];

       unsigned short iMaxSockets;

       unsigned short iMaxUdpDg;

       char *lpVendorInfo;

      }WSADATA,*LPWSADATA;

      举个例子,想加载主版本副版本都为2的Winsock库:

      WASDATA WSAData;

      WSAStartup(0x0202,&WSAData);

      WSAData中wVersion返回希望加载的Winsock版本。wHighVersion返回现有Winsock库的最高版本。

Winsock终止

      程序结束时,应该终止对Winsock DLL的使用,并释放资源,以备下次使用。函数原型如下:

      int WSACleanup(void);

      成功调用则返回0,否则返回错误。

Winsock错误检查

      一般情况下,Winsock API失败时返回SOCKET_ERROR,这个时候可以用WSAGetLastError函数来得到最近一次发生错误的错误码,函数原型如下:

      int WSAGetLastError(void);

      这些值都在Winsock1.h或者Winsock2.h中定义。

Winsock流套接字客户端编程模型

建立套接字

      假设以面向连接的流套接字的客户端为例,首先建立一个windows 套接字socket,函数原型为:

      SOCKET  socket(int af,int type,int protocol);

      其中,af用于指定网络地址类型,一般情况下取为AF_INET

               type表示套接字类型,SOCK_STREAM表示创建流套接字,SOCK_DGRAM表示创建数据包套接字

               protocol用于指定网络协议,默认为0,表示TCP/IP协议,其他例如IPPROTO_UDP表示UDP协议

      所以创建一个流套接字如下所示:

      SOCKET sock(AF_INET,SOCK_STREAM,0);

      如果创建失败则返回INVALID_SOCKET错误。

客户端发出连接请求

      创建流套接字成功之后,就需要和服务器建立连接,函数原型为:

      int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);

      s表示一个未连接的套接字,name是针对TCP的套接字地质结构,用于标识服务器进程的IP、PORT等信息,namelen一般取为sizeof(name),用于标识name的长度。

      若name中地址域全为零的话,则会产生WSAEADDRNOTAVAIL错误。

      若服务器没有侦听这一端口的进程,则会产生WSAECONNREFUSED错误。

      若因网络原因连接超时,则会产生WSAETIMEOUT错误。

数据传输

      一旦连接成功,就可以互相进行数据的发送和接收了,发送函数原型如下:

      int send(SOCKET s,const char* buf,int len,int flags);

      s表示已连接的套接字。

      buf是发送字节缓冲区。

      len表示要发送的字节个数。

      flags取值包括0、MSG_DONTROUTE(禁止路由)、MSG_OOB(数据带外发送),一般情况下取0。

      接收函数原型如下:

      int recv(SOCKET s,char* buf,int len,int flags);

      flags可以是0、MSG_PEEK(有用数据复制到提供的接收端缓冲区,但不从系统缓冲区删除)、MSG_OOB。

      recv函数返回接收字节数。若失败,则返回SOCKET_ERROR。

关闭套接字

      当所有任务完成之后,因必须关掉连接以释放套接字占用的所有资源。

      为了避免数据丢失,先通过shutdown函数来中断连接,然后再调用closesocket关闭套接字。shutdown函数原型为:

      int shutdown(SOCKET s,int how);

      how用于描述终止那些操作,取值有SD_RECEIVE(表示不允许在调用接收函数),SD_SEND(表示不允许在调用发送函数),以及SD_BOTH。

      closesocket用于释放套接字占用的所有资源。函数原型如下:

      int closesocket(SOCKET s);     

Winsock流套接字服务器端编程模型

建立套接字

      假设以面向连接的流套接字的客户端为例,首先建立一个windows 套接字socket,函数原型为:

      SOCKET  socket(int af,int type,int protocol);

      其中,af用于指定网络地址类型,一般情况下取为AF_INET,表示此socket作用于internet域。

               type表示套接字类型,SOCK_STREAM表示创建流套接字,SOCK_DGRAM表示创建数据包套接字。

               protocol用于指定网络协议,默认为0,表示TCP/IP协议,其他例如IPPROTO_UDP表示UDP协议。

      所以创建一个流套接字如下所示:

      SOCKET sock(AF_INET,SOCK_STREAM,0);

      如果创建失败则返回INVALID_SOCKET错误。

绑定本地地址

      将本地地址绑定到所创建的套接字上一边在网络上表示该套接字。bind函数原型如下:

      int bind(SOCKET s,const struct sockaddr * name, int namelen);

      name参数是该套接字绑定的本地地址。struct sockaddr结构如下:

      struct sockaddr{

         u_short  sa_family;

         char       sa_data[14];

      }

      sa_family即是网络地址类型,必须设为AF_INET,表示此socket作用于internet域。

      sa_data会因为所用协议不同而产生变化,故而在一般的TCP/IP协议中定义了一个与sockaddr大小一样的结构体sockaddr_in,在TCP/IP写一下,可以方便的互相转换。

      struct sockadd_in{

         short                  sin_family;

         unsigned short   sin_port;

         struct in_addr    sin_addr;

         char                   size_zero[8];

      }

      sin_port:指定服务器侦听端口,需要把端口号从主机顺序转换成网络顺序,htons()用于把端口号从主机顺序转换成网络顺序,ntohs反过来。

      sin_addr:把一个主机ip地址保存成一个4字节的无符号长整形数。

      inet_addr()用于把一个点式ip转换成一个32位无符号长整形,并且已经是网络顺序,不需要再调用htonl()将其转换成网络顺序。

     inet_ntoa()反过来,讲一个struct in_addr结构的地址转换成一个char*字符串,此字符串存储在windows套接字专用的内存中,即每次调用inet_ntoa()返回的字符串都在同一地址。

      有一个特殊的ip地址INADDR_ANY,使用时需要使用htonl()转换,他表示服务器可以监听本地所有地址,当主机有多个网卡时有用。

      sockaddr_in addr;

      addr.sin_family=AF_INET;
      addr.sin_addr.s_addr=htonl(INADDR_ANY);

      //addr.sin_addr.s_addr=inet_addr(“172.16.200.254”);
      addr.sin_port = htons(port);

      赋值的时候不是给结构体变量sin_addr赋值,而是向sin_addr.s_addr赋值,而inet_ntoa()的参数则是struct in_addr类型。

开始监听模式

      将套接字置于监听模式,函数原型如下:

      int listen(SOCKET s,int backlog);

      backlog用于指定正在等待连接的最大队列长度,而不是已连接最大队列长度。

接收连接请求

      使监听套接字做好接受客户端连接的准备,函数原型如下:

      SOCKET accept(SOCKET s,struct sockaddr * addr,int * addrlen);

      函数返回时,addr是发出连接请求的客户端的ip地址信息,并且返回一个新的套接字描述符,之后就应该用这个新套接字和客户端进行所有操作。

      当无连接请求时,此函数被阻塞。