频道栏目
首页 > 程序开发 > 综合编程 > 其他综合 > 正文
Winsock网络编程快速入门
2011-11-18 16:25:21           
收藏   我要投稿

 

一、基本知识

 

1、Winsock,一种标准API,一种网络编程接口,用于两个或多个应用程序(或进程)之间通过网络进行数据通信。具有两个版本:

Winsock 1:

Windows CE平台支持。

头文件:WinSock.h

库:wsock32.lib

 

Winsock 2:

部分平台如Windows CE貌似不支持。通过前缀WSA可以区别于Winsock 1版本。个别函数如WSAStartup、WSACleanup、WSARecvEx、WSAGetLastError都属于Winsock 1.1规范的函数;

头文件:WinSock2.h

库:ws2_32.lib

 

mswsock.h用于编程扩展,使用时必须链接mswsock.dll。

 

2、网络协议:

IP (Internet Protocol)  网际协议,无连接协议;

TCP (Transmission Control Protocol)  传输控制协议;

UDP (User Datagram Protocol)  用户数据协议;

FTP (File Transfer Protocol)  文件传输协议;

HTTP (Hypertext Transfer Protocol)  超文本传输协议;

 

3、字节存储顺序:

big_endian:大端存储,存储顺序从高位到低位,地址指向最高有效字节。在网络中将IP和端口指定为多字节时使用大端存储,也称为网络字节顺序(network_byte)。貌似MAC OS使用的是大端存储方式;

 

little_endian:小端存储,存储顺序从低位到高位,地址指向最低有效字节。本地主机存储IP和端口制定的多字节时使用,也称为主机字节顺序(host_byte)。大多数系统都是小端存储;

 

用下面的方式可以检测是否为大端存储:

print?

bool IsBig_endian() 

    unsigned short test = 0x1122; 

    if ( *( (unsigned char*)&test ) == 0x11 ) 

    { 

        return true; 

    }  

    else 

    { 

        return false; 

    } 

 

此外有很多函数可以用来进行 主机字节和网络字节之间的转换,如:

u_long htonl( u_long hostlong );

int WSAHtonl( SOCKET s, u_long hostlong, u_long FAR *lpnetlong );

而有时网络IP是点分法表示的,如:192.168.0.1,使用函数unsigned long inet_addr( const char FAR *cp ); 可以将点分法的IP字符串作为一个网络字节顺序的32位u_long返回。

 

 

二、快速了解

 

1、Winsock初始化:

