08. 网络套接字

win32网络套接字

TCP

  • windows网络通信内容大致分成4个部分:1、网络常识,2、socket,3、tcp通信,4、udp通信,这里 面不会太过于牵扯到协议,内容太多了,所以只会是通信

    1. 网络常识也分成几个部分来讲,常见的网络架构、常用通讯协议、tcp/ip

      1. 网络架构 主要讲一下七层网络架构和五层网络架构,两者没有太多区别,只是理论上分类不一 样。

        1. 七层网络架构

          最上面的一层是最靠近人的一层,最下面的一层是最靠近硬件的一层。七层分为:应用层、表示层、会 话层、传输层、网络层、数据链路层、物理层。基于功能和协议去区分的这七层。

          • 应用层:功能:文件传输,email,文件服务,虚拟终端(可以理解为分享的屏幕,在远程终端的用户, 可以在远程计算机上运行应用程序,就象是坐在这台计算机前面一样)。协议:TFTP(简单文件传输协 议,是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不 大的文件传输服务),HTTP超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为 广泛的一种网络协议),FTP(和TFTP差不多,或者说TFTP是FTP的一个升级版,它也是文件传输协议 FTP [ File Transfer Protocol ]使得主机间可以共享文件),SMTP(简单邮件传输协议,它是一组用于由源地址到 目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP协议属于TCP/IP协议簇,它帮助每台计 算机在发送或中转信件时找到下一个目的地),DNS(DNS(Domain Name System,域名系统),因特网上 作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够 被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名 解析)。DNS协议运行在UDP协议之上),TELNET(Telnet协议是TCP/IP协议族中的一员,是Internet远程登 陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用 者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会 在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。)等等。
          • 表示层:顾名思义是用来表示的,所以功能:数据格式化,代码转换,数据加密。协议:无,没有协议 (协议:理解为合同一样,行业类大家约定的大家都遵从的规范)可能有做设备的公司可能会需要自己 去写一些协议
          • 会话层:会话,相互交流通迅。功能:解除或建立与其它连接的关系,协议也没有。
          • 传输层:这个传输层和网络层在编程当中是比较重要的两个层次,这一层用的协议:TCP UDP,这就是 传输层要用到的协议。网络层的协议也会是大家熟悉的IP。(一台主机在www网络上面的地址,这个地 址是根据协议来确定怎么分配的)。回到传输层这块,功能:是提供端对端的接口,可以理解为路由 (路由:传递过程)
          • 网络层:它的功能是:为数据选择路由,选择一个端口,用到的协议是IP协议
          • 数据链路层:功能:传输有地址的帧((理解为数据或流),传输数据用的是数据流或数据报文(报文: (message)是网络中交换与传输的数据单元,即站点一次性要发送的数据块。报文包含了将要发送的完 整的数据信息,其长短很不一致,长度不限且可变。),它们的最小单位称之为帧。),还可以做些错误 的检测。量子计算机比较火,也就是量子计算机在传输数据的时候不再借助电或光,而是量子,量子具 备不可分割的特性,电和光可以被分割出一部分这样可能会导致数据的泄密,量子无法分割,如果被窃 取走了,自身就收不到数据,就知道出问题了。协议就有很多了:SLIP(SLIP协议是指串行线路网际协 议,SLIP协议实现了在串行通信线路上运行TCP/IP协议及其应用服务的功能),CSLIP(一种压缩的SLIP协议, 常用在Telnet,Rlogin之类的应用程序中),PPP(PPP协议能在两个路由器之间互相传递数据分组的基本结 构信息)
          • 物理层:功能:传输媒体数据,因为数据要通过媒介来传输,比如光媒,电媒。协议: ISO2210,IEEE802(ISO国际标准化组织)((IEEE)是一个国际性的电子技术与信息科学工程师的协会),这类协 议的使用期限也比较长,一般不会轻意去淘汰。
        2. 五层网络架构

          相对于七层来说删除了表示层和会话层就可以了,这个五层里面的第一层应用层其实对应了七层网络架 构里面的应用,表示和会话层。这样做的原因是因为表示和会话层都没有协议,在硬件层面进行区分不 太好区分。所以五层更多的是对应硬件的网络设备

          • 应用层:对应的网络设备为PC机,更好的理解为连接网络的一个设备,提供一个端口可以连接网络的东 西。
          • 传输层:对应:四层交换机,工作在四层的路由器,从下往上数这是第四层。
          • 网络层:对应:路由器或三层交换机
          • 数据链路层:可以理解为对应网线,不仅仅是网线,有二层交换机(以太网交换机),网卡这些,网卡 一半在数据链路层,一半在物理层。
          • 物理层:对应:中继器(中继器是网络物理层的一种介质连接设备,即中继器工作在物理层。中继器具有 放大信号的作用,它实际上是一种信号再生放大器。家里的路由都会集成这么一个东西),集线器(集线器 的主要功能是对接收到的信号进行再生整形放大,以扩大网络的传输距离,同时把所有节点集中在以它 为中心的节点上。),双绞线(是一种综合布线工程中最常用的传输介质,是由两根具有绝缘保护层的铜导 线组成的)(继电器(Relay),也称电驿,是一种电子控制器件,它具有控制系统(又称输入回路)和被控 制系统(又称输出回路),通常应用于自动控制电路中,它实际上是用较小的电流去控制较大电流的一 种“自动开关”。)
      2. 常用的通讯协议

        • Telnet远程桌面,ftp协议,http协议

        • tcp/ip

        • tcp只需要知道它是一种通讯方式就可以了,还有一个udp,那这两者之间的关系是什么。TCP/IP协议是 一个协议簇。里面包括很多协议的。UDP只是其中的一个。之所以命名为TCP/IP协议,因为TCP,IP协议 是两个很重要的协议,就用他两命名了,tcp是打电话,udp是发短信。

        • Ip(网络之间互连的协议,外文是Internet Protocol的外语缩写,中文缩写为“网协”。缩写为IP),通过设 置ip地址就可以去访问网络,用的最多的ip协议是ipv4(ip协议v版本4),还有一个版本为ipv6,ipv4不 够用了,Ipv4版本是32位的,一般分成4段,内存中就是一个无符号32位的整数,ipv6的话就是一个64 位的整数,通过位数就知道ipv4和ipv6的区别,能保存多少个的地址。只不过用户并不需要去搞清楚。 现在常用的ip是127.0.0.1这个样子,点分格式(一个字符串)。点所隔开的区间就是一个字符。Ip地址 有ABC三类地址。前三段是用来确定路由器,确定主机连上外围网上的哪一个路由,最后一段用来确定 主机,确定主机是这个路由器上的第多少台,最多255台,0一般是用来做网关的。和ip对应的还有一个 子网掩码(子网掩码(subnet mask)又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个IP地 址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它 必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部 分。子网掩码--屏蔽一个IP地址的网络部分的"全1"比特模式。对于A类地址来说,默认的子网掩码是 255.0.0.0;对于B类地址来说默认的子网掩码是255.255.0.0;对于C类地址来说默认的子网掩码是 255.255.255.0。),子网掩码,一般是255.255.255.0。ip地址的前三段来确定路由器,最后一段是主 机位置。所以子网掩码理解为子网遮罩编码。

    2. Socket

      • pc机对应在网络上就是一台主机,在这台Pc机上面会有多个进程需要访问网络,所以需要在Pc机的操作 系统上面去有处理网络的东西,前人就定了一个“套接字”来专门处理网络(源IP地址和目的IP地址以及源 端口号和目的端口号的组合称为套接字)。把一个主机拆分为N个网络端口(Port)一共会有65536个, short的最大范围,在这些端口当中,要注意0-5000的端口一般不用,用来给操作系统的进程来使用 的。一般会用靠后一点的端口,这样比较安全,当然还有一些端口,比如8080端口也会用的比较多,一 个进程只能占用一个端口,不能多进程占用同一个端口的情况,一个进程可以占用多个端口的,或者严 谨一点,同一时刻一个端口只能由一个进程使用。
      • 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,建立网 络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提 供可供程序员做网络开发所用的接口,这就是Socket编程接口。通常也称作"套接字",用于描述IP地址 和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
    3. tcp通信模型

      • c/s模型,客户端(c)/服务器(s)模型,一个服务器来对应多个客户端的处理,一对多的关系。

      • 以下步骤没有特殊指明,服务器和客户端是都需要有的

        1. .准备工作,头文件包含(winsock2.h),库文件的导入(ws2_32.lib)
        2. 确定版本信息,要确定socket版本,ip是有v4和v6两个版本的
        3. 创建socket,使用socket函数
        4. 初始化协议地址簇
        5. 绑定,使用bind函数,把协议地址簇和socket绑定在一起,客户端不要绑定
        6. 服务器端有,需要监听 listen函数,客户端不需要这一步
        7. 服务器端需要接受连接,使用accept;客户端需要连接服务器,使用connect
        8. 连接完成之后,开始通讯,收发数据,函数很多,recv recvfrom send sendto
        9. 通讯完成后关闭socket,closesocket,在第2步确定版本时有一个WSAStartUp函数,这里需要用一个与之相对应的WSACleanUp函数来关闭初始化。

