Socket 编程教程

Socket 编程教程

(一)TCP/IP协议族

1.什么是 TCP/IP?

TCP/IP 是供已连接因特网的计算机进行通信的通信协议。TCP/IP 指传输控制协议/网际协议 (Transmission Control Protocol / Internet Protocol)。TCP/IP 定义了电子设备(比如计算机)如何连入因特网,以及数据如何在它们之间传输的标准。

2. TCP/IP中的协议

TCP/IP 中包含一系列用于处理数据通信的协议:

TCP (传输控制协议) - 应用程序之间通信

UDP (用户数据包协议) - 应用程序之间的简单通信

IP (网际协议) - 计算机之间的通信

ICMP (因特网消息控制协议) - 针对错误和状态

DHCP (动态主机配置协议) - 针对动态寻址

(1)TCP 使用固定的连接

TCP 用于应用程序之间的通信。当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。

UDP TCP 很相似,但是更简单,同时可靠性低于 TCP

(2)IP 是无连接的

IP 用于计算机之间的通信。IP 是无连接的通信协议。它不会占用两个正在通信的计算机之间的通信线路。这样,IP 就降低了对网络线路的需求。每条线可以同时满足许多不同的计算机之间的通信需要。通过 IP,消息(或者其他数据)被分割为小的独立的包,并通过因特网在计算机之间传送。

IP 负责将每个包路由至它的目的地。

(3)IP 路由器

当一个 IP 包从一台计算机被发送,它会到达一个 IP 路由器。IP 路由器负责将这个包路由至它的目的地,直接地或者通过其他的路由器。在一个相同的通信中,一个包所经由的路径可能会和其他的包不同。而路由器负责根据通信量、网络中的错误或者其他参数来进行正确地寻址。

(4)TCP/IP

TCP/IP 意味着 TCP IP 在一起协同工作。TCP 负责应用软件(比如你的浏览器)和网络软件之间的通信。IP 负责计算机之间的通信。TCP 负责将数据分割并装入 IP 包,然后在它们到达的时候重新组合它们。IP 负责将包发送至接受者。

3.IP地址详解

 随着电脑技术的普及和因特网技术的迅猛发展,因特网已作为二十一世纪人类的一种新的生活方式而深入到寻常百姓家。谈到因特网,IP地址就不能不提,因为无论是从学习还是使用因特网的角度来看,IP地址都是一个十分重要的概念,INTERNET的许多服务和特点都是通过IP地址体现出来的。

  一、IP地址的概念

   我们知道因特网是全世界范围内的计算机联为一体而构成的通信网络的总称。联在某个网络上的两台计算机之间在相互通信时,在它们所传送的数据包里都会含有某些附加信息,这些附加信息就是发送数据的计算机的地址和接受数据的计算机的地址。象这样,人们为了通信的方便给每一台计算机都事先分配一个类似我们日常生活中的电话号码一样的标识地址,该标识地址就是我们今天所要介绍的IP地址。根据TCP/IP协议规定,IP地址是由32位二进制数组成,而且在INTERNET范围内是唯一的。例如,某台联在因特网上的计算机的IP地址为:

11010010 01001001 10001100 00000010

  很明显,这些数字对于人来说不太好记忆。人们为了方便记忆,就将组成计算机的IP地址的32位二进制分成四段,每段8位,中间用小数点隔开,然后将每八位二进制转换成十进制数,这样上述计算机的IP地址就变成了:210.73.140.2。

  二、IP地址的分类

  我们说过因特网是把全世界的无数个网络连接起来的一个庞大的网间网,每个网络中的计算机通过其自身的IP地址而被唯一标识的,据此我们也可以设想,在INTERNET上这个庞大的网间网中,每个网络也有自己的标识符。这与我们日常生活中的电话号码很相像,例如有一个电话号码为0515163,这个号码中的前四位表示该电话是属于哪个地区的,后面的数字表示该地区的某个电话号码。与上面的例子类似,我们把计算机的IP地址也分成两部分,分别为网络标识和主机标识。同一个物理网络上的所有主机都用同一个网络标识,网络上的一个主机(包括网络上工作站、服务器和路由器等)都有一个主机标识与其对应?IP地址的4个字节划分为2个部分,一部分用以标明具体的网络段,即网络标识;另一部分用以标明具体的节点,即主机标识,也就是说某个网络中的特定的计算机号码。例如,盐城市信息网络中心的服务器的IP地址为210.73.140.2,对于该IP地址,我们可以把它分成网络标识和主机标识两部分,这样上述的IP地址就可以写成:

  网络标识:210.73.140.0

  主机标识:     2

  合起来写:210.73.140.2

  由于网络中包含的计算机有可能不一样多,有的网络可能含有较多的计算机,也有的网络包含较少的计算机,于是人们按照网络规模的大小,把32位地址信息设成三种定位的划分方式,这三种划分方法分别对应于A类、B类、C类IP地址。

1.A类IP地址

  一个A类IP地址是指,在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码。如果用二进制表示IP地址的话,A类IP地址就由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”。A类IP地址中网络的标识长度为7位,主机标识的长度为24位,A类网络地址数量较少,可以用于主机数达1600多万台的大型网络。

2.B类IP地址

  一个B类IP地址是指,在IP地址的四段号码中,前两段号码为网络号码,剩下的两段号码为本地计算机的号码。如果用二进制表示IP地址的话,B类IP地址就由2字节的网络地址和2字节主机地址组成,网络地址的最高位必须是“10”。B类IP地址中网络的标识长度为14位,主机标识的长度为16位,B类网络地址适用于中等规模规模的网络,每个网络所能容纳的计算机数为6万多台。

3.C类IP地址

  一个C类IP地址是指,在IP地址的四段号码中,前三段号码为网络号码,剩下的一段号码为本地计算机的号码。如果用二进制表示IP地址的话,C类IP地址就由3字节的网络地址和1字节主机地址组成,网络地址的最高位必须是“110”。C类IP地址中网络的标识长度为21位,主机标识的长度为8位,C类网络地址数量较多,适用于小规模的局域网络,每个网络最多只能包含254台计算机。

  除了上面三种类型的IP地址外,还有几种特殊类型的IP地址,TCP/IP协议规定,凡IP地址中的第一个字节以“lll0”开始的地址都叫多点广播地址。因此,任何第一个字节大于223小于240的IP地址是多点广播地址;IP地址中的每一个字节都为0的地址(“0.0.0.0”)对应于当前主机;IP地址中的每一个字节都为1的IP地址(“255.255.255.255”)是当前子网的广播地址;IP地址中凡是以“llll0”的地址都留着将来作为特殊用途使用;IP地址中不能以十进制“127”作为开头,27.1.1.1用于回路测试,同时网络ID的第一个6位组也不能全置为“0”,全“0”表示本地网络。

  三、IP的寻址规则 