首先确保包含对应版本的头文件,然后保证链接对应的库文件(可以在代码中使用#pragma comment(lib, "WS2_32"),或在编译器项目属性中链接器->输入->附加依赖项中添加ws2_32.lib);

 通过调用WSAStartup函数来实现加载Winsock库:

print?

int 

WSAAPI 

WSAStartup( 

    IN WORD wVersionRequested, 

    OUT LPWSADATA lpWSAData 

    ); 

其中参数wVersionRequested用来指定加载Winsock库的版本,高位字节为次版本,低位字节为主版本,使用宏MAKEWORD(x,y)来生成一个WORD;

参数lpWSAData是指向WASDATA结构指针,加载的版本库信息将会填充这个结构,详细内容自查。

在使用Winsock后需要释放资源,并取消应用程序挂起的Winsock操作。使用int WASCleanup();

 

 

2、错误处理:

如果已经加载了Winsock库,则调用Winsock函数出错后,通常会返回SOCKET_ERROR,而通过使用函数int WSAGetLastError() 可以获得具体信息值,例如:

print?

if ( SOCKET_ERROR == WSACleanup() ) 

    cout << "WSACleanup error " << WSAGetLastError() << endl; 

    return 0; 

根据获取错误信息值,可以知道错误原因,并进行相应的处理。

 

 

3、寻址:

想要进行通信就需要知道彼此的地址,一般来说这个地址由IP和端口号来决定。在Winsock中使用SOCKADDR_IN结构来指定地址信息:

print?

struct sockaddr_in { 

        short   sin_family; 

        u_short sin_port; 

        struct  in_addr sin_addr; 

        char    sin_zero[8]; 

}; 

sin_family字段通常设置为AF_INET,表示Winsock此时正在使用IP地址族;

sin_port用于标示TCP或UDP通信端口,部分端口是为一些服务保留的,如FTP和HTTP使用要注意;

sin_adr字段把地址(例如是IPv4地址)作为一个4字节的量来存储起来,它是u_long类型,且是网络字节顺序的。可以使用inet_addr来处理点分法表示的IP地址;

sin_zero只充当填充项,以使SOCKADDR_IN结构和SOCKADDR结构长度一样。

以下简单的使用SOCKADDR_IN来指定地址:

print?

//创建一个地址 

int serverPort      = 5150; 

 

char FAR serverIP[] = "192.168.1.102"; //本机ip,不知道就ipconfig 

 

SOCKADDR_IN serverAddr; 

serverAddr.sin_family   = AF_INET; 

serverAddr.sin_port = htons( serverPort ); 

serverAddr.sin_addr.s_addr  = inet_addr( serverIP ); 

int serverAddr_size = static_cast<int>( sizeof(serverAddr) ); 

有时作为一个连接通信的服务端来说,在设置监听socket的地址结构时sin_addr.s_addr的值可以是htonl( INADDR_ANY ),INADDR_ANY允许将socket绑定到系统中所有可用的接口,以便传到任意接口的连接(端口必须正确)都可以被监听socket接受。

 

 

4、socket套接字:

套接字是通信时为传输提供的句柄,Winsock的操作都是基于套接字实现的。创建一个套接字有socket和WSASocket方法:

print?

SOCKET 

WSAAPI 

socket( 

       IN int af,       //协议的地址族,使用IPv4来描述Winsock,设置为AF_INET 

       IN int type,     //套接字类型,TCP/IP设置为SOCK_STREAM,UDP/IP设置为SOCK_DGRAM 

       IN int protocol      //用于给定地址族和类型具有多重入口的传送限定,TCP设置为IPPROTO_TCP,UDP设置为IPPROTO_UDP 

       ); 

如果创建成功,函数会返回一个有效的SOCKET,否则会返回INVALID_SOCKET,可以用WSAGetLastError()函数获得错误信息。

 

 

5、连接通信实现过程:

连结通信是基于TCP/IP实现的,进行数据传输前,通信双方要进行连接。

 

服务端:

初始化Winsock后,创建一个监听socket和一个要接受连接的地址结构;

使用bind将监听socket与地址结构进行关联;

print?

int 

WSAAPI 

bind( 

     IN SOCKET s,               //一个用于监听的socket 

     IN const struct sockaddr FAR * name,   //指向进行绑定的地址结构 

     IN int namelen             //进行绑定的地址结构的大小 

     ); 

使用listen将bind成功的监听socket状态设置为监听状态;

print?

int 

WSAAPI 

listen( 

       IN SOCKET s,     //一个用于监听的socket,已经进行bind 

       IN int backlog       //允许挂起连接的队列的最大长度,超过这个长度后,再有连接将会失败 

       ); 

 

使用accept接受通过监听socket获取的连接,成功后将返回的新的连接socket进行保存以便数据传输;

print?

SOCKET 

WSAAPI 

accept( 

       IN SOCKET s,         //处于监听模式的socket 

       OUT struct sockaddr FAR * addr,  //指向一个地址结构,用来接受连接后获得对方地址信息 

       IN OUT int FAR * addrlen     //指向一个整数,表示参数2指向地址结构的大小 

       ); 

 

 

客户端:

初始化Winsock后,创建一个监听socket和一个要连接的服务器地址结构;

使用connect将socket和服务器地址结构进行初始化连接,成功后将使用socket进行数据传输;

print?

int 

WSAAPI 

connect( 

        IN SOCKET s,            //要建立连接的socket 

        IN const struct sockaddr FAR * name,    //指向保存要建立连接信息的地址结构 

        IN int namelen          //参数2指向地址结构的大小 

        ); 

 

 

连接成功后,使用send、recv来进行数据传输;

print?

int 

WSAAPI 

send( 

     IN SOCKET s,       //进行连接的socket 

     IN const char FAR * buf,   //指向发送数据的缓冲区 

     IN int len,        //发送数据的字符数 

     IN int flags       //一个标志位,可以是0、MSG_DONTROUTE、MSG_OOB还可以是他们的或运算结果 

     );         //返回已经发送的数据长度 

 

 

int 

WSAAPI 

recv( 

     IN SOCKET s,       //进行连接的socket 

     OUT char FAR * buf,    //指向接受数据的缓冲区 

     IN int len,        //准备接受数据字节数或缓冲区的长度 

     IN int flags       //可以是0、MSG_PEEK、MSG_OOB还可以是他们的或运算结果 

     );         //返回已接受的数据长度 

 

连接结束后,使用shutdown和closesocket来断开连接和释放资源;

print?

int 

WSAAPI 

shutdown( 

         IN SOCKET s,   //要关闭的socket 

         IN int how //关闭标志:SD_RECEIVE、SD_SEND、SD_BOTH 

         ); 

 

 

6、无连接通信实现过程:

无连接通信是基于UDP/IP实现的,UDP不能确保可靠的数据传输,但能将数据发送到多个目标,或者接受多个源的数据。

初始化Winsock后,可以创建socket和用以进行通信任意地址结构;

使用recvfrom通过socket和通信的地址结构接受数据;

使用sendto通过socket和通信的地址结构发送数据;

print?

int 

WSAAPI 

recvfrom( 

         IN SOCKET s, 

         OUT char FAR * buf, 

         IN int len, 

         IN int flags, 

         OUT struct sockaddr FAR * from, 

         IN OUT int FAR * fromlen 

         ); 

 

int 

WSAAPI 

sendto( 

       IN SOCKET s, 

       IN const char FAR * buf, 

       IN int len, 

       IN int flags, 

       IN const struct sockaddr FAR * to, 

       IN int tolen 

       ); 

 

同样通信结束后,使用shutdown和closesocket来断开连接和释放资源

 

 

上述使用函数都有多个版本,而且相关的一些标志位参数可以提供设置选项,另外,返回的错误处理等也有待于详细研究;

 

 

7、select函数:

 

select()用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。

print?

int 

WSAAPI 

select( 

       IN int nfds,         //指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,在Windows中值无所谓。  

       IN OUT fd_set FAR * readfds, //可选指针,指向一组等待可读性检查的套接字。  

       IN OUT fd_set FAR * writefds,    //可选指针,指向一组等待可写性检查的套接字。 

       IN OUT fd_set FAR *exceptfds,    //可选指针,指向一组等待错误检查的套接字。 

       IN const struct timeval FAR * timeout    //select()最多等待时间,对阻塞操作则为NULL。 

       ); 

 

//用fd_set结构来表示一组等待检查的套接口。在调用返回时,这个结构存有满足一定条件的套接口组的子集: 

 

typedef struct fd_set { 

    u_int fd_count;               //其中set元素数目 

    SOCKET  fd_array[FD_SETSIZE]; //保存set元素的数组 

} fd_set; 

 

fd_set set; 

FD_ZERO(&set);      /*将set清零使集合中不含任何fd*/    

FD_SET(fd, &set);       /*将fd加入set集合*/    

FD_CLR(fd, &set);       /*将fd从set集合中清除*/    

FD_ISSET(fd, &set);     /*测试fd是否在set集合中*/  

 

select的返回值:

select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;

如果超时则返回0;否则的话,返回SOCKET_ERROR错误,通过WSAGetLastError获取相应错误代码。

当返回位0时,所有描述符集清0;

当返回为-1时,不修改任何描述符集;

当返回为非0时,在3个描述符集里,依旧是1的位就是准备好的描述符。这也就是为什么,每次用select后都要用FD_ISSET的原因。

 

 

三、简单实践

 

利用上述内容,实现一个基于TCP/IP的连接通信。

 

服务端:

 

//******************************************************************

print?

#include "stdafx.h" 

 

#include <iostream> 

 

#include <WinSock2.h> 

 

#pragma comment(lib, "WS2_32") 

 

 

using namespace std; 

 

 

 

# define REQUEST_BACKLOG 5 

 

 

//****************************** 

//好吧不用写这些纠结的函数,就是看着清晰些 

//初始化Winsock 

bool InitWSA( const WORD &wVersion, WSADATA *wsadata ) 

    int Ret = 0; 

    if ( ( Ret = WSAStartup( wVersion,wsadata ) ) != 0 ) 

    { 

        cout << "WSAStartup failed, error "  << Ret << endl; 

 

        return false; 

    } 

 

    return true; 

 

 

//结束Winsock 

void cleanWSA() 

    if ( WSACleanup() == SOCKET_ERROR ) 

    { 

        cout << "WSACleanup failed, error "  << WSAGetLastError() << endl; 

    } 

 

 

 

//IPv4寻址,通过ip填充SOCKADDR_IN结构 

void InitSockAddrByIP( SOCKADDR_IN *pSockAddr, const char FAR *strIP, const INT &nPortID ) 

    pSockAddr->sin_family            = AF_INET; 

 

    pSockAddr->sin_port              = htons( nPortID ); 

 

    if ( 0 != strlen(strIP) ) 

    { 

        pSockAddr->sin_addr.s_addr       = inet_addr( strIP ); 

    } 

    else 

    { 

        pSockAddr->sin_addr.s_addr       = htonl( INADDR_ANY ); 

    } 

 

 

//bind 

bool bindAddr( const SOCKADDR_IN *pSockAddr, SOCKET *pSocket ) 

    int bindResult = bind( *pSocket, (sockaddr *)(pSockAddr), sizeof(*pSockAddr)); 

 

    if ( SOCKET_ERROR == bindResult ) 

    { 

        cout << "bind error :" << WSAGetLastError() << endl; 

        return false; 

    }  

    return true; 

 

 

//listen 

bool setListener( SOCKET *pSocket, int backlog ) 

    int nResult = listen( *pSocket, backlog ); 

 

    if ( SOCKET_ERROR == nResult ) 

    { 

        cout << "listen error :" << WSAGetLastError() << endl; 

        return false; 

    }  

    return true; 

 

 

 

 

 

//****************************** 

 

 

//程序入口 

int _tmain(int argc, _TCHAR* argv[]) 

 

    //初始化Winsock 

    WSADATA wsadata; 

    if ( !InitWSA( MAKEWORD(2,2), &wsadata ) ) 

    { 

        return 0; 

    } 

 

 

    //指定连接ip地址和服务器口 

 

    SOCKADDR_IN InternetAddr; 

    //char FAR strIP[]  = "198.0.0.0"; 

    char FAR strIP[]    = ""; 

    INT nPortID         = 5150; 

    InitSockAddrByIP( &InternetAddr, strIP, nPortID ); 

 

 

 

    //创建listener_socket 

    SOCKET listener_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); 

    if ( INVALID_SOCKET == listener_socket ) 

    { 

        cout << "listener_socket creat failed " << endl; 

        return 0; 

    } 

 

 

    //bind 

    if ( !bindAddr( &InternetAddr, &listener_socket ) ) 

    { 

        return 0; 

    } 

 

 

    //监听 

    if ( !setListener( &listener_socket, REQUEST_BACKLOG ) ) 

    { 

        return 0; 

    } 

 

    cout << "server started~~~ " << endl; 

 

 

 

    //创建socket保存结构 

    fd_set fdSocket; 

    FD_ZERO( &fdSocket ); 

    FD_SET( listener_socket, &fdSocket ); 

 

 

    //查找可读的socket 

    while( true ) 

    { 

        fd_set fdSocket_temp; 

        fdSocket_temp = fdSocket; 

         

        fd_set fdRead; 

        fdRead = fdSocket; 

 

        fd_set fdExceptds; 

        fdExceptds = fdSocket; 

 

        int nResult_select = select( 0, &fdRead, NULL, &fdExceptds, NULL ); 

 

        if ( 0 < nResult_select ) 

        { 

            unsigned int socket_count = fdSocket_temp.fd_count; 

            for ( unsigned int i=0; i< socket_count; i++ ) 

            { 

 

                //可读的 

 

                if ( FD_ISSET( fdSocket_temp.fd_array[i], &fdRead ) ) 

                { 

                    //找到所有可读连接 

                    if ( fdSocket_temp.fd_array[i] == listener_socket ) 

                    { 

                        if ( fdSocket.fd_count < FD_SETSIZE ) 

                        { 

 

                         

                            //接受新的连接 

                            SOCKADDR_IN ClientAddr; 

                            int addrlen = static_cast<int>(sizeof(ClientAddr));   //一定要赋值 

 

                            SOCKET newClient_socket = accept( listener_socket, (sockaddr *)&ClientAddr, &addrlen ); 

                            if ( INVALID_SOCKET == newClient_socket ) 

                            { 

                                cout << " accept error " << WSAGetLastError() << endl; 

                            } 

                            else 

                            { 

                                FD_SET( newClient_socket, &fdSocket ); 

                                cout << "find new connect: " << inet_ntoa( ClientAddr.sin_addr ) << endl; 

                            } 

                        } 

 

                        else 

                        { 

                            cout<<"too much connections"<<endl; 

                            continue; 

                        } 

 

                    }  

                    else 

                    { 

 

                        //接收数据 

 

                        char recvbuff[1024]; 

                        int  ret = 0; 

 

                        ret = recv( fdSocket_temp.fd_array[i], recvbuff, static_cast<int>( strlen(recvbuff) ), 0 ); 

                        if ( 0 < ret ) 

                        { 

                            recvbuff[ret] = '\0'; 

                            cout << "recv : " << recvbuff << endl; 

                            //回复 

                            char backbuf[1024] = "receive info!"; 

                            send( fdSocket_temp.fd_array[i], backbuf, static_cast<int>( strlen(backbuf) ), 0 ); 

             

                        } 

                        else 

                        { 

                            //该连接断开 

                            closesocket( fdSocket_temp.fd_array[i] ); 

                            FD_CLR( fdSocket_temp.fd_array[i], &fdSocket ); 

                        } 

 

                    } 

                     

                } 

                else if( fdSocket_temp.fd_array[i] != listener_socket ) 

                { 

                    //该连接断开 

                    closesocket( fdSocket_temp.fd_array[i] ); 

                    FD_CLR( fdSocket_temp.fd_array[i], &fdSocket ); 

                } 

 

 

                if ( FD_ISSET( fdSocket_temp.fd_array[i], &fdExceptds )  &&  (fdSocket_temp.fd_array[i] != listener_socket) ) 

                { 

                    //该连接断开 

                    closesocket( fdSocket_temp.fd_array[i] ); 

                    FD_CLR( fdSocket_temp.fd_array[i], &fdSocket ); 

                } 

     

            }//end for 

        } 

 

 

        else if( SOCKET_ERROR == nResult_select ) 

        { 

            cout << "select error : " << WSAGetLastError() << endl; 

            return 0; 

        } 

 

 

        Sleep( 50 );    //不要挑战你的机器 

 

    }//end while 

 

 

    closesocket( listener_socket ); 

    cleanWSA(); 

 

    return 0; 

 

 

 

