socket 概述
套接字(socket),用于描述IP和端口,是一个通信链的句柄,应用程序通常就是通过socket向网络发出情况求或者应答网络请求。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket工作在哪一层?
网络部分的知识没必要太深究,在这里只要知道两个就差不多了。一个是TCP/IP四层模型(方便对照OSI七层),还有一个需要知道的就是TCP/IP的三次握手和四次挥手。- TCP/IP四层模型
- Applications(SMTP,TELNET,FTP)
- Transport(Transmission Control Protocal)
- Internet(Internet Protocal)
Network Interface
TCP的三次握手和四次挥手:
pass
需要注意的是,socket跑在应用层和传输层中间,是一种中间软件抽象层,他是一组接口。在设计模式中,socket就是一个门面模式,他把复杂的TCP/IP协议族隐藏在socket接口后面。对于使用的用户来说,一组简单的接口就是全部,让socket去组织数据配合规范。
所以我们并不需要特意去深入了解TCP/UDP协议,socket已经封装好了使用方法,我们只要遵循socket的规定去变成,写出的程序自然就是遵循传输层协议标准的。
socket如何标识目的
socket的模式,可以概括为IP-PORT。在互联网中,IP可以用来标识一台唯一的主机,而PortID可以用来标识这台主机上的唯一一个应用(进程)。
socket这种绑定IP-PORT的模式就标识了互联网中一个独一无二的应用程序。socket如何实现数据的传输?
从上图可以看出,socket的数据传输并不是直接进行的。而是先将要发送的数据缓存在内存中,再通过网络到对方的缓存池中的。
理解这个模型很重要,之后在socket传输中,出现粘包的情况时候,就需要这个模型去理解为什么会出现粘包,以及如果用多种方法规避粘包。
socket基本运行流程(TCP为例)
在泳道中的opt其实并不是指的是真的read()write()操作,而是服务器和客户端之间进行的数据交互动作。服务器和客户端互相交互反馈数据后,当客户端关闭了,服务器端读取到了结束链接的讯号,就也会关闭。
但在实际操作上服务器基本上是不可能在客户端断开连接之后也把链接关掉的。所以需要一个循环机制来给服务器在处理完某个客户端的请求后返回到侦听客户端连接的状态。也就是链接循环。
而在opt操作块中,客户端也不可能是单次数据传输完就结束链接,所以还需要给服务器一个通讯循环来持续接受TCP客户端的数据。
典型socket C/S代码
client:
import socket
#实例化socket,并且指定socket类型,连接类型
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#链接某个IP_PORT(即绑定的socket)
phone.connect(('127.0.0.1',8080))
while True:
#循环输入
msg = input('>>:').strip()
#当输入为空的时候,直接跳过,防止空值输入可能出现的报错
if not msg:continue
#将输入的信息进行‘utf-8’的编码
phone.send(msg.encode('utf-8'))
#bytes 格式才能转发出去
# 设置单次获取的数据值为9182字节,并且解码方式设置为GBK(win默认)
data = phone.recv(9182).decode('gbk')
print(data)
#关闭连接
conn.close()
#关闭socket
phone.close()
server:
#server
import socket
import os
import subprocess
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 将socket通信看成一个打电话的过程,先将一个socket实例化为phone
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 这一步这由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
#开机 等待电话链接
#listen()TCP的缓存链接池,可以将别的等待链接挂起,方便之后链接。测试的时候可以写5,但在实际应用中,应该在配置文件中设置这个值
while True:
print('start and waiting')
conn,addr = phone.accept()
print('phone line is ',conn)
print('client line is ',addr)
while True:
try:
data = conn.recv(1024)
# 收消息,1024:单次接收的量;设置的时候考虑到获取的消息来自自己的缓存池,所以不能很大,通常为1024或8192
# linux上客户强制断开后,服务端会接收空信息,空会使服务端进入无限接收发送的循环,所以需要判断是否为空,若为空就直接判断断开连接
if not data:break
print('>>>:',data.decode('utf-8'))
res = subprocess.Popen(str(data.decode('utf-8')), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = res.stdout.read()
error = res.stderr.read()
print(result)
print(error)
if result:
conn.send(result)
if error: conn.send('cmd not found! try again!')
# else: conn.send(''.encode('utf-8'))
except Exception:
break
conn.close()
phone.close()