1.网络寻址规则

A、 网络地址必须唯一。

B、 网络标识不能以数字127开头。在A类地址中,数字127保留给内部回送函数。

C、 网络标识的第一个字节不能为255。数字255作为广播地址。

D、 网络标识的第一个字节不能为“0”,“0”表示该地址是本地主机,不能传送。

2.主机寻址规则

A、主机标识在同一网络内必须是唯一的。

B、主机标识的各个位不能都为“1”,如果所有位都为“1”,则该机地址是广播地址,而非主机的地址。

C、主机标识的各个位不能都为“0”,如果各个位都为“0”,则表示“只有这个网络”,而这个网络上没有任何主机。

  四、IP子网掩码概述 

1.子网掩码的概念

  子网掩码是一个32位地址,用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在远程网上。

2.确定子网掩码数

  用于子网掩码的位数决定于可能的子网数目和每个子网的主机数目。在定义子网掩码前,必须弄清楚本来使用的子网数和主机数目。

  定义子网掩码的步骤为:

A、确定哪些组地址归我们使用。比如我们申请到的网络号为 “210.73.a.b”,该网络地址为c类IP地址,网络标识为“210.73”,主机标识为“a.b”。

B、根据我们现在所需的子网数以及将来可能扩充到的子网数,用宿主机的一些位来定义子网掩码。比如我们现在需要12个子网,将来可能需要16个。用第三个字节的前四位确定子网掩码。前四位都置为“1”,即第三个字节为“11110000”,这个数我们暂且称作新的二进制子网掩码。

C、把对应初始网络的各个位都置为“1”,即前两个字节都置为“1”,第四个字节都置为“0”,则子网掩码的间断二进制形式为:“11111111.11111111.11110000.00000000”

D、把这个数转化为间断十进制形式为:“255.255.240.0”

  这个数为该网络的子网掩码。

3.IP掩码的标注

A、无子网的标注法

  对无子网的IP地址,可写成主机号为0的掩码。如IP地址210.73.140.5,掩码为255.255.255.0,也可以缺省掩码,只写IP地址。

B、有子网的标注法

  有子网时,一定要二者配对出现。以C类地址为例。

1.IP地址中的前3个字节表示网络号,后一个字节既表明子网号,又说明主机号,还说明两个IP地址是否属于一个网段。如果属于同一网络区间,这两个地址间的信息交换就不通过路由器。如果不属同一网络区间,也就是子网号不同,两个地址的信息交换就要通过路由器进行。例如:对于IP地址为210.73.140.5的主机来说,其主机标识为00000101,对于IP地址为210.73.140.16的主机来说它的主机标识为00010000,以上两个主机标识的前面三位全是000,说明这两个IP地址在同一个网络区域中。

2.掩码的功用是说明有子网和有几个子网,但子网数只能表示为一个范围,不能确切讲具体几个子网,掩码不说明具体子网号,有子网的掩码格式(对C类地址):主机标识前几位为子网号,后面不写主机,全写0。

  五、IP的其他事项 

1.一般国际互联网信息中心在分配IP地址时是按照网络来分配的,因此只有说到网络地址时才能使用A类、B类、C类的说法;

2.在分配网络地址时,网络标识是固定的,而计算机标识是可以在一定范围内变化的,下面是三类网络地址的组成形式:

A类地址:73.0.0.0

B类地址:160.153.0.0

C类地址:210.73.140.0

  上述中的每个0均可以在0~255之间进行变化。

3.因为IP地址的前三位数字已决定了一个IP地址是属于何种类型的网络,所以A类网络地址将无法再分成B类IP地址,B类IP地址也不能再分成C类IP地址。

4.在谈到某一特定的计算机IP地址时不宜使用A类、B类、C类的说法,但可以说主机地址是属于哪一个A类、B类、C类网络了。

  通过上面的学习,大家对IP地址肯定有了了解。有了IP地址大家就可以发送电子邮件了,并且可以获得Internet网上的其他信息,例如可以获得Internet上的WWW服务、BBS服务、FTP服务等等。

4.字节顺序与大小端问题

(1)什么是大端和小端

      Big-EndianLittle-Endian的定义如下:

1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:

1)大端模式:

低地址 -----------------> 高地址

0x12  |  0x34  |  0x56  |  0x78

2)小端模式:

低地址 ------------------> 高地址

0x78  |  0x56  |  0x34  |  0x12

可见,大端模式和字符串的存储模式类似。

3)下面是两个具体例子:

16bit宽的数0x1234Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:

 

内存地址

小端模式存放内容

大端模式存放内容

0x4000

0x34

0x12

0x4001

0x12

0x34

32bit宽的数0x12345678Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址

小端模式存放内容

大端模式存放内容

0x4000

0x78

0x12

0x4001

0x56

0x34

0x4002

0x34

0x56

0x4003

0x12

0x78

 

 4)大端小端没有谁优谁劣,各自优势便是对方劣势:

小端模式 :强制转换数据不需要调整字节内容,124字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。

数组在大端小端情况下的存储:

  以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value

Big-Endian: 低地址存放高位,如下:

高地址

        ---------------

        buf[3] (0x78) -- 低位

        buf[2] (0x56)

        buf[1] (0x34)

        buf[0] (0x12) -- 高位

        ---------------

        低地址

Little-Endian: 低地址存放低位,如下:

高地址

        ---------------

        buf[3] (0x12) -- 高位

        buf[2] (0x34)

        buf[1] (0x56)

        buf[0] (0x78) -- 低位

        --------------

低地址

 

(2)为什么会有大小端模式之分呢?

      这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bitchar之外,还有16bitshort型,32bitlong型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bitshortx,在内存中的地址为0x0010x的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,网络字节顺序采用大端模式,

如何检测大小端:

联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写:

[cpp] 

#include <stdio.h>  

#include <stdlib.h>  

union word  

{   

    int     a;  

    char        b;  

} c;  

int checkCPU(void)  

{  

    c.a = 1;  

    printf("c.b=%d\n",c.b);  

    return (c.b==1);  

}   

int main(void)  

