zoukankan      html  css  js  c++  java
  • socket编程

      网络编程就是编写程序使两台计算机相互交换数据。

      两台计算机首先需要物理连接,在此基础上,只需要考虑如何编写数据传输程序。操作系统已经提供了socket。即使对网络数据传输的原理不太熟悉,也能通过socket来编程。

    什么是socket?

      socket的原意是“插座”,在计算机通信领域,socket被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

      我们把插头插到插座上就能从电网获得电力供应,同样,为了远程计算机进行数据传输,需要连接到因特网,而socket就是用来连接到因特网的工具。

      socket的典型应用就是Web服务器和浏览器:浏览器获取用户输入的URL,向服务器发起请求,服务器分析接收到的URL,将对应的网页内容返回给浏览器,浏览器在经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

    socket函数:

      加载了套接字库之后,就可以调用socket函数创建套接字了,该函数的原型声明如下:

        SOCKET socket(int af,int type,int protocol);

        socket函数接收三个参数。第一个参数(af)指定地址族,对于TCP/IP协议的套接字,它只能是AF_INET(也可以写成PF_INET);第二个参数(type)指定socket类型,对于1.1版本的socket,它只支持两种类型的套接字,SOCK_STREAM指定产生流式套接字,SOCK_DGRAM产生数据报套接字;第三个参数(protocol)是与特定的地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。这是推荐使用的一种选择协议的方法。

        如果socket函数调用成功,它就会返回一个新的SOCKET数据类型的套接字描述符;如果调用失败,这个函数就会返回一个INVALID_SOCKET值,错误信息可以通过WSAGet Last Error函数返回。

    流格式套接字(SOCKET_STREAM)

      SOCKET_STREAM是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。

      SOCKET_STREAM有以下几个特征:

      (1)数据在传输过程中不会消失;

      (2)数据是按照顺序传输的;

      (3)数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。

      可以将SOCKET_STREAM比喻成一条传送带,只要传送带本身没有问题(不会断网),就能保证数据不丢失;同时,较晚传送的数据不会先到达,较早传送的数据不会晚到达,这就保证了数据是按照顺序传递的。

      为什么流格式套接字可以达到高质量的数据传输呢?因为它使用了TCP协议,TCP协议会控制你的数据按照顺序到达并且没有错误。

      也许你见过TCP,是因为你经常听说“TCP/IP”。TCP用来确保数据的正确性,IP用来控制数据如何从源头到达目的地,也就是常说的“路由”。

    数据的发送和接收不同步:

      假设传送带传送的是水果,接收者需要凑齐100哥后才能装袋,但是传送带可能把这100个水果分批传送,比如第一批传送20个,第二批传送50个,第三批传送30个。接收者不需要和传送带保持同步,只要根据自己的节奏来装袋即可,不用管传送带传送了几批,也不用每到一批就装一次,可以等到凑够了100个水果再装袋。

      流格式套接字的内部有一个缓冲区(也就是字符数组),通过socket传输的数据将保存到这个缓冲区。接收端在收到数据后并不一定立即读取,只要数据不超过缓冲区的容量,接收端有可能在缓冲区被填满以后一次性的读取,也可能分成好几次读取。也就说,不管数据分几次传送过来,接收端只需要根据自己的要求读取,不用非得在数据到达时立刻读取。传送端有自己的节奏,接收端也有自己的节奏,它们是不一致的。

    数据报格式套接字(SOCK_DGRAM)

      数据报格式套接字也叫“无连接的套接字”,在代码中使用SOCKET_DGRAM表示。计算机只管传输数据,不做数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就是错了,无法重传。因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。

      它有以下特征:

      (1)强调快速传输而非传输顺序;

      (2)传输的数据可能丢失也可能损毁;

      (3)限制每次传输的数据大小;

      (4)数据的发送和接收是同步的(有的教程也称“存在数据边界”)。

    总之,数据报套接字是一种能够不可靠的、不按顺序传递的、以追求速度为目的的套接字。

    数据报套接字也使用IP协议为路由,但是它不使用TCP协议,而使用UDP协议。

    qq视频聊天和语音聊天就使用SOCKET_DGRAM来传输数据,因为首先要保证通信的效率,尽量减小延迟,而数据的正确性是次要的,即使丢失很小的一部分数据,视频和音频也可以正常的解析,最多出现噪点或杂音,不会对通信质量有实质的影响。

    网络模型就是进行数据封装

      我们平常使用的程序一般都是通过应用层来访问网络的,程序产生的数据会一层一层的往下传输,直到最后的网络接口层,就是通过网线发送到互联网上去了。数据每往下走一层,就会被这一层的协议增加一层包装,等到发送到互联网上时,已经比原始数据多了四层包装。整个数据封装的过程就像俄罗斯套娃。

      当另一台计算机接收到数据包时,会从网络接口层一层一层往上传输,每传输一层就拆开一层包装,直到最后的应用层,就到了最原始的数据,这才是程序要使用的数据。

      给数据加包装的过程,实际上就是在数据的头部增加一个标志(一个数据块),表示数据经过了这一层,我已经处理过了。给数据拆包装的过程正好相反,就是去掉数据。头部的标志,让它逐渐现出原形。

    你看,互联网上传输一份数据是多么的复杂,而我们却感受不到,这就是网络模型的厉害之处。我们只要在代码中调用一个函数,就能让下面的所有网络层为我们工作。

      我们所说的socket编程是站在传输层的基础上,所以可以使用TCP/UDP协议,但是不能干【访问网页】这样的事情,因为访问网页所需要的http协议位于应用层。

      两台计算机进行通信时,必须遵守以下原则:

      (1)必须是同一层次进行通信,比如,计算机A的应用层和计算机B的传输层就不能通信,因为它们不在一个层次,数据的拆包会遇到问题。

      (2)每一层的功能都必须相同,也就是拥有完全相同的网络模型。如果网络模型都不同,那不就乱套了,谁都不认识谁。

      (3)数据只能逐层传输,不能跃层。

      (4)每一层可以使用下层提供的服务,并向上层提供服务。

     IP地址

      在因特网上进行通信时,必须要知道对方的IP地址。实际上数据包中已经附带了IP地址,把数据包发送给路由器以后,路由器会根据IP地址找到对方的地理位置,完成一次数据的传递。路由器有非常高效和智能的算法,很快就会找到目标计算机。

    Mac地址

      显示情况是,一个局域网往往才能拥有一个独立的IP,换句话说,IP地址只能定位到一个局域网,无法定位到具体的一台计算机。

      真正唯一标识一台计算机的是MAC地址,每个网卡的MAC地址在全世界都是独一无二的。计算机在出厂时,MAC地址已经被写死在网卡里面了。局域网中的路由器/交换机会记录每台计算机的MAC地址。

      数据包中除了会附带对方的IP地址,还会附带对方的MAC地址,当数据包达到局域网以后,路由器/交换机会根据数据包中的MAC地址找到对应的计算机,然后将数据包转交给它,这样就完成了数据的传递。

    端口号

      有了IP地址和MAC地址,虽然可以找到目标计算机,但仍然不能进行通信。一台计算机可以同时提供多种网络服务,例如Web服务(网站)、FTP服务(文件传输服务)、SMTP服务(邮箱服务)等,仅有IP地址和MAC地址,计算机虽然可以正确接收到数据报=包,但是却不知道要将数据包交给哪个网络程序来处理,所以通信失败。

      为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号,例如,Web服务的端口号时80,FTP服务的端口号时21,SMTP服务的端口号是25。

      端口是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。

      Linux中的一切都是文件,每个文件都有一个整数类型的文件描述符;socket也是一个文件,也有文件描述符。使用socket()函数创建套接字以后,返回值就是一个int类型的文件描述符。

      Windows回区分socket和普通文件,它把socket当作一个网络连接来对待,调用socket()以后,返回值是socket类型,用来表示一个套接字。

     Linux下的socket()函数

      int socket(int af,int type,int protocol);

      (1)af为地址族,也就是IP地址类型,常用的有AF_INET和AF_INET6。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。

    127.0.0.1,它是一个特殊的IP地址,表示本机地址,后面的教程会经常用到。

      (2)type为数据传输方式/套机欸类型,常用的有SOCK_STREAM(流格式套接字/面向连接的套接字)和SOCK_DGRAM(数据报套接字/无连接的套接字)。

      (3)protocol表示传输协议,常用的有IPPROTO_TCP和IPPTOTO_UDP,分别表示TCP传输协议和UDP传输协议。

      一般情况下有了af和type两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。

      本教程使用IPV4地址,参数af的值为PF_INET。如果使用SOCK_STREAM传输数据,那么满足这两个条件的协议只有TCP,因此可以这样来调用socket()函数:

      int tcp_socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//IPPROTO_TCP表示TCP协议。

      这种套接字称为TCP套接字。

      如果使用SOCK_DGRAM传输方式,那么满足这两个条件的协议只有UDP,因此可以这样来调用socket()函数:

      int udp_socket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);   //IPPROTO_UDP表示UDP协议

      这种套接字称为UDP套接字。

      上面两种情况都只有一种协议满足条件,可以将protocol的值设为0,系统会自动推演出因该使用什么协议,如下所示:  

      int tcp_socket = socket(AF_INET,SOCK_STREAM,0);//创建TCP套接字

      int udp_socket = socket(AF_INET,SOCK_DGRAM,0);//创建UDP套接字

    在Windows下创建socket

      SOCKET socket(int af,int type,int protocol);

      除了返回值类型不同,其他都是相同的。Windows不把套接字作为普通文件对待,而是返回SOCKET类型的句柄:

      SOCKET sock = socket(AF_INET,SOCK_STREAM,0);//创建TCP套接字

    bind()和connect()函数:绑定套接字并建立连接

     socket()函数用来创建套接字,确定套接字的各种属性,然后服务器端要用bind()函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理。类似的,客户端也要用connect()函数建立连接。

      bind()函数

      bind()函数的原型为:

        int bind(int sock,struct sockaddr *addr,socklen_t addrlen);//Linux

        int bind (SOCKET sock,const struct sockaddr *addr,int addrlen);//Windows 

    参考自: http://c.biancheng.net/socket/

  • 相关阅读:
    每日总结
    每日总结
    每日总结
    每日总结
    每日总结
    12.20++对王建民老师的评价+个人期末总结
    12.19
    12.18
    12.17
    12.16
  • 原文地址:https://www.cnblogs.com/wjq13752525588/p/11636625.html
Copyright © 2011-2022 走看看