网络编程Day2-socket原理

前言

之前我们在 计算机网络系列中提到过,应用都是依靠传输层协议进行通信的,并且,不同的网络应用使用端口号进行区分。我们知道IP层的ip地址可以唯一标识主机,而TCP协议和端口号可以唯一标示主机的一个进程。本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了。那么,Socket(套接字)就诞生了。

socket套接字 把传输层以下的低层实现抽象化了,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。



套接字介绍

套接字的类型:

  • 套接字有两种(或者称为有两个种族),分别是基于文件型的UNIX和基于网络型的INET。
  • 我们主要讲的是 AF_INET ,这种基于网络类型的套接字

收发消息的原理

应用之间互相发送数据,并不是我们想象的那样,直接发送到对端的应用上,而是通过网卡发送到对方的缓存,再通过OS进行调用获得的。如下图…(画的丑不要介意)

  • 客户端和服务端不是对接性、直接的往对方发送消息,而是有经过缓存的中转(应用程序产生的数据拷贝给缓存)。
  • 操作系统如何控制发送数据是按照我们定义的传输层协议(UDP\TCP)进行管理的。

socket建立连接

服务器端:

1
2
3
4
5
6
7
8
9
10
11
12
13
import socket
# 实例化,以及注明是哪个套接字类型、基于TCP的流
tele_phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 实例化一个套接字对象,绑定、监听都是基于socket对象进行操作的

tele_phone.bind(("127.0.10.1",8080)) # 绑定所监听的地址以及端口号(需要在本机网卡上存在的地址)
tele_phone.listen(5) # 最多可以跟5个客户机保持连接

print("执行到我啦")
conn,addr =tele_phone.accept() # 等客户端的链接地址,当对面使用connect,就建立起连接了

# 注意,收发消息是基于conn连接的
conn.send("hello world".encode('utf-8')) # 发送数据,基于网络通信,须以二进制传输
conn.recv().decode('utf-8') # 等待对方发送数据,编码格式要保持一致
函数 作用
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听,listen对应backlog池,当有多个链接时,服务端对将收到的链接放到backlog池中,链接池里面的链接会等待着与服务器建立链接
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)

客户端:

1
2
3
4
5
6
import socket
phone = socket.socket() # 实例化一个套接字对象
phone.connect(("127.0.0.1",8080)) # 连接服务器绑定的IP地址

phone.recv().decode('utf-8')
phone.send("OK".encode('utf-8'))

通过上面这些代码,我们就已经建立起了socket连接,然后就可以通过 send、recv 函数来完成收发信息的操作了。


粘包现象

从上面我们知道了,数据的交互是通过缓存区的,那么粘包就很好理解了:上次执行的命令,没有全部显示完,还停留在缓存区,等下次执行别的命令时,这时候,缓存把残余的内容发送出来了。所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。


粘包现象两种情况:

① 发送数据量太大,超出对端缓存区限制

② 发送数据量非常小,TCP采用Nagle算法(为了消除网络延迟),Nagle算法简单来说就是:如果你连续发送几次数据,但这些数据并不大,通常TCP会根据优化算法把这些数据合成一个TCP段后一次性发送。而对端当作一个包处理(它并不能逆向解包,导致粘包现象的产生)


当然,这种粘包现象是有解决方案的——使用UDP,它是无连接的、面向消息的,数据包与包之间互相独立,一收对应一发,因此没有粘包现象的产生。但是会丢数据,不可靠。

或者,告诉对端消息边界——在实际数据交互之前,通知对方,“我”的数据有多少;或者将多次发送数据的情况分隔开。


这篇socket就介绍到这儿了,下回见~