C++ Socket 学习笔记.docx
《C++ Socket 学习笔记.docx》由会员分享,可在线阅读,更多相关《C++ Socket 学习笔记.docx(28页珍藏版)》请在冰豆网上搜索。
C++Socket学习笔记
C++Socket学习笔记
IPAddress
IP地址是指互联网协议地址(英语:
InternetProtocolAddress,又译为网际协议地址),是IPAddress的缩写。
IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
IP地址被用来给Internet上的电脑一个编号。
大家日常见到的情况是每台联网的PC上都需要有IP地址,才能正常通信。
我们可以把“个人电脑”比作“一台电话”,那么“IP地址”就相当于“电话号码”,而Internet中的路由器,就相当于电信局的“程控式交换机”。
IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。
IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。
例:
点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。
每台计算机都有自己独一无二的IP地址,根据IP地址判断与哪台计算机进行通信。
Port
一台计算机可以同时提供多种网络服务,例如Web服务、FTP服务(文件传输服务)、SMTP服务(邮箱服务)等,仅有IP地址,计算机虽然可以正确接收到数据包,但是却不知道要将数据包交给哪个网络程序来处理,所以通信失败。
为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(PortNumber),例如,Web服务的端口号是80,FTP服务的端口号是21,SMTP服务的端口号是25。
端口(Port)是一个虚拟的、逻辑上的概念。
可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。
Protocol
Protocol为进行网络中的数据交换而建立的规则、标准或约定。
用于不同系统中实体间的通信。
两个实体要想通信,必须有“同一种语言”,而且,对于通信内容,怎样通信和何时通信,都必须遵守一定的规定,这些规定就是协议。
亦可简单地定义为:
控制两实体间数据交换的一套规则。
在电子通讯连接中,各个不同的层次都有自己的协议。
协议仅仅是一种规范,实现由计算机软件来完成,常见的协议TCP/IP协议:
是目前世界上应用最为广泛的协议,TCP/IP协议是传输层的协议,HTTP超文本传输协议,FTP文件传输协议,SMTP简单邮件传送协议,Telnet远程登录服务是应用层协议。
TCP/IP、Http、Socket的区别
网络由下往上分为:
物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,三者从本质上来说没有可比性,socket则是对TCP/IP协议的封装和应用(程序员层面上)。
也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。
简单的来说,当应用程序产生数据需要传输时,首先使用HTTP协议封装成文本信息,然后使用TCP/IP协议进行网络上传输,而Socket则是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。
通过Socket,我们才能使用TCP/IP协议。
从而形成了一些最基本的函数接口,比如create、listen、connect、accept、send、read和write等等。
TCP是什么?
具体的关于TCP是什么,我不打算详细的说了;当你看到这篇文章时,我想你也知道TCP的概念了,想要更深入的了解TCP的工作,我们就继续。
它只是一个超级麻烦的协议,而它又是互联网的基础,也是每个程序员必备的基本功。
首先来看看OSI的七层模型:
我们需要知道TCP工作在网络OSI的七层模型中的第四层——Transport层,IP在第三层——Network层,ARP在第二层——DataLink层;在第二层上的数据,我们把它叫Frame,在第三层上的数据叫Packet,第四层的数据叫Segment。
同时,我们需要简单的知道,数据从应用层发下来,会在每一层都会加上头部信息,进行封装,然后再发送到数据接收端。
这个基本的流程你需要知道,就是每个数据都会经过数据的封装和解封装的过程。
在OSI七层模型中,每一层的作用和对应的协议如下:
TCP是一个协议,那这个协议是如何定义的,它的数据格式是什么样子的呢?
要进行更深层次的剖析,就需要了解,甚至是熟记TCP协议中每个字段的含义。
上面就是TCP协议头部的格式,由于它太重要了,是理解其它内容的基础,下面就将每个字段的信息都详细的说明一下。
SourcePort和DestinationPort:
分别占用16位,表示源端口号和目的端口号;用于区别主机中的不同进程,而IP地址是用来区分不同的主机的,源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接;
SequenceNumber:
用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号;主要用来解决网络报乱序的问题;
AcknowledgmentNumber:
32位确认序列号包含发送确认的一端所期望收到的下一个序号,因此,确认序号应当是上次已成功收到数据字节序号加1。
不过,只有当标志位中的ACK标志(下面介绍)为1时该确认序列号的字段才有效。
主要用来解决不丢包的问题;
Offset:
给出首部中32bit字的数目,需要这个值是因为任选字段的长度是可变的。
这个字段占4bit(最多能表示15个32bit的的字,即4*15=60个字节的首部长度),因此TCP最多有60字节的首部。
然而,没有任选字段,正常的长度是20字节;
TCPFlags:
TCP首部中有6个标志比特,它们中的多个可同时被设置为1,主要是用于操控TCP的状态机的,依次为URG,ACK,PSH,RST,SYN,FIN。
每个标志位的意思如下:
URG:
此标志表示TCP包的紧急指针域(后面马上就要说到)有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据;
ACK:
此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:
0和1,为1的时候表示应答域有效,反之为0;
PSH:
这个标志位表示Push操作。
所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队;
RST:
这个标志表示连接复位请求。
用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包;
SYN:
表示同步序号,用来建立连接。
SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0;连接被响应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。
扫描者发送一个只有SYN的数据包,如果对方主机响应了一个数据包回来,就表明这台主机存在这个端口;但是由于这种扫描方式只是进行TCP三次握手的第一次握手,因此这种扫描的成功表示被扫描的机器不很安全,一台安全的主机将会强制要求一个连接严格的进行TCP的三次握手;
FIN:
表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。
这个标志的数据包也经常被用于进行端口扫描。
Window:
窗口大小,也就是有名的滑动窗口,用来进行流量控制;这是一个复杂的问题,这篇博文中并不会进行总结的;
TCP连接和断开
三次握手
第一次握手:
建立连接。
客户端发送连接请求报文段,将SYN位置为1,SequenceNumber为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手:
服务器收到SYN报文段。
服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置AcknowledgmentNumber为x+1(SequenceNumber+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,SequenceNumber为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手:
客户端收到服务器的SYN+ACK报文段。
然后将AcknowledgmentNumber设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
既然总结了TCP的三次握手,那为什么非要三次呢?
怎么觉得两次就可以完成了。
那TCP为什么非要进行三次连接呢?
在谢希仁的《计算机网络》中是这样说的:
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
在书中同时举了一个例子,如下:
“已失效的连接请求报文段”的产生在这样一种情况下:
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。
本来这是一个早已失效的报文段。
但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。
于是就向client发出确认报文段,同意建立连接。
假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。
由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。
但server却以为新的运输连接已经建立,并一直等待client发来数据。
这样,server的很多资源就白白浪费掉了。
采用“三次握手”的办法可以防止上述现象发生。
例如刚才那种情况,client不会向server的确认发出确认。
server由于收不到确认,就知道client并没有要求建立连接。
”
四次分手
第一次分手:
主机1(可以使客户端,也可以是服务器端),设置SequenceNumber和AcknowledgmentNumber,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
第二次分手:
主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,AcknowledgmentNumber为SequenceNumber加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
第三次分手:
主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
第四次分手:
主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
那四次分手又是为何呢?
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。
TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。
出处
socket函数
套接字是通信端点的抽象,实现端对端之间的通信。
与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。
任何套接字编程都必须调用socket函数获得套接字描述符,这样才能对套接字进行操作。
以下是该函数的描述:
/*套接字*/
/*
*函数功能:
创建套接字描述符;
*返回值:
若成功则返回套接字非负描述符,若出错返回-1;
*函数原型:
*/
#include
intsocket(intfamily,inttype,intprotocol);
/*
*说明:
*socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;
*family表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:
*
(1)AF_INETIPv4因特网域
*
(2)AF_INET6IPv6因特网域
*(3)AF_UNIXUnix域
*(4)AF_ROUTE路由套接字
*(5)AF_KEY密钥套接字
*(6)AF_UNSPEC未指定
*
*type确定socket的类型,常用类型如下:
*
(1)SOCK_STREAM有序、可靠、双向的面向连接字节流套接字
*
(2)SOCK_DGRAM长度固定的、无连接的不可靠数据报套接字
*(3)SOCK_RAW原始套接字
*(4)SOCK_SEQPACKET长度固定、有序、可靠的面向连接的有序分组套接字
*
*protocol指定协议,常用取值如下:
*
(1)0选择type类型对应的默认协议
*
(2)IPPROTO_TCPTCP传输协议
*(3)IPPROTO_UDPUDP传输协议
*(4)IPPROTO_SCTPSCTP传输协议
*(5)IPPROTO_TIPCTIPC传输协议
*
*/
connect函数
在处理面向连接的网络服务时,例如TCP,交换数据之前必须在请求的进程套接字和提供服务的进程套接字之间建立连接。
TCP客户端可以调用函数connect来建立与TCP服务器端的一个连接。
该函数的描述如下:
/*
*函数功能:
建立连接,即客户端使用该函数来建立与服务器的连接;
*返回值:
若成功则返回0,出错则返回-1;
*函数原型:
*/
#include
intconnect(intsockfd,conststructsockaddr*servaddr,socklen_taddrlen);
/*
*说明:
*sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符;
*servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址;
*addrlen是目的套接字地址的大小;
*
*如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号;
*/
TCP客户端在调用函数connect前不必非得调用bind函数,因为内核会确定源IP地址,并选择一个临时端口作为源端口号。
若TCP套接字调用connect函数将建立TCP连接(执行三次握手),而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
若TCP客户端没有收到SYN报文段的响应,则返回ETIMEOUT错误;
若客户端的SYN报文段的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。
只是一种硬错误,客户端一接收到RST就立即返回ECONNERFUSED错误;RST是TCP在发生错误时发送的一种TCP报文段。
产生RST的三个条件时:
目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;
TCP想取消一个已有连接;
TCP接收到一个不存在的连接上的报文段;
若客户端发出的SYN在中某个路由器上引发一个目的地不可达的ICMP错误,这是一个软错误。
客户端主机内核保存该消息,并在一定的时间间隔继续发送SYN(即重发)。
在某规定的时间后仍未收到响应,则把保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进行。
bind函数
调用函数socket创建套接字描述符时,该套接字描述符是存储在它的协议族空间中,没有具体的地址,要使它与一个地址相关联,可以调用函数bind使其与地址绑定。
客户端的套接字关联的地址一般可由系统默认分配,因此不需要指定具体的地址。
若要为服务器端套接字绑定地址,可以通过调用函数bind将套接字绑定到一个地址。
下面是该函数的描述:
/*套接字的基本操作*/
/*
*函数功能:
将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号;
*返回值:
若成功则返回0,若出错则返回-1;
*函数原型:
*/
#include
intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);
/*
*说明:
*sockfd为套接字描述符;
*addr是一个指向特定协议地址结构的指针;
*addrlen是地址结构的长度;
*/
对于TCP协议,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
若TCP客户端或服务器端不调用bind函数绑定一个端口号,当调用connect或listen函数时,内核会为相应的套接字选择一个临时端口号。
一般TCP客户端使用内核为其选择一个临时的端口号,而服务器端通过调用bind函数将端口号与相应的套接字绑定。
进程可以把一个特定的IP地址捆绑到它的套接字上,但是这个IP地址必须属于其所在主机的网络接口之一。
对于TCP客户端,这就为在套接字上发送的IP数据报指派了源IP地址。
对于TCP服务器端,这就限定该套接字只接收那些目的地为这个IP地址的客户端连接。
TCP客户端一般不把IP地址捆绑到它的套接字上。
当连接套接字时,内核将根据所用外出网络接口来选择源IP地址,而所用外出接口则取决于到达服务器端所需的路径。
若TCP服务器端没有把IP地址捆绑到它的套接字上,内核就把客户端发送的SYN的目的IP地址作为服务器端的源IP地址。
在地址使用方面有下面一些限制:
在进程所运行的机器上,指定的地址必须有效,不能指定其他机器的地址;
地址必须和创建套接字时的地址族所支持的格式相匹配;
端口号必须不小于1024,除非该进程具有相应的特权(超级用户);
一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定;
listen函数
在编写服务器程序时需要使用监听函数listen。
服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。
listen函数描述如下:
/*
*函数功能:
接收连接请求;
*函数原型:
*/
#include
intlisten(intsockfd,intbacklog);//若成功则返回0,若出错则返回-1;
/*
*sockfd是套接字描述符;
*backlog是该进程所要入队请求的最大请求数量;
*/
listen函数仅由TCP服务器调用,它有以下两种作用:
当socket函数创建一个套接字时,若它被假设为一个主动套接字,即它是一个将调用connect发起连接的客户端套接字。
listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求;
listen函数的第二个参数规定内核应该为相应套接字排队的最大连接个数;
listen函数一般应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的SYN报文段对应其中一项:
已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。
这些套接字处于SYN_REVD状态;
已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项。
这些套接字处于ESTABLISHED状态;
accept函数
accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。
如果已完成连接队列为空,那么进程被投入睡眠。
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。
一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。
内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。
该函数描述如下:
/*函数功能:
从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠;
*函数原型:
*/
intaccept(intsockfd,structsockaddr*cliaddr,socklen_t*addrlen);//返回值:
若成功返回套接字描述符,出错返回-1;
/*
*说明:
*参数cliaddr和addrlen用来返回已连接的对端(客户端)的协议地址;
*
*该函数返回套接字描述符,该描述符连接到调用connect函数的客户端;
*这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接,
*而是继续保持可用状态并接受其他连接请求;
*若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址,
*并且将addrlen设为指向代表这个缓冲区大小的整数指针;
*accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小;
*
*若没有连接请求等待处理,accept会阻塞直到一个请求到来;
*/
fork和exec函数
/*函数功能:
创建子进程;
*返回值:
*
(1)在子进程中,返回0;
*
(2)在父进程中,返回新创建子进程的进程ID;
*(3)若出错,则范回-1;
*函数原型:
*/
#include
pid_tfork(void);
/*说明:
*该函数调用一次若成功则返回两个值:
*在调用进程(即父进程)中,返回新创建进程(即子进程)的进程ID;
*在子进程返回值是0;
*因此,可以根据返回值判断进程是子进程还是父进程;
*/
/*exec序列函数*/
/*
*函数功能:
把当前进程替换为一个新的进程,新进程与原进程ID相同;
*返回值:
若出错则返回-1,