{  

    int i;  

    i= checkCPU();  

    if(i==0)  

        printf("this is Big_endian\n");  

    else if(i==1)  

        printf("this is Little_endian\n");  

    return 0;  

}  


如在小端的时候,c.b1,大端的时候为0.

(3)字节顺序转换函数

既然网络上传输的数据以及各种类型的主机字节顺序有差异,因此X86平台下编译网络程序的时候,要注意大小端转换.比如在绑定socketip地址的时候之一使用网络顺序.

htonl() 

htons()

ntohl()

ntohs()

函数可以实现字节顺序与主机字节顺序转换.

 

(二)五层模型与OSI模型

1.TCP/IP模型与OSI参考模型

 

两者的主要区别如下: 
·TCP/IP协议中的应用层处理开放式系统互联模型中的第五层、第六层和第七层的功能。 
·TCP/IP协议中的传输层并不能总是保证在传输层可靠地传输数据包,而开放式系统互联模型可以做到。TCP/IP协议还提供一项名为UDP(用户数据报协议)的选择。UDP不能保证可靠的数据包传输。

  

(1)网络接口层(物理层和数据链路层)

物理层和数据链路层涉及到在通信信道上传输的原始比特流,它实现传输数据所需要的机械、电气、功能性及过程等手段,提供检错、纠错、同步等措施,使之对网络层显现一条无错线路;并且进行流量调控。负责数据帧的发送和接收,将帧格式的数据放到网络上或从网络上把帧取下来

(2)网络层

网络层,将数据封装成Internet数据包,并运行必要的路由算法。网络层检查网络拓扑,以决定传输报文的最佳路由,执行数据转发。其关键问题是确定数据包从源端到目的端如何选择路由。

网络层的主要协议有:

l IP(负责主机和网络之间的路径寻址和路由数据包);

l ICMPInternet Control Message Protocol,互联网控制报文协议,发送消息并报告数据包的传送错误);
网际控制消息协议ICMP是一个网络层的协议,它提供了错误报告和其它回送给源点的关于 IP 数据包处理情况的消息。ICMP通常为IP层或者更高层协议使用,一些ICMP报文把差错报文返回给用户进程。ICMP报文通常被封装在IP数据包内传输。RFC 792 中有关于ICMP的详细说明。
ICMP包含几种不同的消息,其中ping程序借助于echo request消息,主机可通过它来测试网络的可达性,ICMP Echo Reply 消息表示该节点是可达的。ICMP还定义了源抑制(source quench)报文。当路由器的缓冲区满后,送入的报文被丢弃,此时路由器向发送报文的主机发送源抑制报文,要求降低发送速率。

 

l IGMPInternet Group Management Protocol,互联网组管理协议实现本地多路广播路由器报告);

l ARPAddress Resolution Protocol,地址解析协议获得同一物理网络中的硬件主机地址);RARPReverse Address Resolution Protocol,反向地址解析协议) 地址解析协议ARP是一种广播协议,主机通过它可以动态地发现对应于一个IP 地址的MAC层地址。
假定主机A需要知道主机BMAC地址,主机A发送称为ARP请求的以太网数据帧给网段上的每一台主机,这个过程称为广播。发送的ARP请求报文中,带有自己的IP地址到MAC地址的映射,同时还带有需要解析的目的主机的IP地址。目的主机B收到请求报文后,将其中的主机AIP地址与MAC地址的映射存到自己的ARP高速缓存中,并把自己的IP地址到MAC地址的映射作为响应发回主机A。主机A收到ARP应答,就得到了主机BMAC地址,同时,主机A缓存主机BIP地址到MAC地址映射。

RARP常用于X终端和无盘工作站等,这些设备知道自己MAC地址,需要获得IP地址。以上图为例,无盘工作站需要获得自己的IP地址,向网络中广播RARP请求,RARP服务器接收广播请求,发送应答报文,无盘工作站获得IP地址。对应于ARP、RARP请求以广播方式发送,ARP、RARP应答一般以单播方式发送,以节省网络资源。

(3)传输层

传输层的基本功能是为两台主机间的应用程序提供端到端的通信。传输层从应用层接受数据,并且在必要的时候把它分成较小的单元,传递给网络层,并确保到达对方的各段信息正确无误。

传输层的主要协议有:

l TCP:为应用程序提供可靠通信。适合传输大批数据,并要求得到响应的应用程序。

l UDPUser Datagraph Protocol,用户数据报协议):提供无连接通信,且不对传送包进行可靠确认。适合小数据传输。

 

传输层位于应用层和网络层之间,为终端主机提供端到端的连接,以及流量控制(由窗口机制实现)、可靠性(由序列号和确认技术实现)、支持全双工传输等等。传输层协议有两种:TCPUDP。虽然TCPUDP都使用相同的网络层协议IP,但是TCPUDP却为应用层提供完全不同的服务。

传输控制协议TCP:为应用程序提供可靠的面向连接的通信服务,适用于要求得到响应的应用程序。目前,许多流行的应用程序都使用TCP

用户数据报协议UDP:提供了无连接通信,且不对传送数据包进行可靠的保证。适合于一次传输小量数据,可靠性则由应用层来负责。

TCP协议通过以下过程来保证端到端数据通信的可靠性:

1TCP实体把应用程序划分为合适的数据块,加上TCP报文头,生成数据段;

2、当TCP实体发出数据段后,立即启动计时器,如果源设备在计时器清零后仍然没有收到目的设备的确认报文,重发数据段;

3、当对端TCP实体收到数据,发回一个确认。

4TCP包含一个端到端的校验和字段,检测数据传输过程的任何变化。如果目的设备收到的数据校验和计算结果有误,TCP将丢弃数据段,源设备在前面所述的计时器清零后重发数据段。

5、由于TCP数据承载在IP数据包内,而IP提供了无连接的、不可靠的服务,数据包有可能会失序。TCP提供了重新排序机制,目的设备将收到的数据重新排序,交给应用程序。

6TCP提供流量控制。TCP连接的每一端都有缓冲窗口。目的设备只允许源设备发送自己可以接收的数据,防止缓冲区溢出。

7TCP支持全双工数据传输。

TCP协议为终端设备提供了面向连接的、可靠的网络服务,UDP协议为终端设备提供了无连接的、不可靠的数据报服务。从上图我们可以看出,TCP协议为了保证数据传输的可靠性,相对于UDP报文,TCP报文头部有更多的字段选项。

首先让我们来看一下TCP的报文头部主要字段:

每个TCP报文头部都包含源端口号(source port)和目的端口号(destination port),用于标识和区分源端设备和目的端设备的应用进程。在TCP/IP协议栈中,源端口号和目的端口号分别与源IP地址和目的IP地址组成套接字(socket),唯一的确定一条TCP连接。

序列号(Sequence number)字段用来标识TCP源端设备向目的端设备发送的字节流,它表示在这个报文段中的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序列号对每个字节进行计数。序列号是一个32bits的数。

 

既然每个传输的字节都被计数,确认序号(Acknowledgement number32bits)包含发送确认的一端所期望接收到的下一个序号。因此,确认序号应该是上次已成功收到的数据字节序列号加1

TCP的流量控制由连接的每一端通过声明的窗口大小(windows size)来提供。窗口大小用数据包来表示,例如Windows size=3, 表示一次可以发送三个数据包。窗口大小起始于确认字段指明的值,是一个16bits字段。窗口大小可以调节。

校验和(checksum)字段用于校验TCP报头部分和数据部分的正确性。

最常见的可选字段是MSSMaximum Segment Size,最大报文大小)。MSS指明本端所能够接收的最大长度的报文段。当一个TCP连接建立时,连接的双方都要通告各自的MSS协商可以传输的最大报文长度。我们常见的MSS1024(以太网可达1460字节)字节。

相对于TCP报文,UDP报文只有少量的字段:源端口号、目的端口号、长度、校验和等,各个字段功能和TCP报文相应字段一样。

UDP报文没有可靠性保证和顺序保证字段,流量控制字段等,可靠性较差。当然,使用传输层UDP服务的应用程序也有优势。正因为UDP协议较少的控制选项,在数据传输过程中,延迟较小,数据传输效率较高,适合于对可靠性要求并不高的应用程序,或者可以保障可靠性的应用程序像DNSTFTPSNMP等;UDP协议也可以用于传输链路可靠的网络

 

(4)应用层

应用层负责处理特定的应用程序细节。应用层显示接收到的信息,把用户的数据发送到低层,为应用软件提供网络接口。

应用层主要协议有:

l FTP(文件传输协议、File Transfer Protocol)是用于文件传输的Internet标准。FTP支持一些文本文件(例如ASCII、二进制等等)和面向字节流的文件结构。FTP使用传输层协议TCP在支持FTP的终端系统间执行文件传输,因此,FTP被认为提供了可靠的面向连接的服务,适合于远距离、可靠性较差线路上的文件传输。

l TFTPTrivial File Transfer Protocol,简单文件传输协议)也是用于文件传输,但TFTP使用UDP提供服务,被认为是不可靠的,无连接的。TFTP通常用于可靠的局域网内部的文件传输。

l SMTPSimple Mail Transfer Protocol。简单邮件传输协议)支持文本邮件的Internet传输。

l POP3Post Office Protocol)是一个流行的Internet邮件标准。

l SNMPSimple Network Management Protocol。简单网络管理协议)负责网络设备监控和维护,支持安全管理、性能管理等。

l Telnet是客户机使用的与远端服务器建立连接的标准终端仿真协议。

l HTTP协议支持WWWWorld Wide Web,万维网)和内部网信息交互,支持包括视频在内的多种文件类型。HTTP是当今流行的Internet标准。

l DNSDomain Name System,域名系统)把网络节点的易于记忆的名字转化为网络地址。

l WINSWindows Internet Name ServerWindows Internet命名服务器),此服务可以将NetBIOS 名称注册并解析为网络上使用的IP地址。

l BootPBootstrap Protocol,引导协议)是使用传输层UDP协议动态获得IP地址的协议。

 

2.封包和拆包

 

3.端口号

 TCP协议和UDP协议使用16bits端口号(或者socket)来表示和区别网络中的不同应用程序,网络层协议IP使用特定的协议号(TCP 6UDP 17)来表示和区别传输层协议。

任何TCP/IP实现所提供的服务都是1~1023之间的端口号,这些端口号由IANAInternet Assigned Numbers AuthorityInternet号码分配机构)分配管理。其中,低于255的端口号保留用于公共应用;2551023的端口号分配给各个公司,用于特殊应用;对于高于1023的端口号,称为临时端口号,IANA未做规定。

常用的TCP端口号有:HTTP 80FTP 20/21Telnet 23SMTP 25DNS 53等;常用的保留UDP端口号有:DNS 53BootP 67server/ 68client),TFTP 69SNMP 161等。

套接字(socket)分为源套接字和目的套接字:

源套接字:源端口号+IP地址;

目的套接字:目的端口号+目的IP地址;

源套接字和目的套接字用于唯一的确定一条TCP连接。

(三)一、Socket简介

1.基础定义

Socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。

几个定义:

1IP地址:即依照TCP/IP协议分配给本地主机的网络地址,两个进程要通讯,任一进程首先要知道通讯对方的位置,即对方的IP

2)端口号:用来辨别本地通讯进程,一个本地的进程在通讯时均会占用一个端口号,不同的进程端口号不同,因此在通讯前必须要分配一个没有被访问的端口号。

3)连接:指两个进程间的通讯链路。

4)半相关:网络中用一个三元组可以在全局唯一标志一个进程:

(协议,本地地址,本地端口号)

这样一个三元组,叫做一个半相关,它指定连接的每半部分。

4)全相关:一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:

(协议,本地地址,本地端口号,远地地址,远地端口号)

这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。

TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层。

 

2.客户/服务器模式

TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器(Client/Server, C/S)模式,即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:

1)首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。

2)其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务器模式的TCP/IP

服务器端:

其过程是首先服务器方要先启动,并根据请求提供相应服务:

1)打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口(如FTP的端口可能为21)接收客户请求;

2)等待客户请求到达该端口;

3)接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用forkexec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。

4)返回第(2)步,等待另一客户请求。

5)关闭服务器

客户端:

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

2)向服务器发服务请求报文,等待并接收应答;继续提出请求......

3)请求结束后关闭通信通道并终止。

 

从上面所描述过程可知:

1)客户与服务器进程的作用是非对称的,因此代码不同。

2)服务器进程一般是先启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。

Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open > 读写write/read > 关闭close”模式来操作。Socket就是该模式的一个实现,        socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/IO、打开、关闭).
     说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

       注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。

3.三次握手

名词解释:

SYN:发送端希望双方建立同步处理,表示“主动”连接到对方的意思;

ACK:数据确认包;

FIN:表示传送结束,通知对方数据传送完毕,是否同意断线;

 

我们知道tcp建立连接要进行三次握手,即交换三个分组。大致流程如下:

客户端向服务器发送一个SYN J

服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1

客户端再想服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

1socket中发送的TCP三次握手

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

4.四次握手

socket中的四次握手释放连接的过程,请看下图:

2socket中发送的TCP四次握手

图示过程如下:

某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M

另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;

一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N

接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FINACK

5.select模型

   在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的监听客户端的请求,有新的请求到达时,开辟一个新的线程去和该客户端进行后续处理,但是这样针对每一个客户端都需要去开辟一个新的线程,效率必定底下。

     其实,socket编程提供了很多的模型来处理这种情形,我们只要按照模型去实现我们的代码就可以解决这个问题。主要有select模型和重叠I/o模型,以及完成端口模型。这次,我们主要介绍下select模型,该模型又分为普通select模型,wsaasyncselect模型,wsaeventselect模型。我们将通过样例代码的方式逐一介绍。

(1)Select模型原理

利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。

 

select参数和返回值意义如下:

int select (

 IN int nfds,                           //0,无意义

 IN OUT fd_set* readfds,      //检查可读性

 IN OUT fd_set* writefds,     //检查可写性

 IN OUT fd_set* exceptfds,  //例外数据

 IN const struct timeval* timeout);    //函数的返回时间

 

struct  timeval {

        long    tv_sec;        //

        long    tv_usec;     //毫秒

};

select返回fd_set中可用的套接字个数。

 

fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:

FD_CLR( s, *set) 从队列set删除句柄s;

FD_ISSET( s, *set) 检查句柄s是否存在与队列set;

FD_SET( s, *set )把句柄s添加到队列set;

FD_ZERO( *set ) set队列初始化成空队列.

 

Select工作流程

1:用FD_ZERO宏来初始化我们感兴趣的fd_set

也就是select函数的第二三四个参数。

2:用FD_SET宏来将套接字句柄分配给相应的fd_set

如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中

3:调用select函数。

如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,

4:用FD_ISSET对套接字句柄进行检查。

如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。

(2)select模型

使用该模型时,在服务端我们可以开辟两个线程,一个线程用来监听客户端的连接请求,另一个用来处理客户端的请求。主要用到的函数为select函数。

该模型有个最大的缺点就是,它需要一个死循环不停的去遍历所有的客户端套接字集合,询问是否有数据到来,这样,如果连接的客户端很多,势必会影响处理客户端请求的效率,但它的优点就是解决了每一个客户端都去开辟新的线程与其通信的问题。如果有一个模型,可以不用去轮询客户端套接字集合,而是等待系统通知,当有客户端数据到来时,系统自动的通知我们的程序,这就解决了select模型带来的问题了。

(3)WsaAsyncSelect模型

WsaAsyncSelect模型就是这样一个解决了普通select模型问题的socket编程模型。它是在有客户端数据到来时,系统发送消息给我们的程序,我们的程序只要定义好消息的处理方法就可以了,用到的函数只要是WSAAsyncSelect,如:首先,我们定义一个Windows消息,告诉系统,当有客户端数据到来时,发送该消息给我们。

#define  UM_SOCK_ASYNCRECVMSG  WM_USER + 1

接下来,我们需要在我们的窗口添加对UM_SOCK_ASYNCRECVMSG消息的处理函数,在该函数中真正接收客户端发送过来的数据,在这个消息处理函数中的wparam参数表示的是客户端套接字,lparam参数表示的是发生的网络事件如:

可以看到WsaAsyncSelect模型是非常简单的模型,它解决了普通select模型的问题,但是它最大的缺点就是它只能用在windows程序上,因为它需要一个接收系统消息的窗口句柄,那么有没有一个模型既可以解决select模型的问题,又不限定只能是windows程序才能用呢?下面我们来看看WsaEventSelect模型。

(4)WsaEventSelect模型

WsaEventSelect模型是一个不用主动去轮询所有客户端套接字是否有数据到来的模型,它也是在客户端有数据到来时,系统发送通知给我们的程序,但是,它不是发送消息,而是通过事件的方式来通知我们的程序,这就解决了WsaAsyncSelect模型只能用在windows程序的问题。

该模型的实现,我们也可以开辟两个线程来进行处理,一个用来接收客户端的连接请求,一个用来与客户端进行通信,用到的主要函数有WSAEventSelectWSAWaitForMultipleEventsWSAEnumNetworkEvents实现方式如下:

首先定义三个全局数组

SOCKET      g_SockArray[MAX_NUM_SOCKET];//存放客户端套接字

WSAEVENT    g_EventArray[MAX_NUM_SOCKET];//存放该客户端有数据到来时,触发的事件

UINT32      g_totalEvent = 0;//记录客户端的连接数

该模型通过一个死循环里面调用WSAWaitForMultipleEvents函数来等待客户端套接字对应的Event的到来,一旦事件通知到达,就通过该套接字去接收数据。虽然WsaEventSelect模型的实现较前两种方法复杂,但它在效率和兼容性方面是最好的。

以上三种模型虽然在效率方面有了不少的提升,但它们都存在一个问题,就是都预设了只能接收64个客户端连接,虽然我们在实现时可以不受这个限制,但是那样,它们所带来的效率提升又将打折扣,那又有没有什么模型可以解决这个问题呢?

6.Socket API

(1)int socket(int domain, int type, int protocol);

domain协议域,又称为协议族(family)。决定此对象使用的协议类型。常用的协议族有, AF_INETAF_INET6AF_LOCAL(或称AF_UNIXUnixsocket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址AF_LOCAL决定使用本机通信。

Type:指定socket类型。常用的socket类型有,SOCK_STREAM可靠地,面向连接的流SocketTCPSOCK_DGRAM不可靠的,面向无连接的流UDP)SOCK_RAW(原始套接口)SOCK_PACKETSOCK_SEQPACKET等等

Protocol:指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC 等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。一般设置为0

返回值:执行成功返回一个打开的文件描述符,此时,Socket对象没有绑定任何IP信息,还不能进行通信。否则返回-1

(2)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

l sockfd:即监听socket描述字,它是通过socket()函数创建了,唯一标识一个socket

l addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。谨记对主机字节序IP地址和端口不要做任何假定,务必将其转化为网络字节序再赋值给Socket这个地址结构根据地址创建socket时的地址协议族的不同而不同,如
ipv4对应的是: 

 

ipv6对应的是:

 

l addrlen:对应的是地址的长度。一般使用sizeof求得

