网络编程笔记

一、基本概念

socket:

翻译为插座,在网络编程中引申为“网络数据传输用的软件设备”。创建一个socket可以类比为安装一部电话机(都是通信用的)。

C/S模式:

  1. 即客户机和服务器模式,该模式在操作中采取主动请求的方式;
  2. 服务器端:
    2.1. 首先服务器启动,并根据请求提供相应的服务;
    2.2. 打开一个通信通道,在某一地址和端口上接收请求;
    2.3. 等待客户端请求到达该端口;
    2.4. 接收到重复服务请求,处理该请求并发送应答信号;
    2.5. 返回第2步,继续等待其他客户端请求;
    2.6. 关闭服务器;
  3. 客户端:
    3.1. 打开一个通信通道,连接到服务器主机的指定端口;
    3.2. 向服务器发送服务请求,等待并接收应答,继续发送请求;
    3.3. 请求结束后关闭通信通道,并终止;

面向连接与面向消息:

  1. 即TCP与UDP;
  2. 面向连接的套接字(TCP):
    2.1. 传输过程数据不会丢失;
    2.2. 传输过程会按照顺序传输;
    2.3. 传输过程不存在数据边界;
  3. 面向消息的套接字(UDP):
    3.1. 强调快速传输,而并非顺序传输;
    3.2. 传输的数据可能丢失,也可能被破坏;
    3.3. 限制每次传输数据的大小;
    3.4. 传输过程存在数据边界;

基本函数:

在这里插入图片描述

基本数据结构:

struct sockaddr_in {
    short   sin_family;        // 2字节 地址类型 IPV4还是IPV6
    u_short sin_port;          // 2字节 端口号,最大为65535
    struct  in_addr sin_addr;  // 4字节 IP地址
    char    sin_zero[8];       // 8字节 填充 
};

二、流程

TCP基本流程:

在这里插入图片描述
服务端和客户端初始化 socket,得到文件描述符;
服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口;
服务端调用 listen,进行监听;
服务端调用 accept,等待客户端连接;
客户端调用 connect,向服务端端的地址和端口发起连接请求;
服务端 accept 返回用于传输的 socket 的文件描述符;
客户端调用 write 写入数据;服务端调用 read 读取数据;
客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据

所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

UDP基本流程:

在这里插入图片描述

三、示例

例子代码,很简单的一个连接通话流程,有助理解:

TCPDemo:
链接:https://pan.baidu.com/s/19sCHawMUcTljK17UTJUXHA
提取码:ppnh

UDPDemo:
链接:https://pan.baidu.com/s/10A7YoutbKU1tFjuE2QJNZQ
提取码:1blt

四、其他

listen参数意义:

TCP的流程中有一步为listen,共有两个参数,分别为服务器套接字,以及最大监听的队列大小。

listen(
    _In_ SOCKET s,
    _In_ int backlog
);

主要说下第二个参数的意义:从listen开始到accept之间,最大监听数量为n个,即这段时间内,从第n+1个客户端开始是会连接错误的,错误码10061,而监听队列中的n个是不会有影响的 直到队列中的n个开始被陆续的处理,才可以空出位置给后面的客户端。虽然这样,但并不是n越大越好。

大数据的发送与接收:

在传输数据的过程中,肯定会有大数据传输的情况,这种情况该如何处理?上代码:

int MySocketRecv(int iSockFrom, char* buffer, int iDataLen)
{
	int iReadedLen = 0; // 目前已经收到的数据大小 
	int iRemainingLen = iDataLen; // 剩余要接收的数据大小 
	while (1)
	{
		// 从目前已经收完的地方继续向后读取,读取大小为剩余未接收数据的大小,返回实际读取出数据的大小 
		int iCurReadLen = recv(iSockFrom, &buffer[iReadedLen], iRemainingLen, 0);
		printf("本次读取数据大小:[%d],共读取数据大小:[%d],剩余未读取数据大小:[%d]\n",
			iCurReadLen, iReadedLen + iCurReadLen, iRemainingLen - iCurReadLen);
		if (iCurReadLen == iRemainingLen)
		{
			printf("数据全部接收完成\n");
			return 0;
		}
		else if (iCurReadLen > 0)
		{
			iReadedLen += iCurReadLen;// 累加上读取出来的大小 
			iRemainingLen -= iCurReadLen;// 累减去读取出来的大小 
			continue;// 进入下一次读取 
		}
		else if (iCurReadLen < 0 && errno == 11)
			continue;
		else
			return -1;
	}
}

