zoukankan      html  css  js  c++  java
  • [windows网络编程]tcp/udp编程初步详解

           如你所知,简单的网络编程就是称为客户端和服务器的两台主机进行通信。显然通信双方要有一个统一的标识,电话机的比方就很好。这个标识不仅仅是IP地址或者端口号,我们可以将二者结合起来。称之为套接字,socket。在网络编程中socket无疑是关键的部分,因此网络编程也常常被叫做socket编程。

           叫什么不重要,重要的是原理。本文的目的也正是这样,我们试图搞懂它。

           为了减少篇幅,关于协议,TCP,UDP,ISO七层模型等,这些基础知识这里就不做说明了。我们采用的是客户/服务器的模式。又客户对服务器做出通信请求,而服务器对其响应。另外本文的所有程序都可以在vc6.0中实现。

           首先我们先大体文字介绍一下双方的通信流程。比如服务器端:

           1,打开通信通道并告知本地主机,服务器端可在某一地址和端口接收客户端请求。

           2,等待客户请求到达该端口。

           3,接收到重复的服务请求,处理并发送应答信号。如果是并发的请求,那么就启动一个新进程。服务完成后,关闭此进程与客户的通信链路,并终止。

           4,返回第二步,也就是继续等待新的请求

           5,关闭服务器

          客户端所做的要更为简单

           1,打开一个通信通道,并连接到服务器所在主机的特定端口

           2,向服务器端发送请求报文,等待并接受应答,然后可继续提出请求  

           3,通信结束,关闭通信通道并终止

           为了更接近程序实现,可以吧流程简化一下。当然TCP和UDP的实现方法略有不同。事实上windows socket程序只是在伯克利的基础上加了一下异步函数,和符合wondows消息驱动特性的网络事件异步选择机制。根据TCP和UDP的协议远离的区别,可以分为流式套接字(SOCK_STREAM)和数据包套接字(SOCK_DGRAM)

           流式套接字服务器端可分为七步

           1,创建套接字(socket函数)

           2,将套接字绑定到指定端口上(bind函数)

           3,将套接字设定为监听模式,准备接受客户请求(listen函数)

           4,等待客户请求的到来,当请求到来后接收连接请求,返回一个新的对应于此连接的套接字(accept函数)

           5,用返回的套接字和客户进行通信(send/recv函数)

           6,返回,等待一个新的客户请求

           7,关闭套接字(closesocket函数)

           客户端程序可分为四步

            1,创建套接字

            2,向服务器发出链接请求(connect函数)

            3, 和服务器进行通信(send/recv函数)

            4, 关闭套接字

            基于UDP的套接字服务器端稍有不同,创建,绑定,接收,关闭即可因为是无连接的少去了监听和等待的步骤。同样在客户端也少去了请求连接的过程。

            TCP代码实现:首先要建两个工程,不妨设为tcpsrv和tcpclient,分别为客户端和服务器端

            tcpsrv.cpp

           

    /*第一步:加载套接字库 WSAstartup函数,另外也可对套接字库进行版本协商,两个参数,wVersionRequested指定请求的版本号,需要注意的是高字节指定副版本,低字节指定主版本。第二个参数是一个指向wsadata的指针,WSAStartup用其加载的库版本有关的信息填在这个结构中,接收windows socket */
        WORD wVersionRequested;//定义一个word类型的变量
        WSADATA wsaData;
        int err;
        
        wVersionRequested = MAKEWORD( 1, 1 );
        
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            return;
        }
        if ( LOBYTE( wsaData.wVersion ) != 1 
            HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );//终止对winsock库的使用
            return; 
        }
    
    
    /* 1.创建套接字,socket函数,返回一个套接字的描述符,三个参数,一个参数af指定地址族,第二个参数指定套接字的类型也就是TCP或UDP,第三个参数是与特定的地址家族相关的协议,如果指定为0,那么他就根据地址格式和套接字的类别, 自动为你设置一个合适的协议 如果该函数调用成功,他将返回一个新的SOCKET数据类型的套接字描述符。如果失败则返回一个 INVALID_SOCKET错误信息通过WSAGetLastID_SOCKET函数返回。  */
          
    
          SOCKET socksrv=socket(AF_INET,SOCK_STREAM,0);
    
    /*2.绑定套接字函数bind,接收三个参数,第一个指定要绑定的套接字,第二个参数指定该套接字的本地地址信息,是一个指向sockaddr
    结构的指针变量,由于地址结构是为所有地址家族准备的,这个结构可能随所拥有网络协议不同而不同。所以要用第三个参数指定该地址结构的长度,显然要事先定义sockaddr结构体。
    另外,因为实际要求的是内存区,所以对于不同的协议家族,用不同的结构来替换。常用的两个函数
    inet_addr(),将点十进制的IP地址转换为适合分配给s_addr的u_long类型的数值
    inet_ntoa()函数起相反的作用*/
    
          SOCKADDR_IN addrsrv;
          addrsrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY)
          addrsrv.sin_family=AF_INET;
          addrsrv.sin_port=htons(6000);
          bind(socksrv,(SOCKADDR*)&addrsrv,sizeof(SOCKADDR));
    
        
    //4.将套接字设置为监听模式listen函数,第一个参数指定套机字,第二个参数设置等待连接队列的长度,此处设为5
          listen(socaksrv,5);
    
    /*5.设置循环,不断地连接请求的到来,
       首先定义一个地址结构体变量来接受客户的地质结构信息。然后定一个整形变量存储地址长度,为防止调用失败设置一个初始值
    
    */
    
    
         SOCKADDR_IN addrClient;
         int len=sizeof(SOCKADDR);
    
    /*
        6.在while循环中,用一个函数来等待客户的连接到来并接受客户的连接请求。accept函数,成功后会返回一个新的连接套接字的描述符,而原来的套接字则继续监听客户的连接请求
    
    */
    
         while(1)
       {
            SOCKET sockConn=accept(socksrv,(SOCKADDR*)&addrClient,&len);
    
           /*7.进行通信,向客户端发送数据,send函数
    
           */
            char sendBuf[100];
            sprintf(sendBuf,"welcome %s to MFC",inet_ntoa(addrClient.sin_addr));//ip地址
            send(sockConn,sendBuf,strlen(sendBuf)+1,0);
            /*8.同时可以从客户端接受数据用recv函数*/
             
            
            char recvBuf[100];
            recv(sockConn,recvBuf,100,0);
            printf("%s\n",recvBuf);//打印接收到的数据
    
    
            //9.关闭套接字
    
    
              closesocket(sockConn);
          
    
       }
    
         

             相应的客户端程序,tcpclient.cpp

              

    void main()
    {
        WORD wVersionRequested;//定义一个word类型的变量
        WSADATA wsaData;
        int err;
        wVersionRequested = MAKEWORD( 1, 1 );
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            return;
        }                                     
        if ( LOBYTE( wsaData.wVersion ) != 1 ||
            HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
        }
    
          //第一步创建套接字
    
    
           SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
        //第二步,因为客户端不需要绑定,可直接去连接服务器端, //connect函数
            
            SOCKADDR_IN addrsrv;
            addrsrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1")
            //127.0.0.1因为只在本机测试,故此处用本机的IP地址
            addrsrv.addr_family=AF_INET;
            addrsrv.addr_port=htons(6000);
            connect(sockClient,(SOCKADDR*)&addrsrv,sizeof(SOCKADDR));
    
    
    
           //第三步接收数据
    
    
           char recvBuf[100];
           recv(sockClient,recvBuf,100,0);
           printf("%s\n",recvBuf);//将接受到的数据打印出来
           send(sockClient,"this is client",strlen("this is client")+1,0);
    
    
            //第四步 关闭套接字
            closesocket(sockClient);
    
            WSAClenup();
    }

              UDP的相对来说比较简单

              客户端:

    void main()
    {
        WORD wVersionRequested;//定义一个word类型的变量
        WSADATA wsaData;
        int err;
        wVersionRequested = MAKEWORD( 1, 1 );
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            return;
        }
                                             
        if ( LOBYTE( wsaData.wVersion ) != 1 ||
            HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
        }
        
    
        //第一步同样是新建套接字
        SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
        //第二步直接发送数据 用sendto函数
        /*
        int sendto(  
        SOCKET s,                          
        const char FAR *buf,            
        int len,                           
        int flags,                       
        const struct sockaddr FAR *to,  设定目的套接字的地址结构体信息  
        int tolen        地址结构体长度                );
        
        
        */
        SOCKADDR_IN addrsrv;
        addrsrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
        addrsrv.sin_family=AF_INET;
        addrsrv.sin_port=htons(6000);
    
    
    
        sendto(sockClient,"hello",strlen("hello")+1,0,
            (SOCKADDR*)&addrsrv,sizeof(SOCKADDR));
        closesocket(sockClient);
        WSACleanup();
    
    
    }

          

           服务器端:

            

    void main()
    {
        WORD wVersionRequested;//定义一个word类型的变量
        WSADATA wsaData;
        int err;
        wVersionRequested = MAKEWORD( 1, 1 );
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            return;
        }
                                             
        if ( LOBYTE( wsaData.wVersion ) != 1 ||
            HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
        }
        
    
    
    
    
         //第一步同样的创建套接字
        SOCKET socksrv=socket(AF_INET,SOCK_DGRAM,0);
        SOCKADDR_IN addrsrv;
        addrsrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
        addrsrv.sin_family=AF_INET;
        addrsrv.sin_port=htons(6000);
    
        //绑定套接字
        bind(socksrv,(SOCKADDR*)&addrsrv,sizeof(SOCKADDR));
        /*接收数据 此处与TCP不同
        int recvfrom(  
        SOCKET s,                     
        char FAR* buf,  接收数据的buf            
        int len,                     长度 
        int flags,                  0
        struct sockaddr FAR *from,   地址结构体指针,表明是一个返回izhi 
        int FAR *fromlen      设置初始值,并且有返回值      );
        
        
        
        首先定义地址结构的变量    
        */
        SOCKADDR_IN addrClient;
        int len=sizeof(SOCKADDR);
        char recvBuf[100];
    
    
        recvfrom(socksrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
        printf("%s \n",recvBuf);
        closesocket(socksrv);
        WSACleanup();
    
    
    }

           另外加贴一个具有聊天功能的小程序,udp实现

           服务器端:

           

    void main()
    {
        WORD wVersionRequested;//定义一个word类型的变量
        WSADATA wsaData;
        int err;
        wVersionRequested = MAKEWORD( 1, 1 );
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            return;
        }
                                             
        if ( LOBYTE( wsaData.wVersion ) != 1 ||
            HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
        }
    
    
        SOCKET socksrv=socket(AF_INET,SOCK_DGRAM,0);
        SOCKADDR_IN addrsrv;
        addrsrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
        addrsrv.sin_family=AF_INET;
        addrsrv.sin_port=htons(6000);
    
        bind(socksrv,(SOCKADDR*)&addrsrv,sizeof(SOCKADDR));
        
        char recvBuf[100];
        char sendBuf[100];
        char tempBuf[200];
    
    
        SOCKADDR_IN addrClient;
    
        int len=sizeof(SOCKADDR);
        while(1)
        {
            recvfrom(socksrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
            if('q'==recvBuf[0])//判断如果第一个字符是Q则退出
            {
                sendto(socksrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
                printf("chat end !\n");
                break;
            }
            sprintf(tempBuf,"%s say:%s",inet_ntoa(addrClient.sin_addr),recvBuf);
            //如果不是Q则将客户端的IP地址和发送的数据格式化完放到tempBUf中
            printf("%s\n",tempBuf);
            printf("please input data:\n");
            //等待用户输入
            gets(sendBuf);//从标准输入流中获取一行数据
            sendto(socksrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);
            //将用户输入的数据发送到客户端
        }
         closesocket(socksrv);
         WSACleanup();
    
    
    }

           客户端:

             

    void main()
    {
        WORD wVersionRequested;//定义一个word类型的变量
        WSADATA wsaData;
        int err;
        wVersionRequested = MAKEWORD( 1, 1 );
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            return;
        }
                                             
        if ( LOBYTE( wsaData.wVersion ) != 1 ||
            HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
        }
        SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
        SOCKADDR_IN addrsrv;
        addrsrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
        addrsrv.sin_family=AF_INET;
        addrsrv.sin_port=htons(6000);
    
    
        char recvBuf[100];
        char sendBuf[100];
        char tempBuf[200];
    
    
        int len=sizeof(SOCKADDR);
        while(1)
        {
            printf("please input data:\n");
            gets(sendBuf);
            sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,
                (SOCKADDR*)&addrsrv,len);
    
    
            //等待服务器端的回应
            recvfrom(sockClient,recvBuf,100,0,
                (SOCKADDR*)&addrsrv,&len);
            if('q'==recvBuf[0])
            {
                sendto(sockClient,"q",strlen("q")+1,0,
                    (SOCKADDR*)&addrsrv,len);
                printf("chat end!\n");
                break;
            
            
            
            }
            sprintf(tempBuf,"%s say :%s",inet_ntoa(addrsrv.sin_addr),recvBuf);
            printf("%s\n",tempBuf);
        
        
        
        
        }
         closesocket(sockClient);
         WSACleanup();
    }

             

  • 相关阅读:
    【持续更新】leetcode算法-数组篇
    【转】敏捷开发之Scrum扫盲篇
    设计Twitter的api
    给一个表达式字符串加括号,计算它的所有的可能的值
    判断一个整数是否是平方数
    Spring Cloud 入门教程(七): 熔断机制 -- 断路器
    断路器(Curcuit Breaker)模式
    Spring Cloud 入门教程(六): 用声明式REST客户端Feign调用远端HTTP服务
    Spring Cloud 入门教程(五): Ribbon实现客户端的负载均衡
    Spring Cloud 入门教程(四): 分布式环境下自动发现配置服务
  • 原文地址:https://www.cnblogs.com/qxhcpp/p/2473756.html
Copyright © 2011-2022 走看看