• 【转】Windows Socket TCP/UDP


    Windows Socket编程,发现这篇文章不错,就拿过来分享下,转载地址:http://www.cnblogs.com/fantasy-blog/archive/2013/04/21/3033935.html

     

    SOCKET网络编程

    (WINDOWS SOCKET)

    1.前言

    网上看了很多Socket的资料,将理解的知识总结下,详细介绍下VC下windows sockets编程,并结合服务器和客户端的两个实例(TCP/UDP)讲解下。

    2.SOCKET相关原理

    在网络编程中最常用的方案便是Client/Server (客户机/服务器)模型。在这种方案中客户应用程序向服务器程序请求服务。一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态(简单的说就是死循环),直到一个客户向这个服务的地址提出了连接请求。在这个时刻,服务程序被"惊醒"并且为客户提供服务-对客户的请求作出适当的反应。

    在VC中进行WINSOCK的API编程开发的时候,需要在项目中使用下面的三个文件,否则会出现编译错误。
      1.WINSOCK.H: 这是WINSOCK API的头文件,需要包含在项目中。
      2.WSOCK32.LIB: WINSOCK API连接库文件。在使用中,一定要把它作为项目的非缺省的连接库包含到项目文件中去。 
      3.WINSOCK.DLL: WINSOCK的动态连接库,位于WINDOWS的安装目录下。

    3.SOCKET API

    下面讲解的都是在实例中使用的接口,了解相关定义后我们结合实例讲解。

    1.WSAStartup()

    此函数在应用程序中初始化Windows Sockets DLL ,只有此函数调用成功后,应用程序才可以再调用其他Windows Sockets DLL中的API函数。

    int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
    其返回值为整型,调用方式为PASCAL(即标准类型,PASCAL等于__stdcall),参数有两个,第一个参数为WORD类型,指明了Socket的版本号,第二个参数为WSADATA类型的指针。
    若返回值为0,则初始化成功,若不为0则失败。

    在程式中调用该函数的形式如下:WSAStartup(MAKEWORD(1,1), &WSAData),其中MAKEWORD(1,1)表示我们用的是WinSocket1.1版本,WSAata用来存储系统传回的关于WinSocket的资料。

    (1) WORD类型、MAKEWORD、LOBYTE和HIBYTE宏

    WORD类型是一个16位的无符号整型,在WTYPES.H中被定义为:
    typedef unsigned short WORD;
    其目的是提供两个字节的存储,在Socket中这两个字节可以表示主版本号和副版本号。使用MAKEWORD宏可以给一个WORD类型赋值。例如要表示主版本号2,副版本号0,可以使用以下代码:
    WORD wVersionRequested;  

    wVersionRequested = MAKEWORD( 2, 0 );
    注意低位内存存储主版本号2,高位内存存储副版本号0,其值为0x0002。使用宏LOBYTE可以读取WORD的低位字节,HIBYTE可以读取高位字节。在后面的实例中会应用。

    (2)WSADATA类型和LPWSADATA类型

        WSADATA类型是一个结构,描述了Socket库的一些相关信息,其结构定义如下:
    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;
    typedef WSADATA FAR *LPWSADATA;
        值得注意的就是wVersion字段,存储了Socket的版本类型。LPWSADATA是WSADATA的指针类型。它们通过Socket的初始化函数WSAStartup读取出来。

    2.WSACleanup()

    这是Socket环境的退出函数。返回值为0表示成功,SOCKET_ERROR表示失败。

    3.socket()

    SOCKET是socket套接字类型,在WINSOCK2.H中有如下定义:
    typedef unsigned int    u_int;
    typedef u_int           SOCKET;
    可知套接字实际上就是一个无符号整型,它将被Socket环境管理和使用。

    初始化WinSock的动态连接库后,需要在服务器端建立一个监听的Socket,为此可以调用Socket()函数用来建立这个监听的Socket,并定义此Socket所使用的通信协议。此函数调用成功返回Socket对象,失败则返回INVALID_SOCKET(调用WSAGetLastError()可得知原因,所有WinSocket 的API函数都可以使用这个函数来获取失败的原因。

      SOCKET PASCAL FAR socket( int af, int type, int protocol )

    参数: 

    af: 目前只提供 PF_INET(AF_INET);
    type: Socket 的类型 (SOCK_STREAM、SOCK_DGRAM);
    protocol: 通讯协定(如果使用者不指定则设为0);

    如果要建立的是遵从TCP/IP协议的socket,第二个参数type应为SOCK_STREAM,为UDP(数据报)的socket,应为SOCK_DGRAM。

    l SOCK_STREAM:这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

    l SOCK_DGRAM:这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

    套接字将被创建、设置、用来发送和接收数据,最后会被关闭。

    4.bind()

    接下来要为服务器端定义的这个监听的Socket指定一个地址及端口(Port),这样客户端才知道待会要连接哪一个地址的哪个端口,为此我们要调用bind()函数,该函数调用成功返回0,否则返回SOCKET_ERROR。

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

    参 数: 

    s:Socket对象名;
    name:Socket的地址值,这个地址必须是执行这个程式所在机器的IP地址;
    namelen:name的长度; 

    如果使用者不在意地址或端口的值,那么可以设定地址为INADDR_ANY,及Port为0,Windows Sockets 会自动将其设定适当之地址及Port (1024 到 5000之间的值)。此后可以调用getsockname()函数来获知其被设定的值。

    5.sockaddr_in、in_addr类型,inet_addr、inet_ntoa函数,sockaddr

    sockaddr_in定义了socket发送和接收数据包的地址,定义:
    struct sockaddr_in

    {
            short     sin_family;
            u_short  sin_port;
            struct in_addr  sin_addr;
            char     sin_zero[8];
    };
    其中in_addr的定义如下:
    struct in_addr 

    {
             union {
                     struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                     struct { u_short s_w1,s_w2; } S_un_w;
                     u_long S_addr;

    } S_un;
    首先阐述in_addr的含义,很显然它是一个存储ip地址的联合体(union),有三种表达方式:
    第一种用四个字节来表示IP地址的四个数字;
    第二种用两个双字节来表示IP地址;
    第三种用一个长整型来表示IP地址。
    给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如
    addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
    其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。
    sockaddr_in的含义比in_addr的含义要广泛,其各个字段的含义和取值如下:
    第一个字段short   sin_family,代表网络地址族,如前所述,只能取值AF_INET;
    第二个字段u_short sin_port,代表IP地址端口,由程序员指定;
    第三个字段struct in_addr sin_addr,代表IP地址;
    第四个字段char    sin_zero[8],是为了保证sockaddr_in与SOCKADDR类型的长度相等而填充进来的字段。

    sockaddr类型是用来表示Socket地址的类型,同上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。Sockaddr的定义如下:
    struct sockaddr {
     u_short    sa_family;
     char       sa_data[14];
    };  
    可知sockaddr有16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockaddr的。事实上也往往使用这种方法。

    6.listen()

    当服务器端的Socket对象绑定完成之后,服务器端必须建立一个监听的队列来接收客户端的连接请求。listen()函数使服务器端的Socket 进入监听状态,并设定可以建立的最大连接数(目前最大值限制为 5, 最小值为1)。该函数调用成功返回0,否则返回SOCKET_ERROR。
      int PASCAL FAR listen( SOCKET s, int backlog );

    参 数: 

    s:需要建立监听的Socket;
    backlog:最大连接个数;
       服务器端的Socket调用完listen()后,如果此时客户端调用connect()函数提出连接申请的话,Server 端必须再调用accept() 函数,这样服务器端和客户端才算正式完成通信程序的连接动作。为了知道什么时候客户端提出连接要求,从而服务器端的Socket在恰当的时候调用 accept()函数完成连接的建立,我们就要使用WSAAsyncSelect()函数,让系统主动来通知我们有客户端提出连接请求了。该函数调用成功 返回0,否则返回SOCKET_ERROR。

      int PASCAL FAR WSAAsyncSelect( SOCKET s, HWND hWnd,unsigned int wMsg, long lEvent );

    参数: 

    s:Socket 对象;
    hWnd :接收消息的窗口句柄;
    wMsg:传给窗口的消息;
    lEvent:被注册的网络事件,也即是应用程序向窗口发送消息的网路事件,该值为下列值FD_READ、FD_WRITE、FD_OOB、 FD_ACCEPT、FD_CONNECT、FD_CLOSE的组合,各个值的具体含意为FD_READ:希望在套接字S收到数据时收到消 息;FD_WRITE:希望在套接字S上可以发送数据时收到消息;FD_ACCEPT:希望在套接字S上收到连接请求时收到消息;FD_CONNECT: 希望在套接字S上连接成功时收到消息;FD_CLOSE:希望在套接字S上连接关闭时收到消息;FD_OOB:希望在套接字S上收到带外数据时收到消息。 具体应用时,wMsg应是在应用程序中定义的消息名称,而消息结构中的lParam则为以上各种网络事件名称。所以,可以在窗口处理自定义消息函数中使用 以下结构来响应Socket的不同事件:  

    switch(lParam) 
    {
     case FD_READ:
       …  
       break;
     case FD_WRITE:
       …
       break;
     …
    }

     

    7.accept()

    当Client提出连接请求时,Server 端hwnd视窗会收到Winsock Stack送来我们自定义的一个消息,这时,我们可以分析lParam,然后调用相关的函数来处理此事件。为了使服务器端接受客户端的连接请求,就要使用 accept() 函数,该函数新建一Socket与客户端的Socket相通,原先监听之Socket继续进入监听状态,等待他人的连接要求。该函数调用成功返回一个新产 生的Socket对象,否则返回INVALID_SOCKET。

      SOCKET PASCAL FAR accept( SCOKET s, struct sockaddr FAR *addr,int FAR *addrlen );
    参数:

    s:Socket的识别码;

    addr:存放来连接的客户端的地址;
    addrlen:addr的长度

    8.connect()

    Connect用于客户端想服务器发起连接。

    格 式: int PASCAL FAR connect( SOCKET s,const struct sockaddr FAR *name,int namelen );
    参 数: s Socket 的识别码
    name 此 Socket 想要连接的对方位址
    namelen name的长度
    传回值:成功 - 0
    失败 - SOCKET_ERROR (呼叫WSAGetLastError()可得知原因)
    说明: 此函式用来向对方要求建立连接。若是指定的对方位址为 0 的话,会传回错误值。当连接建立完成後,使用者即可利用此一 Socket 来做传送或接收资料之用了。

    9.sendto(),send()函数

    在Socket中有两套发送和接收函数,一是sendto和recvfrom;二是send和recv。前一套在函数参数中要指明地址(UDP协议);而后一套需要先将套接字和一个地址绑定,然后直接发送和接收,不需绑定地址。
    sendto的定义如下:
    int PASCAL FAR sendto (SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen);
    第一个参数就是套接字;
    第二个参数是要传送的数据指针;
    第三个参数是要传送的数据长度(字节数);
    第四个参数是传送方式的标识,如果不需要特殊要求则可以设置为0,其它值请参考MSDN;
    第五个参数是目标地址,注意这里使用的是sockaddr的指针;
    第六个参数是地址的长度;
    返回值为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
    send函数有四个参数与sendto函数的前四个参数是一样的。

     

    10.recvfrom(),recv()函数

    int WSAAPI recvfrom(IN SOCKET s,char FAR * buf,int len,int flags,sockaddr FAR * from,int  * fromlen );
    第一个参数就是套接字;
    第二个参数是要接收消息的char数组的指针;
    第三个参数是要接收的数据长度(字节数);
    第四个参数是接收方式的标识,如果不需要特殊要求则可以设置为0;
    第五个参数是接收地址的结构指针,存放接收到对方的的地址信息;
    第六个参数是地址的长度的指针(&);
    返回值为整型,如果成功,则返回接收的字节数,失败则返回SOCKET_ERROR。
    recv函数有四个参数与recvfrom函数的前四个参数是一样的。

    11.sleep()函数

    线程挂起函数,表示线程挂起一段时间。Sleep(1000)表示挂起一秒。定义于WINBASE.H头文件中。WINBASE.H又被包含于WINDOWS.H中,然后WINDOWS.H被WINSOCK2.H包含。

    12.recvfrom(),recv()函数

    int WSAAPI recvfrom(IN SOCKET s,char FAR * buf,int len,int flags,sockaddr FAR * from,int  * fromlen );
    第一个参数就是套接字;
    第二个参数是要接收消息的char数组的指针;
    第三个参数是要接收的数据长度(字节数);
    第四个参数是接收方式的标识,如果不需要特殊要求则可以设置为0;
    第五个参数是接收地址的结构指针,存放接收到对方的的地址信息;
    第六个参数是地址的长度的指针(&);
    返回值为整型,如果成功,则返回接收的字节数,失败则返回SOCKET_ERROR。
    recv函数有四个参数与recvfrom函数的前四个参数是一样的。

    13.closesocket()

    关闭套接字,其参数为SOCKET类型。成功返回0,失败返回SOCKET_ERROR。


    4.TCP/UDP Socket流程图

    1.UDP流程图

     

    2.TCP流程图

     

     

    以下实现在我本地测试通过

    1. UDP实现

    (1) UDP Server

     

     

    #include <stdio.h>
    /* Windows socket头文件 */
    #include <Winsock2.h>
    /* 网络API的动态链接库 */
    #pragma comment(lib, "ws2_32.lib")
    
    
    void main()
    {
        SOCKET uiFdSocket;
        WSADATA wsaData;
        char szbuffer[1024] = "\0";
        struct sockaddr_in stServerAddr;
        struct sockaddr_in stClientAddr;
        int iAddrlen = sizeof(sockaddr_in);
    
    
        /* 调用Windows Sockets DLL,成功后才能使用socket系列函数 */
        if (0 != WSAStartup(MAKEWORD(2,1), &wsaData))
        {
            printf("Winsock init failed!\r\n");
            WSACleanup();
            return;
        }
        memset(&stServerAddr, 0, sizeof(stServerAddr));
        memset(&stClientAddr, 0, sizeof(stClientAddr));
    
    
        /* 服务器地址 */
        stServerAddr.sin_family = AF_INET;
    
    
        /* 监听端口 */
        stServerAddr.sin_port = htons(6000);
        stServerAddr.sin_addr.s_addr = INADDR_ANY;
    
    
        /* 服务器端创建socket, 报文模式(UDP)*/
        uiFdSocket = socket(AF_INET, SOCK_DGRAM, 0);
    
    
        /* 绑定端口号 */
        bind(uiFdSocket, (struct sockaddr*)&stServerAddr, sizeof(sockaddr_in));
    
    
        while(true)
        {
            printf("waiting client send msg now...\r\n");
    
    
            if (SOCKET_ERROR != recvfrom(uiFdSocket,szbuffer,sizeof(szbuffer),0,(struct  sockaddr*)&stClientAddr, &iAddrlen))
            {
                printf("Received datagram from %s--%s\n", inet_ntoa(stClientAddr.sin_addr), 	 szbuffer);
                sendto(uiFdSocket, szbuffer, sizeof(szbuffer), 0, (struct sockaddr*)&stClientAddr, 	 iAddrlen);
            }
    
    
        }
        closesocket(uiFdSocket);
    }
    
    
    

    (2) UDP Client

     

     

    #include <stdio.h>
    /* Windows socket头文件 */
    #include <Winsock2.h>
    /* 网络API的动态链接库 */
    #pragma comment(lib, "ws2_32.lib")
    
    
    void main()
    {
        SOCKET uiFdsocket;
        WSADATA wsaData;
        struct sockaddr_in stServerAddr;
        int iAddrlen = sizeof(sockaddr_in);
        char szbuffer[1024] = "\0";
        if (0 != WSAStartup(MAKEWORD(2,1),&wsaData))
        {
            printf("Winsock init faied!\r\n");
            WSACleanup();
            return;
        }
    
    
    
    
        /* 服务器监听的端口和地址 */
    
    
        memset(&stServerAddr, 0, sizeof(stServerAddr));
        stServerAddr.sin_family = AF_INET;
        stServerAddr.sin_port = htons(6000);
        stServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    
        printf("Now connecting the server...\r\n");
    
    
        uiFdsocket = socket(AF_INET, SOCK_DGRAM, 0);
    
    
        while(true)
        {
            printf("Input message here...\r\n");
    //        scanf("%s", szbuffer);
            gets(szbuffer);
            if(strcmp(szbuffer, "bye") == 0)
            {
                printf("exit\r\n");
                Sleep(100);
                closesocket(uiFdsocket);
                break;
            }
    
    
            if(SOCKET_ERROR != sendto(uiFdsocket, szbuffer,sizeof(szbuffer), 0, (struct  sockaddr*)&stServerAddr, iAddrlen))
            {
                Sleep(100);
                if (SOCKET_ERROR != recvfrom(uiFdsocket, szbuffer, sizeof(szbuffer), 0, (struct sockaddr*)&stServerAddr, &iAddrlen))
                {
                    printf("recive from server:%s\r\n", szbuffer);
                }
            }
        }
        closesocket(uiFdsocket);
        return;
    }
    
    
    

    2. TCP 实现

    (1) TCP Server

     

    #include <winsock2.h>
    #include <stdio.h>
    #include <windows.h>
    #pragma comment(lib,"ws2_32.lib")
    int main(int argc, char* argv[]){
        //判断是否输入了端口号
        if(argc!=2){
            printf("Usage: %s PortNumber\n",argv[0]);
            exit(-1);
        }
        //把端口号转化成整数
        short port;
        if((port = atoi(argv[1]))==0){
            printf("端口号有误\n");
            exit(-1);
        }
        WSADATA wsa;
        //初始化套接字DLL
        if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){
            printf("套接字初始化失败!\n");
            exit(-1);
        }
        //创建套接字
        SOCKET serverSocket;
        if((serverSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){
            printf("创建套接字失败!\n");
            exit(-1);
        }
        struct sockaddr_in serverAddress;
        memset(&serverAddress,0,sizeof(sockaddr_in));
        serverAddress.sin_family=AF_INET;
        serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
        serverAddress.sin_port = htons(port);
        //绑定
        if(bind(serverSocket,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){
            printf("套接字绑定到端口失败! 端口: %d\n",port);
            exit(-1);
        }
        //进入侦听状态
        if(listen(serverSocket,SOMAXCONN)==SOCKET_ERROR){
            printf("侦听失败!");
            exit(-1);
        }
        printf("Server %d is listening......\n",port);
        SOCKET clientSocket;//用来和客户端通信的套接字
        struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址
        memset(&clientAddress,0,sizeof(clientAddress));
        int addrlen = sizeof(clientAddress);
        //接受连接
        if((clientSocket=accept(serverSocket,(sockaddr*)&clientAddress,&addrlen))==INVALID_SOCKET){
            printf("接受客户端连接失败!");
            exit(-1);
        }
        printf("Accept connection from %s\n",inet_ntoa(clientAddress.sin_addr));
        char buf[4096];
        while(1){
            //接收数据
            memset(buf,0x00,sizeof(buf));
            int bytes;
            if((bytes=recv(clientSocket,buf,sizeof(buf),0))==SOCKET_ERROR){
                printf("接收数据失败!\n");
                exit(-1);
            }
            buf[bytes]='\0';
            printf("Message from %s: %s\n",inet_ntoa(clientAddress.sin_addr),buf);
            if(send(clientSocket,buf,bytes,0)==SOCKET_ERROR){
                printf("发送数据失败!");
                exit(-1);
            }
        }
        //清理套接字占用的资源
        WSACleanup();
        return 0;
    }
    
    
    

    (2) TCP Client

     

     

    #include <winsock2.h>
    #include <stdio.h>
    #include <windows.h>
    #pragma comment(lib,"ws2_32.lib")
    int main(int argc, char* argv[]){
        //判断是否输入了IP地址和端口号
        if(argc!=3){
            printf("Usage: %s IPAddress PortNumber\n",argv[0]);
            exit(-1);
        }
        //把字符串的IP地址转化为u_long
        unsigned long ip;
        if((ip=inet_addr(argv[1]))==INADDR_NONE){
            printf("不合法的IP地址:%s",argv[1]);
            exit(-1);
        }
        //把端口号转化成整数
        short port;
        if((port = atoi(argv[2]))==0){
            printf("端口号有误!");
            exit(-1);
        }
        printf("Connecting to %s:%d......\n",inet_ntoa(*(in_addr*)&ip),port);
        WSADATA wsa;
        //初始化套接字DLL
        if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){
            printf("套接字初始化失败!");
            exit(-1);
        }
        //创建套接字
        SOCKET sock;
        if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){
            printf("创建套接字失败!");
            exit(-1);
        }
        struct sockaddr_in serverAddress;
        memset(&serverAddress,0,sizeof(sockaddr_in));
        serverAddress.sin_family=AF_INET;
        serverAddress.sin_addr.S_un.S_addr = ip;
        serverAddress.sin_port = htons(port);
        //建立和服务器的连接
        if(connect(sock,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){
            printf("建立连接失败!");
            exit(-1);
        }
        char buf[4096];
        while(1){
            printf(">");
            //从控制台读取一行数据
            memset(buf,0,sizeof(buf));
            gets(buf);
            buf[strlen(buf)] = '\0';
            //发送给服务器
           if(send(sock,buf,strlen(buf),0)==SOCKET_ERROR){
                printf("发送数据失败!\n");
                exit(-1);
            }
           int bytes;
            if((bytes=recv(sock,buf,strlen(buf),0))==SOCKET_ERROR){
                printf("接收数据失败!\n");
                exit(-1);
            }
            buf[bytes]='\0';
            printf("Message from %s: %s\n",inet_ntoa(serverAddress.sin_addr),buf);
        }
        //清理套接字占用的资源
        WSACleanup();
        return 0;
    }
  • 相关阅读:
    JS笔记之第七天
    JS笔记之第六天
    JS笔记之第五天
    JS笔记之第四天
    JS笔记之第三天
    JS笔记之第二天
    JS笔记之第一天
    PHP文件上传
    bit、Byte、bps、Bps、pps、Gbps的单位的说明及换算
    Redis初级安装及使用
  • 原文地址:https://www.cnblogs.com/chunqiao/p/3812352.html
Copyright © 2020-2023  润新知