返回值:将指定Socket与对应网络地址(IP、端口信息)绑定,执行成功返回0,否则返回-1

l 其他:通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。小于1024的端口号为系统保留。

(3)int listen(int sockfd, int backlog);

l Sockfd:绑定了IP和端口信息的监听Socket文件描述符;

l Backlog:请求排队的最大长度。当有多个客户端程序和服务端相连时。此值表示可以使用的处于等待的队列长度。默认值为5.

返回值:执行成功返回0, 否则返回-1

 

(4)int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

l Sockfd:客户端socket描述符;

l Addr:服务器端的地址(IP地址和端口);

l Addrlenaddr的长度;

返回值:执行成功,则与服务器建立连接,并返回0。失败返回-1

(5)int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd

l Sockfd:服务器端监听socket描述符;

l Addr:用来存储客户端的IP地址和端口信息

l Addrlenaddr地址空间的长度;

返回值:服务端通过调用accept函数来监听客户端请求。若没有监听到客户端请求,此函数处于阻塞状态。若监听到,则返回一个新的文件描述符以标示该链接,从而使原来的文件描述符可以继续等待新的链接,从而实现多客户端。如果执行失败 则返回-1.

此时我们需要区分两种套接字,

       监听套接字: 监听套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,是服务器开始调用socket()函数生成的,称为监听socket描述字(监听套接字)

       连接套接字:一个套接字会从主动连接的套接字变身为一个监听套接字;而accept函数返回的是已连接socket描述字(一个连接套接字),它代表着一个网络已经存在的点点连接。

        一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

        自然要问的是:为什么要有两种套接字?原因很简单,如果使用一个描述字的话,那么它的功能太多,使得使用很不直观,同时在内核确实产生了一个这样的新的描述字。

连接套接字socketfd_new 并没有占用新的端口与客户端通信,依然使用的是与监听套接字socketfd一样的端口号

(6)Ssize_t write(int fd,const void *buf,size_t nbytes);

l  Fd:连接套接字

l Buf:待写入的字符串;

l Nbytes:要写入的数量;

l 返回值:

 Write函数将buf中的nbytes字节内容写入到文件描述符中,成功返回写的字节数,失败返回-1.并设置errno变量。在网络程序中,当我们向套接字文件描述舒服写数据时有两种可能:

    1write的返回值大于0,表示写了部分数据或者是全部的数据,这样用一个while循环不断的写入数据,但是循环过程中的buf参数和nbytes参数是我们自己来更新的,也就是说,网络编程中写函数是不负责将全部数据写完之后再返回的,说不定中途就返回了!

    2、返回值小于0,此时出错了,需要根据错误类型进行相应的处理。

如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

(7) Ssize_t read(int fd,void *buf,size_t nbyte)

    Read函数是负责从fd中读取内容,当读取成功时,read返回实际读取到的字节数,如果返回值是0,表示已经读取到文件的结束了,小于0表示是读取错误。

如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

在网络上传递数据时,我们一般把数据转换为char类型,接收的时候也是一样的的。没必要在网络上传递指针。

 

 

(8) Int recv(int fd,void *buf,int len,int flags)

l     MSG_DONTROUTE:不查找表send函数使用的标志,这个标志告诉IP,目的主机在本地网络上,没有必要查找表,这个标志一般用在网络诊断和路由程序里面。

l     MSG_OOB:接受或者发生带外数据表示可以接收和发送带外数据。

l Flags0时,效果与read等同。

(9) Int send(int fd,void *buf,int len,int flags)

    前面的三个参数和readwrite函数是一样的。第四个参数可以是0或者是一下组合:

l     MSG_PEEK:查看数据,并不从系统缓冲区移走数据recv函数使用的标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样在下次读取的时候,依然是一样的内容,一般在有过个进程读写数据的时候使用这个标志。

l     MSG_WAITALL:等待所有数据。 recv函数的使用标志,表示等到所有的信息到达时才返回,使用这个标志的时候,recv返回一直阻塞,直到指定的条件满足时,或者是发生了错误。

l Flags0时与write等同。

(10)Int PASCAL FAR sendto( SOCKET s, const char FAR* buf, int len, int flags,  const struct sockaddr FAR* to, int tolen);

l s:一个标识套接口的描述字。

l  buf:包含待发送数据的缓冲区。

l  lenbuf缓冲区中数据的长度。

l  flags:调用方式标志位。

l  to:(可选)指针,指向目的套接口的地址。

l  tolento所指地址的长度。

sendto()适用于已连接的数据报或流式套接口发送数据。对于数据报类套接口,必需注意发送数据长度不应超过通讯子网的IP包最大长度。IP包最大长度在WSAStartup()调用返回的WSADataiMaxUdpDg元素中。如果数据太长无法自动通过下层协议,则返回WSAEMSGSIZE错误,数据不会被发送。  请注意成功地完成sendto()调用并不意味着数据传送到达。
  sendto()函数主要用于SOCK_DGRAM类型套接口向to参数指定端的套接口发送数据报。对于SOCK_STREAM类型套接口,totolen参数被忽略;这种情况下sendto()等价于send()
  为了发送广播数据(仅适用于SOCK_DGRAM),in参数所含地址应该把特定的IP地址INADDR_BROADCASTwinsock.h中有定义)和终端地址结合起来构造。通常建议一个广播数据报的大小不要大到以致产生碎片,也就是说数据报的数据部分(包括头)不超过512字节。
  如果传送系统的缓冲区空间不够保存需传送的数据,除非套接口处于非阻塞I/O方式,否则sendto()将阻塞。对于非阻塞SOCK_STREAM类型的套接口,实际写的数据数目可能在1到所需大小之间,其值取决于本地和远端主机的缓冲区大小。可用select()调用来确定何时能够进一步发送数据。
  在相关套接口的选项之上,还可通过标志位flag来影响函数的执行方式。也就是说,本函数的语义既取决于套接口的选项也取决于标志位。后者由以下一些值组成:

      意义
MSG_DONTROUTE   指明数据不选径。一个WINDOWS套接口供应商可以忽略此标志;参见2.4节中关于SO_DONTROUTE的讨论。
MSG_OOB     发送带外数据(仅适用于SO_STREAM;参见2.2.3节)。  
返回值:
  若无错误发生,send()返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

(11)int PASCAL FAR recvfrom( SOCKET s, char FAR* buf, int len, int flags,  struct sockaddr FAR* from, int FAR* fromlen);

l   s:标识一个已连接套接口的描述字。

l   buf:接收数据缓冲区。

