一、Windows Sockets API简介
VC++对网络编程的支持有socket支持,WinInet支持,MAPI和ISAPI支持等。其中,Windows Sockets API是TCP/IP网络环境里,也是Internet上进行开发最为通用的API。最早美国加州大学Berkeley分校在UNIX下为TCP/IP协议开发了一个API,这个API就是著名的Berkeley Socket接口(套接字)。
在桌面操作系统进入Windows时代后,仍然继承了Socket方法。在TCP/IP网络通信环境下,Socket数据传输是一种特殊的I/O,它也相当于一种文件描述符,具有一个类似于打开文件的函数调用-socket()。
可以这样理解:Socket实际上是一个通信端点,通过它,用户的Socket程序可以通过网络和其他的Socket应用程序通信。Socket存在于一个“通信域”(为描述一般的线程如何通过Socket进行通信而引入的一种抽象概念)里,并且与另一个域的Socket交换数据。Socket有三类。第一种是SOCK_STREAM(流式),提供面向连接的可靠的通信服务,比如telnet,http。第二种是SOCK_DGRAM(数据报),提供无连接不可靠的通信,比如UDP。第三种是SOCK_RAW(原始),主要用于协议的开发和测试,支持通信底层操作,比如对IP和ICMP的直接访问。
二、Windows Socket机制分析
2.1一些基本的Socket系统调用     主要的系统调用包括:socket()-创建Socket;bind()-将创建的Socket与本地端口绑定;connect()与accept()- 建立Socket连接;listen()-服务器监听是否有连接请求;send()-数据的可控缓冲发送;recv()-可控缓冲接收;closesocket()-关闭Socket。
2.2Windows Socket的启动与终止   启动函数WSAStartup()建立与Windows Sockets DLL的连接,终止函数WSAClearup()终止使用该DLL,这两个函数必须成对使用。
2.3异步选择机制  Windows是一个非抢占式的操作系统,而不采取UNIX的阻塞机制。当一个通信事件产生时,操作系统要根据设置选择是否对该事件加以处理,WSAAsyncSelect()函数就是用来选择系统所要处理的相应事件。当Socket收到设定的网络事件中的一个时,会给程序窗口一个消息,这个消息里会指定产生网络事件的Socket,发生的事件类型和错误码。
2.4异步数据传输机制   WSAAsyncSelect()设定了Socket上的须响应通信事件后,每发生一个这样的事件就会产生一个WM_SOCKET消息传给窗口。而在窗口的回调函数中就应该添加相应的数据传输处理代码。
三、WinSock基本定义:
WinSock是Microsoft Windows Socket的简称,WinSock为软件开发人员提供一套Windows操作系统上的开放的、支持多种协议的网络编程接口,已成为在TCP/IP网络编程和Internet应用开发中通用的解决方案。 WinSock对一些重要的数据类型和结构做了如下定义。
1、套接字的定义
typedef unsigned int u_int;
     typedef u_int Socket;
2、基本数据类型的定义
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
3、网络地址的数据结构
网络地址用无符号长整数unsigned long 表示。例如IP地址127.0.0.1可以表示为:
#define INADDR_LOOPBACK 0x7f000001
用inet_addr()函数可以将分16位IP地址转换为unsigned long 类型,其定义为:
unsigned long inet_addr(const char FAR* cp)
Cp为点分16位IP地址,如“192.1.8.84”。
4、套接字地址结构
sockaddr结构——通用Socket地址结构,其定义为:
struct sockaddr{u_short sa_family;char sa_data[14];};
其中sa_family为网络地址类型,随协议的不同而不同,一般为AF_INET,表示该socket在Internet域中通信。
sockaddr_in结构——专门针对Internet域的Socket地址结构,其定义为:
struct sockaddr_in
{
     short sin_family;
     u_short sin_port;
     struct in_addr sin_addr;
    char sin_zero[8];
};
其中sin_family必须设定为AF_INET;sin_port为服务器端口,如果端口设置为0,则系统会自动分配一个唯一的端口;sin_addr为一个unsigned long 的IP地址,若sin_addr为INADDR_ANY,则表示所有的IP地址;sin_zero为填充字段,用来保证结构的大小;
5、主机地址
struct hostent
{
     char FAR* h_name;                //主机名字
     char FAR*FAR* h_aliases;     //主机别名列表
     short h_addrtype;                    //地址类型
     short h_length;                        //地址长度
    char FAR*FAR* h_addr_list;    //IP地址
    #define h_addr h_addr_list[0];
};
6、常见TCP/IP协议的定义
#define IPPROTO_IP          0
#define IPPROTO_ICMP    1
#define IPPROTO_IGMP    2
#define IPPROTO_TCP      6
#define IPPROTO_UDP     17
#define IPPROTO_RAW    255
四、WinSock基本函数:
以下介绍几个最基本的WinSock函数,用这些函数就可以完成从客户端到服务器端的套接字编程。其基本流程如下图所示:
基于(TCP/IP面向连接)的socket编程:
服务器端程序: 1.创建套接字(socket)。 2.将套接字绑定到一个本地地址和端口上(bind) 3.将套接字设为监听模式,准备接受客户请求(listen) 4.等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。 5.用返回的套接字和客户端进行通信(send/recv)。 6.返回,等待另外客户请求。 7.关闭套接字。
客户端程序: 1.创建套接字(socket)。 2.向服务器发出连接请求(connect)。 3.和服务器端进行通信(send/recv)。 4.关闭套接字。
基于UDP(面向无连接)的socket编程 :
服务器端(接收端)程序: 1.创建套接字(socket)。 2.将套接字绑定到一个本地地址和端口上(bind)。 3.等待接受数据(recvfrom)。 4.关闭套接字。
客户端(发送端)程序: 1.创建套接字(socket)。 2.向服务器发送数据(sendto)。 3.关闭套接字。
1、socket()
功能:创建一个新的套接字。
原形:Socket PASCAL FAR socket(int af,int type,int      protocol);
参数:af——通信发生的区域;
               type——要建立的套接字类型;
              procotol——使用的协议类型。