服务端示例

//1、准备工作,准备头文件,库文件的导入
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
    //2、确定socket版本信息
    //MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)
    (((DWORD_PTR)(b)) & 0xff))) << 8))
    WSADATA wsaData;
    //WSAStartup 是windows异步套接字的启动命令
    //第1个参数是版本请求,哪个版本,高阶字节指的是小版本(修订版本),低位字节指定主版本号
    //第2个参数是一个结构体,用来接收socket实现的细节
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        printf("请求版本失败,启动不成功!\n");
        return -1;
    }
    printf("请求版本成功!\n");
    //3、创建一个socket
    //第1个参数协议族,决定了socket的地址类型,在通讯中必须采用对应的地址
    //AF_INET,表示用ipv4地址(32位)与端口号(16位)的组合
    //第2个参数表示类型,用流式socket(SOCK_STREAM)
    //第3个参数指定传输协议,IPPROTO_TCP对应tcp传输协议
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == serverSocket)
    {
        printf("套接字创建失败\n");
        WSACleanup();//关闭套接字的请求
        return -1;
    }
    printf("套接字创建成功\n");
    //4、初始化协议地址
    SOCKADDR_IN serverAddr = { 0 };
    //指定协议族,这个参数值必须和socket函数的第1个参数一致,如果不一致就会失败
    serverAddr.sin_family = AF_INET;
    //存储端口号,注意:网络上的数值方式和pc的数值方式是有区别的,网络上的数值叫大端(先高位再
    存低位)
    //pc是先存低位,再存高位(小端),要通过htons来转换
    serverAddr.sin_port = htons(8888);//这个端口,确定之后,客户端只能通过这个端口来进行
    连接,否则会连接不上
    //存储ip地址,ip地址常用是点分格式的字符串,用inet_addr这个函数转换成ulong;
    serverAddr.sin_addr.S_un.S_addr = inet_addr("172.16.202.160");
    //5、绑定(服务端)
    //第1个参数为之前创建的serverSocket
    //第2个参数为保存协议地址结构的首地址
    //第3个参数为serverAddr的内存大小
    if (bind(serverSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) ==
    SOCKET_ERROR)
    {
        printf("绑定失败!\n");
        closesocket(serverSocket);//先关闭socket
        WSACleanup();//关闭套接字的请求
        return -1;
    }
    printf("绑定成功!\n");
    //6、监听(服务端)
    //第1个参数监听绑定的socket
    //第2个参数监听数量,等待队列的最大长度
    if (listen(serverSocket, 10) == SOCKET_ERROR)
    {
        printf("监听失败!\n");
        closesocket(serverSocket);//先关闭socket
        WSACleanup();//关闭套接字的请求
        return -1;
    }
    printf("监听成功!\n");
    //7、接收连接(服务端)
    SOCKADDR_IN clinetAddr = {};//准备用来接收客户端的ip和端口
    int len = sizeof(clinetAddr);//用一个变量来表示客户端的协议内存大小
    //第1个参数是哪一个socket接收连接
    //第2个参数是客户端的ip地址
    //第2个和第3个参数如果不写,无法保存客户端的协议族信息
    //返回的是表示客户端的socket
    SOCKET clientSocket = accept(serverSocket, (sockaddr *)&clinetAddr, &len);
    if (INVALID_SOCKET == clientSocket)
    {
        printf("连接失败!\n");
        closesocket(serverSocket);//先关闭socket
        WSACleanup();//关闭套接字的请求
        return -1;
    }
    printf("连接成功!\n");
    //inet_ntoa 把一个整数转成一个ip的点分格式的字符串
    printf("有客户端连接进来,客户端的ip: >> %s\n",inet_ntoa(clinetAddr.sin_addr));
    //8、通讯,收发数据
    char buff[128] = {};
    while (1)
    {
        memset(buff, 0, sizeof(buff));//把缓冲区的内容清掉,防止残留上一次发过来的信息
        //第1个参数代表客户端,是从客户端来进行接收信息
        //第4个参数表示收发方式,0表示默认的收发方式,一次都收完,等待流传输完成之后一次收取
        //返回值如果>0表示成功,==0表示没有收到数据或者被关闭,出错会返回负数
        if (recv(clientSocket, buff, sizeof(buff)-1, 0) > 0)//阻塞函数
        printf(">>: %s\n",buff);
        char tempBuff[128] = {};
        printf("请输入:");
        scanf_s("%s", tempBuff, sizeof(tempBuff)-1);
        if (send(clientSocket, tempBuff, strlen(tempBuff), 0) > 0)
        printf("发送消息成功\n");
    }
    //9、关闭socket
    closesocket(clientSocket);
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}