l   len:缓冲区长度。

l   flags:调用操作方式。

l   from:(可选)指针,指向装有源地址的缓冲区。

l   fromlen:(可选)指针,指向from缓冲区长度值。

注释:
  本函数由于从(已连接)套接口上接收数据,并捕获数据发送源的地址。
  对于SOCK_STREAM类型的套接口,最多可接收缓冲区大小个数据。如果套接口被设置为线内接收带外数据(选项为SO_OOBINLINE),且有带外数据未读入,则返回带外数据。应用程序可通过调用ioctlsocket()SOCATMARK命令来确定是否有带外数据待读入。对于SOCK_STREAM类型套接口,忽略fromfromlen参数。
  对于数据报类套接口,队列中第一个数据报中的数据被解包,但最多不超过缓冲区的大小。如果数据报大于缓冲区,那么缓冲区中只有数据报的前面部分,其他的数据都丢失了,并且recvfrom()函数返回WSAEMSGSIZE错误。
  from非零,且套接口为SOCK_DGRAM类型,则发送数据源的地址被复制到相应的sockaddr结构中。fromlen所指向的值初始化时为这个结构的大小,当调用返回时按实际地址所占的空间进行修改。
  如果没有数据待读,那么除非是非阻塞模式,不然的话套接口将一直等待数据的到来,此时将返回SOCKET_ERROR错误,错误代码是WSAEWOULDBLOCK。用select()WSAAsynSelect()可以获知何时数据到达。
  如果套接口为SOCK_STREAM类型,并且远端优雅地中止了连接,那么recvfrom()一个数据也不读取,立即返回。如果立即被强制中止,那么recv()将以WSAECONNRESET错误失败返回。
  在套接口的所设选项之上,还可用标志位flag来影响函数的执行方式。也就是说,本函数的语义既取决于套接口选项,也取决于标志位参数。标志位可取下列值:
  值 意义
  MSG_PEEK 查看当前数据。数据将被复制到缓冲区中,但并不从输入队列中删除。
  MSG_OOB 处理带外数据(参见2.2.3节具体讨论)。

错误代码:
  WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()
  WSAENETDOWNWINDOWS套接口实现检测到网络子系统失效。
  WSAEFAULTfromlen参数非法;from缓冲区大小无法装入端地址。
  WSAEINTR:阻塞进程被WSACancelBlockingCall()取消。
  WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
  WSAEINVAL:套接口未用bind()进行捆绑。
  WSAENOTCONN:套接口未连接(仅适用于SOCK_STREAM类型)。
  WSAENOTSOCK:描述字不是一个套接口。
  WSAEOPNOTSUPP:指定了MSG_OOB,但套接口不是SOCK_STREAM类型的。
  WSAESHUTDOWN:套接口已被关闭。当一个套接口以02how参数调用shutdown()关闭后,无法再用recv()接收数据。
  WSAEWOULDBLOCK:套接口标识为非阻塞模式,但接收操作会产生阻塞。
  WSAEMSGSIZE:数据报太大无法全部装入缓冲区,故被剪切。
  WSAECONNABORTED:由于超时或其他原因,虚电路失效。
  WSAECONNRESET:远端强制中止了虚电路。 

(12)int close(int fd);

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为readwrite的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

(13)int shutdown(int sockfd,int how);

Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
    SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
    SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作
    SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。
shutdown可直接关闭描述符,不考虑描述符的参考数,可选择中止一个方向的连接。

注意:
    1>. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套

接字将被释放。
    2>. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会

影响到其它进程. 得自己理解引用计数的用法了. Kernel编程知识的更好理解了.

(14)int getsockname(int s, struct sockaddr *name, socklen_t *namelen);

获取本地套接口的名字,包括它的IP和端口。getsockname()在指定的套接口绑定地址和端口后才能调用,即服务器在bind()后可调用,客户端在bind()connect()之后可调用。

 

(15)int getpeername(int s, struct sockaddr *name, socklen_t *namelen);

获取远程套接口的名字,包括它的IP和端口。getpeername()在连接建立之后才可调用。

 

(16)in_addr_t inet_addr(const char *cp);

将字符串形式的IP地址转换为按网络字节顺序的整型值。inet_addr返回的整数形式是网络字节序,而inet_network返回的整数形式是主机字节序他俩都有一个小缺陷,那就是当IP255.255.255.255时,这两个函数会认为这是个无效的IP地址,inet_aton函数和上面这俩小子的区别就是在于他认为255.255.255.255是有效的,他不会冤枉这个看似特殊的IP地址。

 

 

7.WinSock API 

(1)WSAStartup()

连结应用程序与Winsock.DLL 的第一个函数。
式: 
int WSAStartup( WORD wVersionRequested,LPWSADATA lpWSAData )
数:
wVersionRequested 欲使用的 Windows Sockets API 版本
lpWSAData 指向 WSADATA 资料的指标
传回值:
成功 - 0
失败 - WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL
说明: 
此函数「必须」是应用程序呼叫到 Windows Sockets DLL 函数中的第一个函数呼叫成功后,才可以再呼叫其他 Windows Sockets DLL 的函数。此函数亦让使用者可以指定要使用的 Windows Sockets API 版本,及获取设计者的一些信息。

 

WSACleanup()
结束 Windows Sockets DLL 的使用。
式: 
int WSACleanup( void );
数:
传回值: 成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:当应用程序不再需要使用Windows Sockets DLL 时,须呼叫此一函数来
注销使用,以便释放其占用的资源。

(3) closesocket():关闭某一Socket
式: int PASCAL FAR closesocket( SOCKET s );
数: s Socket 的识别码
传回值: 成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明: 此一函式是用来关闭某一 Socket
若是使用者原先对要关闭之 Socket 设定 SO_DONTLINGER,则在呼叫此一函式後,会马上回覆,但是此一 Sokcet 尚未传送完毕的资料会继续送完後才关闭。
若是使用者原先设定此 Socket SO_LINGER,则有两种情况:
(a) Timeout 设为 0 的话,此一 Socket 马上重新设定 (reset),未传完或未收到的资料全部遗失。
(b) Timeout 不为 0 的话,则会将资料送完,或是等到 Timeout 发生後才关闭。