2、Bind()
功能:为一个新创建未绑定的Socket分配端口和local地址。对客户机/服务器构架的程序,服务器必须使用此函数为Socket分配端口号,客户机不用绑定,而是由系统自动分配。
原形:int PASCAL FAR Bind(Socket s,
                     const struct sockaddr FAR* name,int namelen);
参数:s——由socket()调用返回的并且未作连接的Socket识    别符。Name——WinSock地址结构,因协议类型而不同,对应TCP/IP协议的结构为 sockaddr_in。namelen——表示name的长度。
3、connect()和accept()
功能:用于在客户机/服务器构架的程序中建立套接字连接,客户端用connect()要求连接某一流式套接字到指定服务器,服务器用accept()完成套接字中服务器端Socket,接受客户端的连接请求。
原形:int PASCAL FAR connect(Socket s,
                     const struct sockaddr FAR* name,int namelen);
               Socket PASCAL FAR accept(Socket s,
                    struct sockaddr FAR* addr,int FAR *addrlen);
参数:name——服务器端Socket将要建立连接的客户端地址; namelen——表示name的长度。addr——要求与服务器端Socket建立连接的客户端地址;addrlen——addr的长度。
返回值:connect若成功返回0,否则返回SOCKET_ERROR;
                   accept返回的Socket是与客户端通信的Socket。
4、listen()
功能:设定Socket状态为监听,监听客户端的请求,准备被连接。
原形: int PASCAL FAR listen(Socket s,int backlog);
参数:backlog——未完成连接之前,客户端连接请求的最大数。
返回值:若成功则返回0,否则返回SOCKET_ERROR。
5、send()与recv()
功能:在已建立连接的流式套接字之间收发数据, send()发送数据, recv()接收数据。
原形:int PASCAL FAR send(Socket s,
                     const char FAR* buf,int len,int flags);
               int PASCAL FAR recv(Socket s,
                     const char FAR* buf,int len,int flags);
参数:s——发送或接受数据的套接字。
               buf——发送或接受数据的缓冲区。
               len——buf的长度;
               flag——函数被调用的方式,其值为0,MSG_PEEK
                           或MSG_OOB.
6、 sendto()与recvfrom()
功能:若前图建立的不是流式套接字的话,就要用sendto()与recvfrom()代替send()与recv()来读写数据。功能与send()与recv()相同。
原形:int sendto(Socket s,const char FAR* buf,int len,
                    int flags,const struct sockaddr FAR* to,int tolen);
               int recvfrom(Socket s, char FAR* buf,int len,
                  int flags, struct sockaddr FAR* from,int FAR* fromlen);
参数:to——指向目标套接字地址的指针(可选);
               tolen——to的长度;
               from——指向保存着返回的源地址的缓冲区的指针
                            (可选);
               fromlen——指向from缓冲区的指针(可选)。
返回值:若成功返回发送或接受到的数据长度,否则返回
                   SOCKET_ERROR。
7、closesocket()
功能:关闭套接字;
原形: BOOL PASCAL FAR closesocket(Socket s);
参数:s——要关闭的套接字
返回值:若成功则返回0,否则返回SOCKET_ERROR。
五、WinSock扩展:
WinSock对最早的Socket进行了一些扩展,主要是增加了符合Windows消息驱动特性的网络事件异步选择机制,扩充了一些异步函数。主要异步函数如下:
1、异步选择函数
功能:用来注册应用程序相关的网络事件,当事件发生时,应用程序窗口函数将接收到一个消息。
原形:int PASCAL FAR WSAAsyncSelect(
                      Socket s,                 
                      HWND hWnd,             //窗口句柄
                     Unsigned int wMsg,   //需要发送的消息
                     Long lEvent );            //事件类型
     事件类型的值可以是:
      FD_READ:当套接字收到数据时接到通知;
      FD_WRITE:当套接字可发送数据时接到通知;
      FD_OOB:当套接字有外来数据到达时接到通知;
      FD_ACCEPT:当套接字有外来连接时接到通知;
FD_CONNECT:当套接字连接建立完成时接到通知;
          FD_CLOSE:当套接字关闭时接到通知;