客户端示例

//1、准备工作,准备头文件,库文件的导入
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
    //2、确定socket版本信息
    //MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)
    (((DWORD_PTR)(b)) & 0xff))) << 8))
    WSADATA wsaData;
    //WSAStartup 是windows异步套接字的启动命令
    //第1个参数是版本请求,哪个版本,高阶字节指的是小版本(修订版本),低位字节指定主版本号
    //第2个参数是一个结构体,用来接收socket实现的细节
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        printf("请求版本失败,启动不成功!\n");
        return -1;
    }
    printf("请求版本成功!\n");
    //3、创建一个socket
    //第1个参数协议族,决定了socket的地址类型,在通讯中必须采用对应的地址
    //AF_INET,表示用ipv4地址(32位)与端口号(16位)的组合
    //第2个参数表示类型,用流式socket(SOCK_STREAM)
    //第3个参数指定传输协议,IPPROTO_TCP对应tcp传输协议
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == clientSocket)
    {
        printf("套接字创建失败\n");
        WSACleanup();//关闭套接字的请求
        return -1;
    }
    printf("套接字创建成功\n");
    //4、初始化协议地址
    SOCKADDR_IN clientAddr = { 0 };
    //指定协议族,这个参数值必须和socket函数的第1个参数一致,如果不一致就会失败
    clientAddr.sin_family = AF_INET;
    //存储端口号,注意:网络上的数值方式和pc的数值方式是有区别的,网络上的数值叫大端(先高位再存低位)
    //pc是先存低位,再存高位(小端),要通过htons来转换
    clientAddr.sin_port = htons(8888);//这个端口,确定之后,客户端只能通过这个端口来进行连接,否则会连接不上
    //存储ip地址,ip地址常用是点分格式的字符串,用inet_addr这个函数转换成ulong;
    clientAddr.sin_addr.S_un.S_addr = inet_addr("172.16.202.160");//服务端的ip地址,不是客户端
    //5、连接(客户端)
    //用来建立与服务端的连接
    if (connect(clientSocket, (sockaddr *)&clientAddr, sizeof(clientAddr)) ==
    SOCKET_ERROR)
    {
        printf("连接失败\n");
        closesocket(clientSocket);//先关闭socket
        WSACleanup();//关闭套接字的请求
        return -1;
    }
    printf("连接成功\n");
    //6、通讯,收发数据
    char buff[128] = {};
    while (1)
    {
        memset(buff, 0, sizeof(buff));//把缓冲区的内容清掉,防止残留上一次发过来的信息
        printf("请输入:");
        scanf_s("%s", buff, sizeof(buff)-1);
        if (send(clientSocket, buff, strlen(buff), 0) > 0)
        printf("发送消息成功\n");
        char tempBuff[128] = {};
        if (recv(clientSocket, buff, sizeof(buff)-1, 0) > 0)//阻塞函数
            printf("服务发回 >>: %s\n", buff);
    }
    //9、关闭socket
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}