(7) getsockopt():要求某一 Socket 目前状态设定的资料。
格式: int PASCAL FAR getsockopt( SOCKET s,int level,int optname,char FAR *optval,int FAR *optlen );
参数: s= Socket的识别码,level=选项设定的,level=optname 选项名称,optval=选项的设定值,optlen=选项设定值的长度
传回值: 成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明: 此函式用来获取目前 Socket的某些状态设定值。 WINSOCK 提供之 level 只有 SOL_SOCKET IPPROTO_TCP optname则有以下 之选择:(参见WINSOCK 2930 页之定义)
Value Type
----------------------------------------------
SO_ACCEPTCONN BOOL
SO_BROADCAST BOOL
*SO_DEBUG BOOL
SO_DONTLINGER BOOL
*SO_DONTROUTE BOOL
*SO_ERROR int
*SO_KEEPALIVE BOOL
SO_LINGER struct linger FAR*
SO_OOBINLINE BOOL
*SO_RCVBUF int
SO_REUSEADDR BOOL
*SO_SNDBUF int
SO_TYPE int
TCP_NODELAY BOOL
(* 表示暂不提供此功能选项)

(12) ioctlsocket():控制 Socket 的模式。 
格式: int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR *argP ); 
参数: s Socket 的识别码,cmd 指令名称,argP 指向 cmd 参数的指标
传回值: 成功 - 0 
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因
若无错误发生,inet_ntoa()返回一个字符指针。否则的话,返回NULL。其中的数据应在下一个WINDOWS套接口调用前复制出来。
注释:本函数将一个用in参数所表示的Internet地址结构转换成以“.” 间隔的诸如“a.b.c.d”的字符串形式。请注意inet_ntoa()
返回的字符串存放在WINDOWS套接口实现所分配的内存中。应用程序不应假设该内存是如何分配的。在同一个线程的下一个WINDOWS套接口调用前,数据将保证是有效。
说明: 此函式用来获取或设定 Socket 的运作参数。其所提供的指令有: 
FIONBIO -- 开关 non-blocking 模式 
FIONREAD -- Socket 一次可读取的资料量 
SIOCATMARK -- OOB 资料是否已被读取完 (*暂不提供此功能)

(13) listen():设定 Socket 为监听状态,准备被连接。 
式: int PASCAL FAR listen( SOCKET s, int backlog ); 
数: s Socket 的识别码,backlog 未真正完成连接前(尚未呼叫 accept() )彼端的连接要求的最大个数 
传回值: 成功 - 0 
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因
说明: 使用者可利用此函式来设定 Socket 进入监听状态,并设定最多可有多少个在未真正完成连接前的彼端的连接要求。(目前最大值限制为 5, 最小值为1)

格式: int PASCAL FAR setsockopt( SOCKET s,int level,int optname,const char FAR *optval,int optlen );
参数: s Socket 的识别码,level 选项设定的 level,optname 选项名称,optval 选项的设定值,optlen 选项设定值的长度
传回值: 成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因
说明: 此函式用来设定 Socket 的一些选项,藉以更改其动作。 
可更改的选项有: (参见WINSOCK1.154
Value Type 
----------------------------------------------- 
SO_BROADCAST BOOL 
*SO_DEBUG BOOL 
SO_DONTLINGER BOOL 
*SO_DONTROUTE BOOL 
*SO_KEEPALIVE BOOL 
SO_LINGER struct linger FAR* 
SO_OOBINLINE BOOL 
*SO_RCVBUF int 
SO_REUSEADDR BOOL 
*SO_SNDBUF int 
TCP_NODELAY BOOL

(22) shutdown():停止 Socket 接收/传送的功能。 
格式: int PASCAL FAR shutdown( SOCKET s, int how ); 
参数: s Socket 的识别码,how 代表该停止那些动作的标帜 
传回值: 成功 - 0 
失败 - SOCKET_ERROR (呼叫 WSAGetLastError()可得知原因
说明: 此函式用来停止 Socket 的後续接收或传送的功能。 
how 的值为 0,则不再接收资料。 
how 的值为 1,则不再允许传送资料。 
how 的值为 2,则不再接收且不再传送资料。 
shutdown() 函式并没有将 Socket 关闭,所以该 Socket 所占用之资源必须在呼叫closesocket() 之後才会释放。

(24) gethostbyaddr():利用某一 host 的位址来获取该 host 的资料。 
格式: struct hostent FAR * PASCAL FAR gethostbyaddr( const char FAR *addr, int len, int type );
参数: addr network 排列方式的位址,len addr 的长度,type PF_INET(AF_INET)
传回值: 成功 - 指向 struct hostent 的指标 
struct hostent

char FAR * h_name; 
char FAR * FAR * h_aliases; 
short h_addrtype; 
short h_length; 
char FAR * FAR * h_addr_list; 

失败 - NULL (呼叫 WSAGetLastError() 可得知原因
说明: 此函式是利用位址来获取 host的其他资料,如 host 的名称、别名,位址的型态、长度等。

(25) gethostbyname():利用某一 host 的名称来获取该 host 的资料。 
格式: struct hostent FAR * PASCAL FAR gethostbyname( const char FAR *name ); 
参数: name host 的名称
传回值: 成功 - 指向 struct hostent 的指标 
struct hostent

char FAR * h_name; 
char FAR * FAR * h_aliases; 
short h_addrtype; 
short h_length; 
char FAR * FAR * h_addr_list; 

失败 - NULL (呼叫 WSAGetLastError() 可得知原因
说明: 此函式是利用 host 名称来获取其他的资料,如 host 的位址、别名,位址的型态、长度等。

(26) gethostname():获取目前使用者使用的 host 的名称。 
格式: int PASCAL FAR gethostname( char FAR *name, int namelen ); 
参数: name 用来存放 host 名称的暂存区,namelen name 的大小 
传回值: 成功 - 0 
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因
说明: 此函式用来获取 host 的名称。

(27) getprotobyname():依照通讯协定 (protocol) 的名称来获取该通讯协定的其他资料。 
格式: struct protoent FAR * PASCAL FAR getprotobyname( const char FAR *name ); 
参数: name 通讯协定名称
传回值: 成功 - 一指向 struct protoent 的指标 
struct protoent 

char FAR * p_name; 
char FAR * FAR * p_aliases; 
short p_proto; 

失败 - NULL (呼叫 WSAGetLastError() 可得知原因
说明: 利用通讯协定的名称来得知该通讯协定的别名、编号等资料。

(28) getprotobynumber():依照通讯协定的编号来获取该通讯协定的其他资料。 
格式: struct protoent FAR * PASCAL FAR getprotobynumber( int number ); 
参数: number host 排列方式的通讯协定编号
传回值: 成功 - 一指向 struct protoent 的指标

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值