2、异步请求函数WSAAsyncGetXByY()
3、阻塞处理方法
当一个应用程序的套接字调用处于阻塞时,应能够使套接字放弃CPU让其他应用程序运行。WinSock的阻塞处理为:DLL初始化->循环操作。在循环中发送任何Windows消息,并检查此WinSock调用是否完成,必要时可以放弃CPU让其他应用程序执行。可以调用 WSACancelBlockingCall()函数取消此阻塞处理。
在WinSock中,默认的阻塞处理例程BlockingHook()能够获取并发送Windows消息。如果要对复杂的程序进行处理, WinSock中的WSASetBlockingHook()使得用户能够安装自己的阻塞处理例程;与该函数相对应的则是 WSAUnhookBlockingHook(),用于删除先前安装的任何阻塞处理例程,并重新安装默认的处理例程。应注意,设计自己的阻塞处理例程时,除了WSACancelBlockingCall()以外不能使用其他的WinSock API函数。调用WSACancelBlockingCall()函数将取消处于阻塞的操作,结束阻塞循环。
4、出错处理
    WinSock为了和多线程环境兼容,提供了WSAGetLastError()和WSASetLastError()两个出错处理函数来获取和设置当前线程的最近错误代码。
     WSAGetLastError()函数的功能是返回故障类型,其定义为:int WSAGetLastError(void);
5、启动与终止
    使用WSAStartup()和WSACleanup()启动和终止套接字。
WSAStartup()完成与Winsock.DLL的连接;
WSACleanup()结束对Winsock.DLL的调用。
六、WinSock编程:
WinSock包括开发组件和运行组件两大部分,开发组件包括WinSock实现文档、应用程序接口(API)引入库和一些头文件。运行组件包括 WinSock应用程序接口的动态连接库。在VC++6.0中使用WinSock编程时,需要将以下三个文件包含到项目中(以WinSock V2.0为例)
头文件: Winsock2.h
库文件:WS2_32.LIB
动态库: WS2_32.DLL
    在TCP/IP网络中两个主机进行进程间异步通信时采用客户端/服务器模式。其中服务器工作过程如下:
1、服务器方先开启,打开一通信通道,公布接收客户请求的端口地址;
2、等待客户端发送请求到该端口;
3、接收到客户端服务请求,处理该请求并发送应答信号;
4、重复2、3步,处理多个客户请求;
5、关闭服务器。
客户端工作过程如下:
1、打开一通信通道,连接到服务器所在主机的开放端口;
2、向服务器发送服务请求,等待应答信号;
3、收到应答信号后,关闭通信通道。
下面看一看WinSock网络应用程序的详细步骤。
1、启动与终止:
    必须首先使用启动函数WSAStartup(),它指定WinSock API的版本,并获得Socket技术细节。调用方式为:
         WORD wVersionRequested; //定义版本信息变量
         WSADATA wsaData;              //定义数据信息变量
         int err;                                      //定义错误号变量
        wVersionRequested=MAKEWORD(1,1);
        //给版本信息赋值
        err= WSAStartup(wVersionRequested,&wsaData);
       //获取错误信息
        if(err!=0) return;
    程序可多次调用WSAStartup()函数,但每次调用时wVersionRequested必须相同。
2、创建套接字:
    Socket sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
     //流式套接字
     if(sock==INVALID_Socket)   //错误处理
           …..
3、套接字的绑定:
     sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(0);                                    //保证字节顺序
    addr.sin_addr.s_addr=addr(“192.168.12.84”);   //指定地址
    int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
4、服务器端套接字的监听:
    int nResult=listen(s,5);
    //s是已绑定但未连接的套接字,最多监听5个连接
5、服务器端套接字等待连接:
     sockaddr_in addr;
     Socket s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
6、客户端将两个套接字连接起来准备通信:
     sockaddr_in addr;
    addr.sin_family=AF_INET;
     addr.sin_port=htons(0);               //保证字节顺序
     addr.sin_addr.s_addr=htonl(INADDR_ANY);
     int nResult=connect(s, (sockaddr*)&addr,sizeof(sockaddr));
7、服务器端向客户端套接字发送数据:
     char buf[ ]=“einsun”; //指定缓冲区
     int nResult=send(s,buf,strlen(buf));
8、客户端套接字接收数据:
    char mess[1000];
    int nResult=recv(s,mess,1000,0);
9、服务器端和客户端中断套接字连接,通知服务器端或客户端停止接收和发送数据:
     int nResult=shutdown(s,SD_BOTH);
10、服务器端或客户端关闭套接字,释放所占有的资源:
    int nResult=closesocket(s);//s为要关闭的套接字
    关闭套接字时,所有已经打开并连接的流式套接字将被复位,但那些已经由closesocket()函数关闭,但仍有未发送数据的套接字不受影响,仍然可发送未发完的数据。
11、出错处理
    出错处理函数WSAGetLastError()的调用方式如下:
    len=send(s,lpBuffer,len,0);
    if((len==socket_ERROR)
          &&(WSAGetLastError()==WSAWOULDBLOCK))
    {
         …….
     }