UDP

  • TCP的连接建立协议来说需要有三次握手,TCP的连接断开协议需要有四次挥手。
  • 三次握手:服务器和客户端要建立一条稳定的传输通道,要经过几个步骤,
    • 第1步:由客户端向服务器发送信息,这个信息叫报文((message)是网络中交换与传输的数据单元,即 站点一次性要发送的数据块。报文包含了将要发送的完整的数据信息,其长短很不一致,长度不限且可 变。)(SYN(SYN:同步标志):SYN_SENT表示请求连接,访问其它的计算机的服务时首先要发个同步 信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为ESTABLISHED,此时SYN_SENT状态非 常短暂)。
    • 第2步:服务器把刚客户端发送的报文重新发回客户端(SYN),再发一个报文(ACK:确认字符。在 数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误)服务器进入 SYN_RECV状态。
    • 第3步,客户端再向服务器发送报文(ACK)代表已经收到服务器端消息。这样客户端和服务器就建立 了一个稳定的连接通道。有三个步骤,所以也叫三次握手。(服务器是不会主动向客户端连接的)
  • 四次挥手(连接终止协议):
    • 第1步:TCP客户端发送一个FIN(结束标志),用来关闭客户到服务器的数据传送。
    • 第2步:服务器收到这个FIN,它发回一个ACK(确认字符),确认收到结束标志。
    • 第3步:服务器关闭客户端的连接(因为服务器处理的客户端会比较多,由服务器先关闭客户端的连 接),再发送一个FIN给客户端。
    • 第4步:客户端发回ACK报文确认。(服务器一般不会主动断开连接,如果需要断开连接,只需要后面 两步就可以了,直接关闭客户端的连接,发FIN报文,然后客户端发回ACK确认)
  • tcp的11种状态,看图:图中:实线箭头代表:客户的正常状态转换;虚线箭头代表:服务器的正常状态 转换;应用:表示状态转换在应用进程发起推行时发生;接收:表示状态转换在接收到分节时发生;发 送:表示这个转换发送什么。
  • 客户端独有的:(1)SYN_SENT (2)FIN_WAIT1 (3)FIN_WAIT2 (4)CLOSING (5)TIME_WAIT 。
  • 服务器独有的:(1)LISTEN (2)SYN_RCVD (3)CLOSE_WAIT (4)LAST_ACK 。
  • 共有的:(1)CLOSED (2)ESTABLISHED 。
  • 客户端TCP状态迁移:
    • CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
  • 服务器TCP状态迁移:
    • CLOSED->LISTEN->SYN收到 ->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
  • CLOSED: 起始状态,关着的 LISTEN: 监听状态,服务器开始监听,为接受客户端的连接做准备
  • SYN_RCVD: 连接建立过程中的状态,接受到了SYN,发送SYN,ACK ,这个状态非常短暂.
  • ESTABLISHED:服务器已接受到了ACK,刚讲过的三次握手完毕,连接已经建立好了,accept函数返回.
  • SYN_SENT:在发送连接请求后等待匹配的连接请求
    • 客户端:SYN发送之后,服务器的SYN,ACK发过来之前.
    • 服务器:SYN发送之后,ACK发过来之前
    • 但不管是客户端和服务器都是:连接请求发送后,收到确定之前
  • CLOSE_WAIT:等待关闭
    • 别人给你发了FIN,你的反应当然只有ACK
  • LAST_ACK:
    • 最后一次发送ACK之前,一般都是客户端回收资源.
  • FIN_WAIT_1和FIN_WAIT_2 都是表示等待对方的FIN报文
  • FIN_WAIT_1:
    • 是当SOCKET在传输状态的时候,想主动关闭连接,就向对方发送FIN报文,这个时候立即进入FIN_WAIT_1 状态,然后等对方回应ACK,那么进入FIN_EAIT_2状态
    • 这个状态是服务器准备回收客户端句柄,并且在队列中选择
  • FIN_WAIT_2:
    • 半关闭,表示我可以断开,但请等我把剩余的一点数据传输完.
  • CLOSING:
    • 同时关闭
  • TIME_WAIT:

