C++ Winsock网络编程(1)——编程流程

        在Winsock(Socket API)中,用户可以使用TCP/IP地址家族中统一的套接字地址结构解决TCP/IP寻址中可能出现的问题。该套接字地址结构定义如下:

struct sockaddr_in{

    short             sin_family;          //指定地址家族即地址格式

    unsigned          sin_port;           //端口号码

    structin_addr    sin_addr;         //IP地址

    char              sin_zero[8];        //留做备用,需要指定为0

};

        在这个结构中,成员sin_family指定使用该套接字地址的地址家族。在这里必须设置为AF_INET,表示程序所使用的地址家族是TCP/IP。

        该结构成员变量sin_addr表示32位的IP地址结构,其结构定义如下:

struct in_addr{

    union{

       struct{

           unsignedchar s_b1,s_b2,s_b3,s_b4; //用4个u_char字符描述IP地址

}S_un_b;

struct{

            unsigned short s_w1,s_w2;          //用2个u_short类型描述IP地址

}S_un_w;

unsigned long S_addr;                  //用1个u_long类型描述IP地址

}S_un;

};

        通常,用户在网络编程中使用1个u_long类型的字符进行描述IP地址。例如,使用IP地址结构in_addr进行描述IP地址为“192.168.1.1”,代码如下:

sockaddr_in addr;

addr.sin_addr.S_un.S_addr=inet_addr(“192.168.1.1”);

        在程序中首先定义sockaddr_in结构对象,然后为IP地址结构in_addr中的成员S_addr赋值。因为结构成员S_addr所描述的IP地址均为网络字节顺序,所以程序调用inet_addr()函数将字符串IP转化为网络字节顺序排列的IP地址。

 

        在Socket套接字编程中,传输数据的排列顺序以网络字节顺序和主机字节顺序为主。通常情况下,如果用户将数据通过网络发送时,需要将数据转换成以网络字节顺序排列,否则可能造成数据损坏。如果用户是将网络中收到的数据存储在本机上,那么需要将数据转换成以主机字节顺序排列。从数据存储角度来讲,网络字节顺序即将数据中最重要的字节首先存储,而主机字节顺序则将不重要的字节首先存储。

        注意:IP地址结构in_addr中的成员S_addr的值均是以网络字节顺序排列。

        在Winsock中提供了几个关于网络字节顺序与主机字节顺序之间转换的函数,函数定义如下:

u_short htons(u_short hostshort);            //将一个u_short类型的IP地址从主机字节顺序转换到网络字节顺序

u_long htonl(u_long hostlong);      //将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序

u_long ntohl(u_long netlong);           //将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序

u_short ntohs(u_short netshort);       //将一个u_short类型的IP地址从网络字节顺序转换到主机字节顺序

unsigned long inet_addr(const char FAR*r cp);       //将一个字符串IP转换到以网络字节顺序排列的IP地址

char FAR* inet_ntoa(struct in_addr in);                      //将一个以网络字节顺序排列的IP地址转换为一个字符串IP地址

1          初始化和释放套接字

        由于所有的Winsock函数都是从动态链接库WS2_32.DLL中导出,但默认情况下,编译器并没有于该库进行连接,所以必须手动添加。

#include "winsock2.h"

#pragma comment(lib, "ws2_32.lib")

        用户必须首先从动态库中调用函数WSAStartup()对该库进行初始化,之后才能从该库中继续正确调用其他的Winsock函数,否则,将出现错误。函数WSAStartup()的原型如下:

int WSAStartup(WORDwVersionRequested,LPWSADATA lpWSAData);

        该函数调用成功返回0。参数wVersionRequested表示套接字库的版本号。例如,当前套接字版本号为2.0,则将该参数设置为2.0,代码如下:

WORD wVersionRequested=MAKEWORD(2,0);

        参数lpWSAData指向结构体WSADATA的指针变量,表示获取到的套接字详细信息,该结构体定义如下;

Typedef struct WSAData{

    WORDwVersion;       //库文件建议应用程序使用的版本号

    WORDwHighVersion;   //库文件支持的最高版本

    charszDescription[WSADESCRIPTION_LEN+1]; //描述库文件的字符串

    charszSystemStatus[WSASYS_STATUS_LEN+1]; //系统状态字符串

    unsignedshort iMaxSockets;               //同时支持的最大套接字数

    unsignedshort iMaxUdpDg;                //已废弃

    charFAR* lpVendorInfo;                       //已废弃

}WSADATA,FAR*LPWSADATA;

        用户初始化套接字库,代码如下:

WSAData data;                                    //定义WSAData变量

WORD wVersionRequested=MAKEWORD(2,0);           //定义套接字库版本号

