ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

无处不在的网络编程,到底是如何工作的?今天我们一探究竟!

2021-05-22 15:03:50  阅读:212  来源: 互联网

标签:一探 socket 编程 无处不在 内核 服务器 连接 客户端


一、引言

不知道大家有没有这样的经历,上网搜索技术文章,总是会看到网络编程这个字眼,而各个互联网大厂,也对掌握了网络编程的人才,求贤若渴。

其实网络编程无处不在,我们平时用到的互联网产品和网络编程技术息息相关。

掌握网络编程,才能在繁杂的网络世界中,看透问题本质,遇到网络相关技术问题,也才能解决得游刃有余。

 

二、目录


 

三、网络协议栈

那什么是网络编程呢?有人说http就是网络编程,有人说开发RPC框架是网络编程,有人说嵌入式硬件相互通信是网络编程,其实这些都涉及网络编程,都脱离不了网络协议栈。

在大学课本《计算机网络》中,我们学过网络协议栈是OSI七层。现实中,大多数操作系统,实现的是TCP/IP四层协议栈,如图所示:


 

而网络编程就是在操作系统封装的TCP/IP协议栈的基础上,使用系统内核暴露出来的socket网络编程Api,进行应用程序开发。

 

四、echo回显服务器代码


 

=============

#编译echo回显服务器

go build -o echoServer echoServer.go

# 启动服务器

./echoServer

=============

使用nc,伪装echoClient客户端

=============

# 和服务器建立连接

nc 127.0.0.1 8888

# 客户端

hello-echo

# 服务端

hello-echo

=============

这时候,我们通过nc给服务器发送字符串,服务器会原样把字符串回传给我们。

nc和echoServer整个交互过程是怎样的呢?下图展示详细过程!

 

五、网络交互图


 

 

六、过程详解

众所周知,TCP会有 3次握手 和 4次挥手,我们的echo服务器就是基于TCP协议的。当然少不了3次握手和4次挥手。

服务器 | 建立socket内核数据结构

✿ 首先,我们需要创建socket内核数据结构,通过socket()系统调用,我们可以告诉内核我们要建立基于ipv4的tcpsocket套接字,内核会维护一个sock数据结构并和一个文件相绑定,同时给我们返回一个socketfd,供后续函数使用。

✿ 之后,我们需要通过bind(),告知内核将哪个地址绑定到该socket内核数据结构。

✿ 然后,使用listen()系统调用,将socket转换为已监听套接字。此时,服务器就可以进行被动连接了。

客户端 | 发起主动连接;服务器 | 被动连接

✿ 如果此时客户端通过connect()系统调用发起主动连接,客户端内核协议栈,向服务器发送三次握手的第一步SYN。

✿ 当服务器收到这个SYN,会把该套接字放入半连接队列,并向客户端发送ACK、SYN。

✿ 当客户端接受到这个ACK、SYN,并向服务器发送ACK。此时客户端connect()系统调用返回,客户端认为三次握手完成。

✿ 当服务器收到客户端传来的ACK,则将内核中的套接字放入全连接队列,等待服务器调用accept,并返回给服务器。

服务器 | 等待已连接套接字

✿ 当服务器调用accept(), 如果此时全连接队列中没有已完成三次握手的socket,则默认会阻塞,直到全连接队列中拥有已经完成三次握手的socket。

✿ accept()会为之前的监听套接字sock内核数据结构,构建一个新的文件,并分配新的fd,这个文件称为已连接套接字。

✿ 此时,服务器和客户端就可以收发数据了。

服务器 | 客户端 | 收发数据

收发数据为何需要调用read和write呢?

✿ 在内核中,看到的acceptfd(已连接套接字),本质和文件一样,acceptfd就是文件描述符,所以我们可以直接使用read、write这种操作文件的系统调用。

✿ linux内核为每个已连接套接字,分配一个接受缓冲区和一个发送缓冲区。

✿ 对于read(),是本机读取acceptfd(服务器)或者socketfd(客户端)相关的socket接收缓冲区,如果socket接收缓冲区中有数据,read()返回,否则read()会阻塞,直到socket接收缓冲区中拥有了对端数据。这个接受数据的过程是由内核TCP/IP协议栈实现的。

✿ 对于write(),写入的是socket发送缓冲区,如果此时发送缓冲区是满的,write()则会被阻塞。写入socket发送缓冲区的数据,由内核TCP/IP协议栈真实的发往对端。

=====================

这里还有几个点:

▶ read返回大于0,表示读取成功。

▶ read返回等于0,表示对端关闭连接,此时应该调用Close关闭连接。

▶ read返回小于0,表示读操作产生错误。

▶ write返回小于0,表示写操作产生错误。

=====================

客户端 | 服务器 | 关闭连接

✿ 在nc所在的终端上键入Ctrl+c,结束掉nc进程。此时内核协议栈,会给服务器发送FINTcp节,告知本端已经关闭。

✿ 服务端收到客户端的FIN,内核协议栈会给客户端回复ACK,同时read()调用会返回0,这样服务器就知道客户端关闭连接了。

✿ 这时候,服务器应该调用close(),开启四次挥手的第二个阶段,向客服端发送一个FIN。

✿ 当客户端收到该FIN后,内核协议栈回复ACK,自身并进入TIME_WAIT状态,Linux下等待2MSL, 也就是60秒。

====================

至此,整个echoServer和nc的交互过程就讲解完了。整个过程都是正常的网络交互过程,很多异常的边界没有讨论。

 

七、网络答疑

假如一方断网了,不能进行4次挥手,服务器会怎么处理?

✿ 默认未断开一端,会保持established状态。

✿ 未断开一端开启了Keepalive,则会定期往对端发送TCP保活Segment,对端协议栈回复RST,未断开端就会知道已经出现异常,关闭本端连接。

✿ 如果未断开一端没有开启Kepalive,则一直不知道对端已经关闭,直到往对端写数据会得到Connection reset by peer错误,进而知道对端已经关闭。这种情况下,如果未断开端再次往断开对端写数据,则会产生EPIPE错误。

✿ 所以通常需要服务器在应用层做保活心跳,对这种情况的连接做定时踢掉处理。

客户端发送FIN之后,会发生什么?

✿ TCP是全双工通信,也就是双通道通信4次握手中,主动关闭端(客户端)发送FIN,是告诉被动关闭端(服务器)我不会再给你发送数据了,你不要再在acceptFd上读取数据了,此时服务端再读取数据,会返回0,也就是EOF。

✿ 如果服务端不需要发送数据给客户端,通常需要调用close,服务端向客户端发送FIN,也就是4次握手的第3步。

✿ 如果服务端有数据要发送给客户端,此时依旧可以通过socketfd发送数据给客户端,这也是通常说的TCP半关闭状态。

 

八、后记

通过之前的讲解,我们其实可以发现:TCP/IP协议和socketApi是紧密相关的。然而它们又有很多语义的差别。

很多时候,想当然的理解,并不是你所想的那样。比如close()系统调用,会不会给对端发送FIN,要看该已连接套接字的引用计数是否达到0。

如果有多个进程共享这个文件,其中一个进程close(),并不会给对端发送FINTcp节。诸如此类的细节还有很多很多,由此可见,学好网络编程也是一门不简单的学问。


 

不管你是转行也好,初学也罢,进阶也可,如果你想学编程,进阶程序员~

【值得关注】我的 编程学习交流俱乐部 !【点击进入】

C语言入门资料:


 

C语言必读书籍:


 

标签:一探,socket,编程,无处不在,内核,服务器,连接,客户端
来源: https://www.cnblogs.com/huya-edu/p/14798863.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有