三种通信方式 - 网络延迟等待

  • 根据通信双方的分工和信号传输方向可将通信分为三种方式:
    • 单工、半双工与全双工。在计算机网络中主要采用双工方式,
    • 其中:局域网采用半双工方式,城域网和广域网采用全双年方式。
    1. 单工(Simplex)方式:通信双方设备中发送器与接收器分工明确,只能在由发送器向接收器的单一固定 方向上传送数据。采用单工通信的典型发送设备如早期计算机的读卡器,典型的接收设备如打印机。
    2. 半双工(HalfDuplex)方式:通信双方设备既是发送器,也是接收器,两台设备可以相互传送数据,但某 一时刻则只能向一个方向传送数据。例如,步话机是半双工设备,因为在一个时刻只能有一方说话。
    3. 全双工(FullDuplex)方式:通信双方设备既是发送器,也是接收器,两台设备可以同时在两个方向上传 送数据。例如,电话是全双工设备,因为双方可同时说话。
  • 单工:只能a->b,不能反向
  • 半双工:可以a->b 也可以b->a,但a往b发的时候,b不能往a发,b也是同理
  • 双工:可以同时双向通讯
  • 特点
    • tcp
        1. 提供IP环境下的数据可靠传输(一台计算机发出的字节流会无差错的发往网络上的其他计算机,而且计 算机A接收数据包的时候,也会向计算机B回发数据包,这也会产生部分通信量),有效流控,全双工操作 (数据在两个方向上能同时传递),多路复用服务,是面向连接,端到端的传输;
        2. 面向连接:正式通信前必须要与对方建立连接。事先为所发送的数据开辟出连接好的通道,然后再进 行数据发送,像打电话
        3. TCP支持的应用协议:Telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传输协议)。TCP用于 传输数据量大,可靠性要求高的应用。
    • Udp
        1. 面向非连接的(正式通信前不必与对方建立连接,不管对方状态就直接发送,像短信,QQ),不能提供 可靠性、流控、差错恢复功能。UDP用于一次只传送少量数据,可靠性要求低、传输经济等应用。
        2. UDP支持的应用协议:NFS(网络文件系统)、SNMP(简单网络管理系统)、DNS(主域名称系统)、 TFTP(通用文件传输协议)等。

