## 一、什么是Socket
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
## 二、Socket参数介绍
在python中,实例化一个socket连接对象后,需要传入的参数有以下几项(括号中是默认参数):
**`socket.``socket`(*family=AF\_INET*, *type=SOCK\_STREAM*, *proto=0*, *fileno=None*) **
#### Socket Families(地址簇)
`socket.``AF_UNIX unix本机进程间通信 `
`socket.``AF_INET IPV4 `
`socket.``AF_INET6 IPV6`
#### Socket Types
`socket.``SOCK_STREAM #for tcp`
`socket.``SOCK_DGRAM #for udp `
`socket.``SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。`
`socket.``SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。`
`socket.``SOCK_SEQPACKET #废弃了`
剩下的参数proto,fileno一般我们不用管。
## 三、socket实例
最简单的socket实例
```
import socket
client = socket.socket()
client.connect(('localhost',9999))
client.send(b'hello world')
data = client.recv(1024)
print('recv',data)
client.close()
socket_client
```
```
import socket
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
conn,addr = server.accept()
data = conn.recv(1024)
print('recv:',data)
conn.send(data.upper())
server.close()
socket_server
```
上面的代码的有一个问题, 就是SocketServer.py运行起来后, 接收了一次客户端的data就退出了。。。, 但实际场景中,一个连接建立起来后,可能要进行多次往返的通信。
```
import socket
client = socket.socket()
client.connect(('localhost',8888))
while True:
data = input('>>请输入要发送的内容:')
if len(data) == 0:
continue
client.send(data.encode('utf-8'))
res = client.recv(1024)
print(res.decode())
client.close()
socket_client 支持多次交互
```
```
import socket
server = socket.socket()
server.bind(('localhost',8888))
server.listen(5)
print('我要开始等电话了')
conn,addr = server.accept()
print('电话来了')
while True:
data = conn.recv(1024)
print(data)
if not data:
print('client has lost')
break
conn.send(data.upper())
conn.close()
socket_server 支持多次交互
```
那么知道了socket的实现原理之后,我们可以通过socket实现一个极简版的ssh工具,就是客户端连接上服务器后,让服务器执行命令,并返回结果给客户端。
```
import socket
client = socket.socket()
client.connect(('localhost',8888))
while True:
data = input('>>请输入要发送的内容:')
if len(data) == 0:
continue
client.send(data.encode('utf-8'))
res_size = client.recv(1024)
print(res_size)
res_data = b''
recv_size = 0
while recv_size < int(res_size.decode()):
data = client.recv(1024)
recv_size += len(data)
res_data += data
else:
print(recv_size)
print(res_data.decode())
print(res_data)
client.close()
socket_client_ssh
```
```
import socket
import os
server = socket.socket()
server.bind(('localhost',8888))
server.listen(5)
print('我要开始等电话了')
while True:
conn,addr = server.accept()
print('电话来了')
while True:
data = conn.recv(1024)
if not data:
print('client has lost')
break
res = os.popen(data.decode()).read()
conn.send(str(len(res.encode())).encode())
conn.send(res.encode())
print('send done')
conn.close()
socket_server_ssh
```
输出结果:
![](https://img.kancloud.cn/45/07/450769cb35a6dbbf85968fe0ca3375a5_950x373.png)
看程序执行报错了, 我在客户端本想只接服务器端命令的执行结果大小,但实际上却连命令结果也跟着接收了一部分。
这里就引入了一个重要的概念,“粘包”, 即服务器端你调用时send 2次,但你send调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的socket发送缓冲区里,等缓冲区满了、或者数据等待超时了,数据才会被send到客户端,这样就把好几次的小数据拼成一个大数据,统一发送到客户端了,这么做的目地是为了提高io利用效率,一次性发送总比连发好几次效率高。 但也带来一个问题,就是“粘包”,即2次或多次的数据粘在了一起统一发送了。就是我们上面看到的情况 。
我们在这里必须要想办法把粘包分开, 因为不分开,你就没办法取出来服务器端返回的命令执行结果的大小。首先你是没办法让缓冲区强制刷新把数据发给客户端的。 你能做的,只有一个。就是让缓冲区超时,超时了,系统就不会等缓冲区满了,会直接把数据发走,那么如何让缓冲区超时呢?
答案就是:
1. time.sleep(0.5),经多次测试,让服务器程序sleep 至少0.5就会造成缓冲区超时。虽然我们觉得0.5s不多,但是对数据实时要求高的业务场景,比如股票交易等用这种方法肯定是不行的。
2. 不用sleep,服务器端每发送一个数据给客户端,就立刻等待客户端进行回应,即调用 conn.recv(1024), 由于recv在接收不到数据时是阻塞的,这样就会造成,服务器端接收不到客户端的响应,就不会执行后面的conn.sendall(命令结果)的指令,收到客户端响应后,再发送命令结果时,缓冲区就已经被清空了,因为上一次的数据已经被强制发到客户端了。看下面代码实现。
```
import socket
client = socket.socket()
client.connect(('localhost',8888))
while True:
data = input('>>请输入要发送的内容:')
if len(data) == 0:
continue
client.send(data.encode('utf-8'))
res_size = client.recv(1024)
client.send(b'ok')
print(res_size)
res_data = b''
recv_size = 0
while recv_size < int(res_size.decode()):
data = client.recv(1024)
recv_size += len(data)
res_data += data
else:
print(recv_size)
print(res_data.decode())
client.close()
socket_client_ssh
```
```
import socket
import os
server = socket.socket()
server.bind(('localhost',8888))
server.listen(5)
print('我要开始等电话了')
while True:
conn,addr = server.accept()
print('电话来了')
while True:
data = conn.recv(1024)
if not data:
print('client has lost')
break
res = os.popen(data.decode()).read()
conn.send(str(len(res.encode())).encode())
conn.recv(1024)
conn.sendall(res.encode())
print('send done')
conn.close()
socket_serevr_ssh
```
**SocketServer**
socketserver一共有这么几种类型
1class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server.
1class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as forTCPServer
12class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)
**There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:**
~~~
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
~~~
创建一个socketserver 至少分以下几步:
1. First, you must create a request handler class by subclassing the BaseRequestHandler class and overriding its handle() method; this method will process incoming requests.
2. Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
3. Then call the handle\_request() or serve\_forever() method of the server object to process one or many requests.
4. Finally, call server\_close() to close the socket.
最基本的socketserver代码实现
~~~
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print(self.data)
self.request.send(self.data.upper())
except Exception as e:
print('链接断开',e)
break
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()
~~~
让你的socketserver并发起来, 必须选择使用以下一个多并发的类
***class *`socketserver.``ForkingTCPServer`**
***class *`socketserver.``ForkingUDPServer`**
***class *`socketserver.``ThreadingTCPServer`**
***class *`socketserver.``ThreadingUDPServer`**
so 只需要把下面这句
~~~
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
~~~
换成下面这个,就可以多并发了,这样,客户端每连进一个来,服务器端就会分配一个新的线程来处理这个客户端的请求
~~~
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
~~~
当然,知道了socket的实现原理之后,我们还可以使用socket来完成很多事情,比如说用socket来完成一个FTP文件上传下载的功能也都可以。
- Python学习
- Python基础
- Python初识
- 列表生成式,生成器,可迭代对象,迭代器详解
- Python面向对象
- Python中的单例模式
- Python变量作用域、LEGB、闭包
- Python异常处理
- Python操作正则
- Python中的赋值与深浅拷贝
- Python自定义CLI三方库
- Python并发编程
- Python之进程
- Python之线程
- Python之协程
- Python并发编程与IO模型
- Python网络编程
- Python之socket网络编程
- Django学习
- 反向解析
- Cookie和Session操作
- 文件上传
- 缓存的配置和使用
- 信号
- FBV&&CBV&&中间件
- Django补充
- 用户认证
- 分页
- 自定义搜索组件
- Celery
- 搭建sentry平台监控
- DRF学习
- drf概述
- Flask学习
- 项目拆分
- 三方模块使用
- 爬虫学习
- Http和Https区别
- 请求相关库
- 解析相关库
- 常见面试题
- 面试题
- 面试题解析
- 网络原理
- 计算机网络知识简单介绍
- 详解TCP三次握手、四次挥手及11种状态
- 消息队列和数据库
- 消息队列之RabbitMQ
- 数据库之Redis
- 数据库之初识MySQL
- 数据库之MySQL进阶
- 数据库之MySQL补充
- 数据库之Python操作MySQL
- Kafka常用命令
- Linux学习
- Linux基础命令
- Git
- Git介绍
- Git基本配置及理论
- Git常用命令
- Docker
- Docker基本使用
- Docker常用命令
- Docker容器数据卷
- Dockerfile
- Docker网络原理
- docker-compose
- Docker Swarm
- HTML
- CSS
- JS
- VUE