Socket,原意是“插座”,在计算机通信领域,Socket一般被翻译为“套接字”。百度百科关于Socket的定义如下:“所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口” [[1]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-1)。
如何理解?为理解上面这段关于Socket的定义,我们需要先简单了解一下TCP/IP协议。TCP/IP 协议,即传输控制协议/网际协议(Transmission Control Protocol/Internet Protocol),是指能够在多个不同网络间实现信息传输的协议簇,其是一系列网络通信协议的总称 [[2]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-2)。通俗点理解,TCP/IP协议是对计算机之间通信所必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信 [[3]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-3)。TCP/IP 协议采用4层结构,分别是应用层、运输层、网络层和链路层,如下图1所示 [[1]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-1) [[4]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-4):
图1. TCP/IP协议结构
而Socket就是位于应用层与运输层中间的软件抽象层,如下图2所示。Socket是对TCP/IP协议的封装 [[5]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-5)。Socket提供了一组接口,以用于应用程序与网络协议栈进行交互。通过Socket,应用程序可以方便地与网络协议栈进行交互,从而不同应用程序可以进行通信。
图2. Socket在TCP/IP协议中的位置
因此,我们可以将Socket抽象为应用程序间进行通信的端点,如下图3所示。
图3.Socket是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象
更通俗点理解,就像我们把插头插到插座上就能从电网中获得电力供应一样,应用程序需要先连接到因特网才能接收或发送数据,而 Socket 就是用来将应用程序连接到因特网的工具 [[6]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-6)。
Socket典型的应用就是Web服务器和浏览器:浏览器获取用户输入的URL,向服务器发起请求;服务器分析接收到的URL,将对应的网页内容返回给浏览器;浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户 [[6]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-6)。
Python中,我们用socket()函数来创建套接字,其语法格式如下(需先import socket模块)[[7]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-7):
socket.socket([family[, type[, proto]]]) # 使用给定的地址族、套接字类型及协议号来创建套接字
参数说明 [[7]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-7)-[[9]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-9):
这一部分,我们只介绍几个下面我们会用到的重要方法,其余方法请参考文档 [[10]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-10)。
方法 | 描述 |
---|---|
服务器方法 | |
socket.bind(address) | 将套接字绑定到地址address,地址的格式取决于上述地址族。对于AF_INET而言,以元组(host, port)的形式表示地址。 |
socket.listen([backlog]) | 允许服务器接受连接,即监听连接。backlog指定在拒绝连接之前,系统可以挂起的最大连接数量。该值至少为0,大部分应用程序设为5就可以了。 |
socket.accept() | 接受连接。Socket必须先绑定到一个地址并监听连接。 该方法返回一对(conn, address),其中conn是一个新的Socket对象,用于在连接上发送和接收数据;address是绑定到连接另一端Socket的地址。 |
客户端方法 | |
socket.connect(address) | 连接到address处的套接字。地址的格式取决于上述地址族。 |
socket.connect_ex(address) | connect方法的扩展版本。不同的是,该方法在出错时会返回一个错误指示符,而不是像connect在出错时抛出异常。如果该方法成功,那么错误指示符为0;否则,错误指示符为变量errno的值。该方法对于支持例如异步连接很有用。 |
公共方法 | |
socket.recv(bufsize[, flags]) | 接收数据,返回值是一个字节对象,表示接收到的数据。bufsize指定了一次所能接收的最大数据量。 |
socket.send(bytes[, flags]) | 发送数据,该Socket必须连接到一个远程Socket。 该方法返回发送的字节数,该值可能小于bytes的字节数。 |
socket.sendall(bytes[, flags]) | 发送数据,该Socket必须连接到一个远程Socket,成功则返回None。不同于send方法,该方法会一直从bytes中发送数据,直到所有数据都已发送,或发生错误。 |
socket.recvfrom(bufsize[, flags]) | 接收数据,返回值是一对(bytes, address),其中bytes是表示接收到的数据的字节对象,而address是发送数据的Socket的地址。 |
socket.sendto(bytes, flags, address) | 发送数据,该Socket不能连接到一个远程Socket,因为address指定了目标Socket。该方法返回发送的字节数。 |
socket.close() | 关闭Socket。 |
服务器 [[11]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-11):
客户端 [[11]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-11):
上述流程可以总结如下图4所示:
图4. 基于TCP的Socket编程流程图 [11]
我们以日常打电话为例,先形象直观地理解一下上述基于TCP的Socket编程的一般思路 [[6]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-6)。感兴趣的,可以再深入了解一下TCP协议。
服务器——相当于接电话方:
客户端——相当于打电话方:
就像我们日常打电话需要通话双方先建立连接之后才能讲话一样,服务器需要使用socket.listen()、socket.accept()方法,客户端需要使用socket.connect()/socket.connect_ex()方法先彼此建立连接,之后才能进行网络通信。
注释:
下面,我们以下述代码为例,再深入理解一下上述基于TCP的Socket编程的一般思路。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
""" 服务器 """
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建服务器Socket
s.bind(('127.0.0.1', 8888)) # 绑定创建的服务器Socket到一个ip和端口
s.listen(5) # 监听客户端的连接请求
print("Waiting connection from client...")
conn, address = s.accept() # 接受客户端的连接请求
print("Connected by {}".format(address)) # 打印客户端Socket的地址
receive_message = conn.recv(1024).decode() # 接收客户端传来的数据并解码
print("Message received: {}".format(receive_message)) # 打印客户端传来的数据
send_message = receive_message.upper().encode() # 将客户端传来的字符串转为大写之后,再编码发送回客户端
conn.send(send_message) # 发送数据给客户端
conn.close() # 关闭连接
s.close()
#!/usr/bin/env python
# -*- coding:utf-8 -*-
""" 客户端 """
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建客户端Socket
s.connect(('127.0.0.1', 8888)) # 连接客户端Socket到服务器ip和端口
print(s.getsockname()) # 打印客户端Socket的地址
send_message = 'Hello, world!'
s.send(send_message.encode()) # 发送数据给服务器
receive_message = s.recv(1024).decode() # 接收服务器传来的数据并解码
print("Message received: {}".format(receive_message)) # 打印服务器传来的数据
s.close() # 关闭连接
先运行服务器脚本,再运行客户端脚本,结果如下:
""" 服务器 """
Waiting connection from client...
Connected by ('127.0.0.1', 52161)
Message received: Hello, world!
""" 客户端 """
('127.0.0.1', 52161)
Message received: HELLO, WORLD!
这里需要注意以下几点:
一、我们使用的是socket.accept()方法返回的新的Socket(称为服务Socket或连接Socket)与客户端进行网络通信的,而非服务器Socket(称为监听Socket)本身。关于监听Socket和服务Socket/连接Socket,具体请参考[[13]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-13)、[[14]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positivedefault-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-14)。
二、Python3以后,socket传递的都是bytes类型的数据,字符串需要先转换一下,string.encode()即可;另一端接收到的bytes数据想转换成字符串,只要bytes.decode()一下就可以 [[11]](https://blog.csdn.net/Graduate2015/article/details/122209859?ops_request_misc=%7B%22request%5Fid%22%3A%22170211679216800215015807%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170211679216800215015807&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-122209859-null-null.nonecase&utm_term=python socket&spm=1018.2226.3001.4450#refer-anchor-11)。
相比基于TCP的Socket编程,基于UDP的Socket编程更加简单。由于UDP没有TCP的握手和挥手的过程,因此socket.listen()、socket.accept()和socket.connect()/socket.connect_ex()方法都不需要。
服务器:
客户端:
上述流程以及和基于TCP的Socket编程的区别可以总结如下图5所示:
图5. 基于TCP的Socket编程和基于UDP的Socket编程的比较。上图为基于TCP的Socket编程流程图,下图为基于UDP的Socket编程流程图。 [15]
我们先还是以日常发短信为例,先形象直观地理解一下上述基于UDP的Socket编程的一般思路。感兴趣的,可以再深入了解一下UDP协议。
服务器——相当于收短信方:
客户端——相当于发短信方:
和打电话不同,我们日常收发短信,并不需要收发短信双方先建立连接,而是一方发短信给另一方,另一方收到短信之后再回过去。客户端Socket通过socket.sendto()方法通过指定address参数的形式将消息发送给对应地址的服务器Socket,这就相当于发短信时需要输入对方的手机号。然后服务器Socket通过socket.recvfrom()方法接收消息,并返回对方的地址,这就相当于我们在收到别人发给我们的短信的时候,手机不仅会显示对方发来的短信内容,还会显示对方的手机号码。这样,服务器Socket就又可以通过socket.sendto()方法通过指定address参数的形式给客户端发送消息,这就相当于给对方手机号回消息。
下面,我们以下述代码为例,再深入理解一下上述基于UDP的Socket编程的一般思路。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
""" 服务器 """
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建服务器Socket
s.bind(('127.0.0.1', 8888)) # 绑定创建的服务器Socket到一个ip和端口
receive_message, address = s.recvfrom(1024) # 接收客户端传来的数据
print("Receive message from {}".format(address)) # 打印客户端Socket的地址
receive_message = receive_message.decode() # 解码客户端传来的数据
print("Message received: {}".format(receive_message)) # 打印客户端传来的数据
send_message = receive_message.upper().encode() # 将客户端传来的字符串转为大写之后,再编码发送回客户端
s.sendto(send_message, address) # 发送数据给客户端
s.close() # 关闭连接
#!/usr/bin/env python
# -*- coding:utf-8 -*-
""" 客户端 """
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建客户端Socket
send_message = 'Hello, world!'
s.sendto(send_message.encode(), ('127.0.0.1', 8888)) # 发送数据给服务器
print(s.getsockname()) # 打印客户端Socket的地址
receive_message, address = s.recvfrom(1024) # 接收服务器传来的数据
print("Receive message from {}".format(address)) # 打印服务器Socket的地址
receive_message = receive_message.decode() # 解码服务器传来的数据
print("Message received: {}".format(receive_message)) # 打印服务器传来的数据
s.close() # 关闭连接
先运行服务器脚本,再运行客户端脚本,结果如下:
""" 服务器 """
Receive message from ('127.0.0.1', 64410)
Message received: Hello, world!
1234
""" 客户端 """
('0.0.0.0', 64410)
Receive message from ('127.0.0.1', 8888)
Message received: HELLO, WORLD!
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。