TCP和UDP的区别:

  • 总结:
    • TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立 连接需要开销较多(时间,系统资源)。
    • UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。
  • UDP的编程通信模型
    • UDP也分成客户端和服务器,但是它是没有连接的,那怎么区分客户端和服务器呢?通常来讲,客户端 是不需要绑定端口号的,而服务器端是需要绑定监听的端口号。其他的其实区别不是很大了。从socket 通信的角度来看,UDP通信属于帧传输,TCP则是流传输,在帧传输过程中对于消息的次序和到达情况 没有需求,所以UDP属于不可靠传输,不需要确认和排序。这样在客户端和服务器端的实现上就没有太 大的差别了。

过程

  • 以下步骤没有特殊指明,服务器和客户端是都需要有的
      1. 准备工作,头文件包含(winsock2.h),库文件的导入(ws2_32.lib)
      2. 确定版本信息,要确定socket版本
      3. 创建socket,使用socket函数,第2个参数和第3个参数会不一样(传输媒介和传输协议不同),第2个 参数在TCP时用的就是SOCK_STREAM在udp时用的就是sock_dgram,第3个参数在tcp时用 IPPROTO_TCP在udp时用的就是IPPROTO_UDP
      4. 初始化协议地址簇
      5. 绑定,使用bind函数,就是把协议地址簇和socket绑定在一起,客户端不要绑定。在TCP里面接下来 就是监听,监听后连接,但在udp里面是不需要连接的,所以和tcp不一样,不需要监听和连接,所以绑 定之后直接通讯
      6. 通讯,传输数据
      7. 结束,关闭socket,回收库