客户端:

 

//******************************************************************

print?

#include "stdafx.h" 

 

#include <WinSock2.h> 

 

#include <iostream> 

 

#pragma comment(lib, "WS2_32") 

 

using namespace std; 

 

 

 

 

int _tmain(int argc, _TCHAR* argv[]) 

    int result = 0; 

 

    //初始化winsock 

 

    WSADATA wsadata; 

    result = WSAStartup( MAKEWORD(2,2), &wsadata ); 

 

    if ( 0 != result ) 

    { 

        cout << "WSAStartup error " << result << endl; 

        return 0; 

    } 

 

    //创建一个地址 

    int serverPort      = 5150; 

 

    char FAR serverIP[] = "192.168.1.102"; //本机ip,不知道就ipconfig 

 

    SOCKADDR_IN serverAddr; 

    serverAddr.sin_family       = AF_INET; 

    serverAddr.sin_port         = htons( serverPort ); 

    serverAddr.sin_addr.s_addr  = inet_addr( serverIP ); 

    int serverAddr_size         = static_cast<int>( sizeof(serverAddr) ); 

 

    //创建一个socket 

    SOCKET socket_toServer; 

    socket_toServer = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); 

    if ( INVALID_SOCKET == socket_toServer ) 

    { 

        cout << "socket_toServer creat failed " << WSAGetLastError() << endl; 

        return 0; 

    } 

 

     

    //连接 

    result = connect( socket_toServer, (sockaddr *)&serverAddr, serverAddr_size ); 

    if ( SOCKET_ERROR == result ) 

    { 

        cout << "connect error :" << WSAGetLastError() << endl; 

        return 0; 

    } 

 

     

    char sendbuff[2048]; 

    char recvbuff[2048]; 

 

    while( true ) 

    { 

        cout << "input send info:" << endl; 

        cin >> sendbuff; 

 

        int ret = send( socket_toServer, sendbuff, static_cast<int>( strlen(sendbuff) ), 0 ); 

        if ( SOCKET_ERROR == ret ) 

        { 

            cout << "send error " << WSAGetLastError() <<  endl; 

            break; 

        } 

 

        //处理接受的消息,由于之前没有accept和listen,这里使用recvfrom来接受 

        int nRecv = 0; 

        nRecv = recvfrom( socket_toServer, recvbuff, static_cast<int>( strlen(recvbuff) ), 0, (sockaddr *)&serverAddr, &serverAddr_size ); 

        if ( 0 < nRecv ) 

        { 

            recvbuff[nRecv] = '\0'; 

            cout << "receive : " << recvbuff << endl; 

            cout << "from : " << inet_ntoa( serverAddr.sin_addr ) << endl; 

            cout << " " << endl; 

        } 

    } 

 

 

    //清除各种数据和链接 

 

    closesocket( socket_toServer ); 

 

    if ( SOCKET_ERROR == WSACleanup() ) 

    { 

        cout << "WSACleanup error " << WSAGetLastError() << endl; 

        return 0; 

    } 

     

 

    return 0; 

 

如题,上述内容只是帮助快速入门,要掌握Winsock还要经过大量的学习和实践。

 

摘自 zhao_yin的记事本

点击复制链接 与好友分享!回本站首页
相关TAG标签 网络编程
上一篇:Makefile 解析
下一篇:程序员究竟该如何提高效率
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站