::WSAStartip(wVersionRequested,&data);        //初始化套接字库

        当程序退出时,用户还应该调用函数WSACleanup释放该套接字,代码如下:

::WSACleanup();

2          创建套接字句柄

        在Socket API中,创建套接字句柄的函数是socket(),该函数原型如下:

SOCKET socket(

    intaf,           //指定套接字所使用的地址格式

    inttype,         //套接字类型

    intprotocol,       //如果参数type已指定套接字类型为TCP或UDP,则该参数可以设置为0

);

        该函数执行成功,将返回新创建的套接字句柄,参数type的取值如下表:

套接字取值类型

含义

SOCK_STREAM

创建流式套接字(基于TCP协议)

SOCK_DGRAM

创建数据报套接字(基于UDP协议)

SOCK_RAM

创建原始套接字

例如,创建流式套接字,代码如下:

SCOKET s;                              //定义套接字句柄

s=::socket(AF_INET,SOCK_STREAM,0); //创建并返回套接字句柄

3          绑定地址信息

        对于服务器而言,套接字创建成功后,还应该将套接字与地址结构信息相关联,实现这一功能的函数是bind(),该函数原型如下:

int bind(

    SOCKETs,                           //套接字句柄

    conststruct sockaddr FAR* name,   //地址结构信息

    intnamelen                           //地址结构的大小

);

        该函数调用成功返回0。将套接字句柄绑定到本地地址的代码如下:

```

sockaddr_in addr;           //定义套接字地址结构变量

addr.sin_family = AF_INET;      //指定地址家族为TCP/IP

addr.sin_port = htons(4999);    //指定端口

addr.sin_addr.s_addr = INADDR_ANY; //表示服务器可以接收任何计算机发来的请求

retVal = bind(sServer,(LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));//绑定套接字到指定地址结构

        当服务器程序将套接字句柄绑定套接字地址成功时,则调用函数listen()实现监听端口的功能,该函数原型如下:

int listen(

    SOCKETs,                //实现监听功能的套接字

    intbacklog                //指定监听的最大连接数量

);

        该函数仅被用于流式套接字上。如果多个客户端同时向服务器发出连接请求,并且超过了最大监听数,则客户端返回错误代码,例如,程序在已创建的套接字s上进行监听,代码如下:

···

::listen(s,5);       //在套接字上进行监听,并将最大监听数指定为5

4          连接

        客户端程序连接服务器使用函数connect()实现,函数原型如下:

int connect(

    SOCKETs,                          //套接字句柄

    conststruct sockaddr FAR* name,   //将要连接的服务器地址信息结构指针

    intnamelen                        //地址信息结构体长度

);

        例如,客户端使用该函数连接地址为“127.0.0.1”,端口为80的服务器,代码如下:

sockaddr_in addr;            //定义套接字地址结构变量

addr.sin_family=AF_INET;     //指定地址家族为TCP/IP

addr.sin_port=htons(80);    //指定端口号

addr.sin_addr.s_addr=inet_addr(“127.0.0.1”);   //指定服务器地址

SOCKET s;

s=socket(AF_INET,SOCK_STREAM,0);                 //创建并返回套接字句柄

::connect(s,(sockaddr)&addr,sizeof(addr));         //连接服务器

        如果服务器接收到客户端的连接请求,则可以调用函数accept()接受该请求,函数原型如下:

SOCKET accept(

    SOCKET s,                       //创建套接字句柄

    structsockaddr FAR* addr,      //获取对方的地址信息

    intFAR* addrlen                //地址长度

);

        该函数调用成功后,返回一个新的套接字句柄,用于通讯双方数据的传输。

5          数据收发

        当用户使用Winsock编程时,都是调用函数send()和recv()进行数据的发送和接收,函数原型如下:

int send(SOCKET s,const char FAR* buf,intlen,int flags); //发送数据

int recv(SOCKET s,char FAR* buf,int len,intflags);     //接收数据

        两个函数的各个参数以及表示的意义均相同。参数buf是指向数据缓冲区的指针变量,参数flags通常设置为0。

        注意:如果服务器使用上面的函数进行数据收发,则参数s应该为监听函数返回的新套接字句柄。如果客户端使用以上函数进行数据收发,则参数s应该为客户端创建的套接字句柄

6          关闭套接字

        当套接字使用完毕或程序退出时,用户应该调用函数closesocket()关闭套接字句柄,函数原型如下:

int closesockeet(

    SOCKETs             //将关闭的套接字句柄

);

        参数s表示将要关闭的套接字句柄,例如,用户关闭前面创建的套接字句柄,代码如下:

::closesocket(s);

 

你可能感兴趣的:(C++,网络编程,winsock)