int MySocketSend(int iSockTo, char* buffer, unsigned iLen)
{
	unsigned iSendedLen = 0;// 目前已发送数据大小 
	unsigned iRemainingLen = iLen;// 剩余未发送数据大小 
	while (1)
	{
		// 从目前已经发送的地方继续向后发送,发送大小为剩余未发送数据的大小,返回实际发送数据的大小 
		int iCurSendLen = send(iSockTo, &buffer[iSendedLen], iRemainingLen, 0);
		printf("本次发送数据大小:[%d],共发送数据大小:[%d],剩余未发送数据大小:[%d]\n",
			iCurSendLen, iSendedLen + iCurSendLen, iRemainingLen - iCurSendLen);
		if (iCurSendLen == iRemainingLen)
		{
			printf("数据全部发送完成\n");
			return 0;
		}
		else if (iCurSendLen > 0)
		{
			iSendedLen += iCurSendLen;
			iRemainingLen -= iCurSendLen;
			continue;
		}
		else if (iCurSendLen < 0 && errno == 11)
			continue;
		else
			return -1;
	}
}

即采用循环发送/接收的思想。

五、TCP三次握手

图示

在这里插入图片描述

TCP首部格式

在这里插入图片描述
源端口:占16比特,写入源端口号,用来标识发送该TCP报文段的应用进程。
目的端口:占16比特,写入目的端口号,用来标识接收该TCP报文段的应用进程。
序号:占32比特,取值范围[0,2^32-1],序号增加到最后一个后,下一个序号就又回到0。指出本TCP报文段数据载荷的第一个字节的序号。
确认号:占32比特,取值范围[0,2^32-1],确认号增加到最后一个后,下一个确认号就又回到0。指出期望收到对方下一个TCP报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。若确认号=n,则表明到序号n-1为止的所有数据都已正确接收,期望接收序号为n的数据。
数据偏移:占4比特,并以4字节为单位。用来指出TCP报文段的数据载荷部分的起始处距离TCP报文段的起始处有多远。这个字段实际上是指出了TCP报文段的首部长度。
窗口: 占16比特,以字节为单位。指出发送本报文段的一方的接收窗。
确认标志位ACK: 取值为1时确认号字段才有效;取值为0时确认号字段无效。TCP规定,在连接建立后所有传送的TCP报文段都必须把ACK置1。
同步标志位SYN: 在TCP连接建立时用来同步序号。
终止标志位FIN: 用来释放TCP连接。
复位标志位RST: 用来复位TCP连接。
推送标志位PSH: 接收方的TCP收到该标志位为1的报文段会尽快上交应用进程,而不必等到接收缓存都填满后再向上交付。
校验和: 占16比特,检查范围包括TCP报文段的首部和数据载荷两部分。在计算校验和时,要在TCP报文段的前面加上12字节的伪首部。
紧急指针: 占16比特,以字节为单位,用来指明紧急数据的长度。
填充: 由于选项的长度可变,因此使用填充来 确保报文段首部能被4整除,(因为数据偏移字段,也就是首部长度字段,是以4字节为单位的)。

为什么需要三次握手

使TCP双方能够确知对方的存在
使TCP双方能够协商一些参数( 最大窗口值是否使用窗口扩大选项和时间戳选项,以及服务质量等)
使TCP双方能够对运输实体资源(例如缓存大小连接表中的项目等)进行分配

为什么不是两次或四次握手

1、三次握手可以验证双方的接收和发送能力;
2、三次握手可以阻止重复历史连接的初始化;
客户端连续发送多次 SYN (都是同一个四元组)建立连接的报文,在网络拥堵情况下:
一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是x+1。
客户端收到后,发现自己期望收到的确认号应该是 m+1,而不是 x+1,于是就会回 RST 报文。
服务端收到 RST 报文后,就会释放连接。
后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
上述中的「旧 SYN 报文」称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止「历史连接」初始化了连接。

而如果是两次握手的话,服务端没有中间状态给客户端来阻止历史连接,收到一个客户端的SYN报文就会分配资源建立连接,此时便可以向客户端发送数据了,而此时的客户端还没有进入ESTABLISHED 状态,当客户端发现确认号错误时回复RST报文,又断开连接,导致资源浪费。
3、三次握手可以同步双方的初始序列号;
首先,序列号的作用是:接收方可以去除重复数据、接收方保证数据包按序接收。因为TCP是稳定的连接,它必须要确保双方的初始序列号能可靠的同步,而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
4、三次握手可以避免资源浪费;
5、小结:

两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

六、TCP四次挥手

图示

在这里插入图片描述

为什么需要四次挥手,而不是三次

TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。

这就意味着,关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了,但是客户端还能接收服务端的数据。
服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,但此时服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手。

简单地说,前 2 次挥手用于关闭一个方向的数据通道,后两次挥手用于关闭另外一个方向的数据通道。

2MSL是什么,为什么要有这个延迟等待

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。
可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值