服务端示例

//1、准备工作
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
    //2、确定版本信息
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        printf("请求失败!\n");
        return -1;
    }
    printf("请求成功!\n");
    //3、创建socket
    SOCKET serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (serverSocket == INVALID_SOCKET)
    {
        printf("套接字创建失败!\n");
        WSACleanup();
        return -1;
    }
    printf("套接字创建成功!\n");
    //4、初始协议地址
    SOCKADDR_IN serverAddr = {};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    serverAddr.sin_addr.S_un.S_addr = inet_addr("172.16.202.160");//INADDR_ANY
    //5、绑定(服务端)
    if (bind(serverSocket, (sockaddr *)&serverAddr, sizeof(serverAddr)) ==
    SOCKET_ERROR)
    {
        printf("绑定失败\n");
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }
    printf("绑定成功\n");
    //6、通讯
    char buff[128] = {};
    SOCKADDR_IN clientAddr = {};//用来保存客户端的ip地址
    int addrSize = sizeof(clientAddr);
    //最后两个参数可以不给,代表不保存客户端的ip地址
    if (recvfrom(serverSocket, buff, sizeof(buff)-1, 0, (sockaddr *)&clientAddr,
    &addrSize) > 0)
    {
    	printf("来自ip: %s >>: %s\n",inet_ntoa(clientAddr.sin_addr),buff);
    }
    char tempbuff[128] = {};
    scanf_s("%s", tempbuff, 127);
    //第5个参数,代表服务端的ip地址,如果给null,服务端就将接不到消息,这里不可以给null
    //第6个参数代表addr的内存大小
    if (sendto(serverSocket, tempbuff, sizeof(tempbuff)-1, 0, (sockaddr
    *)&clientAddr, sizeof(clientAddr)) > 0)
    	printf("发送成功!\n");
    getchar();
    getchar();
    //7、结束
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}

客户端示例

//1、准备工作
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
    //2、确定版本信息
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        printf("请求失败!\n");
        return -1;
    }
    printf("请求成功!\n");
    //3、创建socket
    SOCKET clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (clientSocket == INVALID_SOCKET)
    {
        printf("套接字创建失败!\n");
        WSACleanup();
        return -1;
    }
    printf("套接字创建成功!\n");
    //4、初始协议地址
    SOCKADDR_IN clientAddr = {};
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_port = htons(8888);
    clientAddr.sin_addr.S_un.S_addr = inet_addr("172.16.202.160");//INADDR_ANY
    //5、通讯
    char buff[128] = {};
    scanf_s("%s", buff, 127);
    //第5个参数,代表服务端的ip地址,如果给null,服务端就将接不到消息,这里不可以给null
    //第6个参数代表addr的内存大小
    if (sendto(clientSocket, buff, sizeof(buff)-1, 0, (sockaddr *)&clientAddr,
    sizeof(clientAddr)) > 0)
    	printf("发送成功!\n");
    char tempBuff[128] = {};
    //最后两个参数,因为知道是服务端发回的消息,服务端的ip及端口已经在clientAddr这里面保存
    if (recvfrom(clientSocket, tempBuff, sizeof(tempBuff)-1, 0, 0,0) > 0)
    {
        printf("来自ip: %s >>: %s\n", inet_ntoa(clientAddr.sin_addr), tempBuff);
    }
    getchar();
    getchar();
    //7、结束
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}
posted @ 2022-06-23 13:31  Quirkygbl  阅读(181)  评论(0编辑  收藏  举报