一)什么是Socket?
- 可能介绍时稍加自己理解,有偏差的地方,还请见谅。
(1)首先我们了解计算机网络中的信息传输过程
- (有七层,五层,四层计算机网络原理体系结构)我说下我学习的五层结构。
- (1)主机A发送数据:
应用层-->运输层-->网络层-->数据链路层-->物理层
- (2)中间经过路由器:
物理层-->数据链路层-->网络层
- (3) 知道到达主机B:
物理层-->数据链路层-->网络层-->运输层-->应用层
(2)为何使用socket
- 我们不再去细说每层所具有的功能,和使用的协议。但当经过运输层时,所有的外层首部字段(比如ip等)都将褪去(也就是剩下tcp/udp层次的)。 ip协议只是负责将数据传送给指定的主机。 到达运输层后,选用
tcp/udp 协议
,利用端口
,实现端到端的通信,也就是应用进程之间的通信。
- 我的理解是,运输层通过tcp/udp 协议将数据传送给了应用进程,也就是
应用层
,应用层根据自己的协议来规实现功能。 - (重点):那么如何利用运输层传递的信息数据呢?那就是
socket!
(3) socket介绍(书上原话):
- 大部分的网络协议都是由软件实现的(特别是协议栈中的高级协议),绝大多数的计算机系统都将运输层以下的网络协议在
操作系统内核实现
。(重点)应用程序想要执行网络操作,必须通过操作系统为应用程序操作网络所提供的的接口,这个接口通常称为网络应用编程接口 (Application Programming interface)。
- 大多数操作系统都具有自己的网络API,但是随着时间的推移,
套接字(Socket)API
得到广泛应用。 - 套接字API 定义了一组
数据结构和操作。
其中最关键的就是套接字数据结构(简称套接字)。套接字是一个非常复杂的数据结构,包括进行网络操作所需的各种资源(如缓存),各种参数(地址,端口号,协议类型等)。应用进程想要进行网络操作,必须先调用套接字API中定义的操作创建套接套接字数据结构,已获得进行网络操作所需的资源。
- 套接字数据结构位于操作系统内核,应用程序不能直接访问数据结构,需要通过
创建操作
返回的套接字描述符
来操作数据结构,此后,应用程序所进行的网络操作(建立连接,收发数据,调整网络通信参数等)都必须以该描述符为参数调用套接字API中的操作完成。
相信你已经初步了解了套接字。我在读到这里时,也豁然开朗了很多。
二)Tcp套接字,客户端/服务端调用套接字API基本流程
三)python socket 标准库基本使用介绍(官方文档)
(1)socket库介绍
- 可以看到这个库,也是对底层调用 套接字API的封装,使操作更简洁,更智能。
- socket-底层网络接口
(2)用法:
1.创建套接字对象: 字段均可在官方文档上查阅:
import socket
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
"""
:param family:协议,一般表示ivp4(默认) 或者 ivp6
:param type: 套接字类型, SOCK_STREAM(默认)tcp 流
使用给定的地址族,套接字类型和协议号创建一个新的套接字。
地址族应为AF_INET(默认设置),AF_INET6,AF_UNIX,AF_CAN,AF_PACKET或AF_RDS。
套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
如果指定了fileno,则其他参数将被忽略,从而导致具有指定文件描述符的套接字返回。
与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的套接字。
这可能有助于使用socket.close()关闭分离的套接字。
"""
(3)什么是UDP报文首部格式:
** UDP
- 用户数据报协议 UDP只在IP的数据报服务上增加了
端口
功能,特点:UDP是无连接的,尽最大努力交付的,没有拥塞控制,面向报文的,支持一对一,一对多,多对一和多对多交互通道。
(4) udp的小例子(发送数据)
- udp实现比较简单,直接创建连接,然后便可以发送数据,单工通信。
- 重点是上面的tcp基本流程
1 import socket
2
3 def main():
4 """ udp小例子"""
5 # 创建套接字对象 (使用socket.SOCK_DGRAM,也就是udp数据报)
6 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 也可进行端口绑定 bind,否则随机分配端口
7 # 选择发送方地址(元组形式的ip地址和端口号)
8 dest_addr = ('10.212.xxx.xxx', 8080)
9 # 发送信息
10 send_data = input("请输入要发送的数据:")
11 udp_socket.sendto(send_data.encode("utf-8"), dest_addr)
12 # 关闭套接字
13 udp_socket.close()
14 print("-----end----")
15
16
17 if __name__ == "__main__":
18 main()
- 本例子是在VMware虚拟机 uubntu系统上面编写,往物理机 windows 上发送数据,利用模拟软件接收。
- 运行
- 结果:
(5)UDP的小例子(接收数据)
1 import socket
2
3 def recv():
4
5 # 1.创建套接字
6 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
7 # 2. 绑定信息
8 local_addr = ("", 8081) # ip地址和端口号,一般ip地址不用写,表示主机任
何一个IP地址
9 udp_socket.bind(local_addr)
10
11 # 3. 等待对方发送数据
12 print("正在等待接收数据--:")
13 recv_data = udp_socket.recvfrom(1024) # 最大接收字节数
14
15 # 4. 显示接受的数据
16 print("接收到的数据为:")
17 print(recv_data)
18
19 # 5. 关闭套接字
20 udp_socket.close()
21 print('----end------')
22
23 if __name__ == "__main__":
24 recv()
- 绑定信息
bind() 函数 - 接收函数
(6) udp 聊天器的小例子
1 import socket
2
3 class Chat():
4 """ udp简单聊天器 """
5 def __init__(self):
6 """ 初始化 """
7 self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
8 self.udp_socket.bind(("", 7999))
9
10 def welcome(self):
11 print("欢迎登陆!输入序号开始聊天")
12 print("1. 选择聊天对象")
13 order = input()
14 if order == "1":
15 self.people()
16
17 def people(self):
18 print("以下是聊天列表:请选择序号进行聊天~")
19 print("1.小明")
20 order = input()
21 if order == "1":
22 print("开始聊天:输入 exit结束聊天")
23
24 def send(self, send_data):
25 self.udp_socket.sendto(send_data.encode("gbk"), ("192.168.91.128", 8080))
26 print("内容已发送")
27
28 def recv(self):
29 recv_data = self.udp_socket.recvfrom(1024)
30 # print("%s,小明:%s" % (str(recv_data[1]),recv_data[0].decode("gbk") ))
31 print(recv_data[0].decode("utf-8"))
32 print("完成over~~")
33
34 def chat(self):
35 while True:
36 send_data = input("请输入想要说的内容:")
37 if send_data == "exit":
38 print("聊天结束!")
self.udp_socket.close()
39 break
40 self.send(send_data)
41 print("正在等待对方回应:...")
42 self.recv()
43
44 if __name__ == "__main__":
45 a = Chat()
46 a.welcome()
47 a.chat()
- 效果(比较死板,只能收发收发的运行)
(四)TCP介绍及 客户端与服务器简单实现
(1)什么是tcp:
传输控制协议(TCP)
:提供全双工,可靠交付服务。- TCP是面相连接的(字节流),并且在运输层时使用了流量控制和拥塞控制机制。
与upd不同,tcp的端口队列不同,tcp的发送缓存和接收缓存都是分配给一个连接的,而不是一个端口。TCP的链接由四元组(源IP地址,源端口号 ,目的IP地址,目的端口号)标识。
(2) TCP是区分客户端和 服务器的
- 如 (二) 中介绍的客户端服务器调用套接字接本流程。
-
- 创建套接字
-
- 对于客户端程序, 要做的就是 connect 链接服务器。服务器端,需要
绑定,监听,和接收
,返回新的发送套接字
。
- 对于客户端程序, 要做的就是 connect 链接服务器。服务器端,需要
- 课本C语言展示:
(3)服务器示例
-
监听
-
接收链接
-
接收请求数据
-
接收套接字,发送数据
-
客户端程序发送请求; 一般都是(IP地址, 端口号)
-
代码展示:
-
功能: 同一时刻为一台客户端服务,服务完成后,允许其他客户端链接。
1 import socket
2
3 def server():
4 # 1. 创建套接字
5 tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
6 # 2. 绑定本地信息(ip,端口)
7 tcp_socket.bind(("", 7890))
8 # 3.设置为监听状态(监听套接字)
# 连接数,这个数字还是有操作系统决定的,根据操作系统不一样,能连接多少也不一样
9 tcp_socket.listen(128)
10 print("我是服务器,我现在正在堵塞中,等待监听客户端请求。。。。")
11
12 while True:
13 # 4.等待客户端的链接(产生指定的接收套接字,服务该链接),采用阻塞的方式!
14 client_socket, client_addr = tcp_socket.accept()
15 print("我同意客户端的链接请求,下面是接受套接字为您服务。。。。")
16 while True:
17 # 5.接受对方(客户端)发送的数据请求(接收套接字),接受到的只是数据,不包含地址信> 息
18 recv_data = client_socket.recv(1024).decode("utf-8")
19 print("接收到的数据为: ", recv_data)
20
21 # 如果发送的是 exit, 或者主动关闭客户端链接,那么服务结束(当对方客户端主动close> 关闭自己链接时,服务器会受到空值)
22 if recv_data == "exit" or recv_data == '':
23 print("服务结束")
24 break
25
26 # 回复给客户端的请求
27 client_socket.send("hello world".encode("utf-8"))
28
29 # 6.关闭套接字
30 print("关闭客户端")
31 client_socket.close()
32 tcp_socket.close()
33
34
35 if __name__ == "__main__":
36 server()
- 运行示例结果
(五)实现简单的下载器
- 简单的模拟文件的下载请求,和服务器传输下载文件。
- 客户端
1 import socket
2 import sys
3 import time
4
5
6 _output = sys.stdout
7
8 def client():
9 """ 请求下载文件的客户端 """
10 # 1. 创建套接字
11 dw_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
12 # 2. 连接服务器
13 dest_ip = input("请输入下载服务器的ip地址:")
14 dest_port =int(input("请输入端口:"))
15 dw_client.connect((dest_ip, dest_port))
16
17 # 3. 获取下载文件名
18 dw_filename = input("请输入要下载的文件名字:")
19 # 4. 发送文件名字到服务器
20 dw_client.send(dw_filename.encode("utf-8"))
21
22
23 # 7.保存文件中的数据到文件中
24 i = 1
25 with open("[new]" + dw_filename, "ab+") as f:
26 while True:
27 # 接受文件,大小为1M 1024 --> 1k
28 dw_data = dw_client.recv(1024*1024)
29 if dw_data == b'001':
30 print('接收完毕!')
31 break
32 f.write(dw_data)
33 time.sleep(0.1)
34 _output.write("\r传输数据次数: %d" % i)
35 i += 1
36 _output.flush()
37 print("文件下载完毕请查看!,本次共传输: %d 次!" % i)
38 # 8. 关闭套接字
39 dw_client.close()
40
41
42 if __name__ == "__main__":
43 client()
- 下载服务器端:
1 import socket
2 import time
3
4 def read_data(f, n):
5 """ 生成器形式,
6 当文件过大时,我们需要分次读取,读取太大要考虑内存大小,读取太小要考虑到读取操作上的> 开销
7 """
8 content = b''
9 for i, line in enumerate(f):
10 content += line
11 if (i+1) % n == 0:
12 yield content
13 content = b''
14 else:
15 # 存在最后不到 n行的数据不能被返回,这里返回一下
16 yield content
17
18
19 def send_file(dw_server, filename):
20 """ 完成传输文件的功能"""
21 f = None
22 # 尝试打开文件
23 try:
24 f = open(filename, "rb")
25 except Exception as e:
26 print("没有该文件%s" % filename)
27
28 # 当文件正常打开,发送数据
29 if f:
30 for i in read_data(f, 5):
31 print("开始读入数据")
32 # 回复给客户端的下载请求
33 dw_server.send(i)
34 time.sleep(0.1)
35 else:
36 # 循环正常结束,则执行关闭操作
37 f.close()
38 # 发送结束标志,说明数据传输完毕!
39 dw_server.send(b'001')
40 else:
41 print("open files error")
42
43
44 def server():
45 # 1. 创建套接字
46 tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
47 # 2. 绑定本地信息(ip,端口)
48 tcp_socket.bind(("", 7890))
49 # 3.设置为监听状态(监听套接字)
50 tcp_socket.listen(128)
51 print("我是服务器,我现在正在堵塞中,等待监听客户端请求。。。。")
52
53 while True:
54 # 4.等待客户端的链接(产生指定的接收套接字,服务该链接),采用阻塞的方式!
55 client_socket, client_addr = tcp_socket.accept()
56 print("我同意客户端的链接请求,下面是接受套接字为您服务。。。。")
57 while True:
58 dw_filename = client_socket.recv(1024).decode("utf-8")
59 print(dw_filename)
60 if dw_filename == "exit" or dw_filename == '':
61 print("服务结束")
62 break
63 send_file(client_socket ,dw_filename)
64 # 6.关闭套接字
65 print("关闭客户端")
66 client_socket.close()
67 tcp_socket.close()
68
69
70 if __name__ == "__main__":
71 server()
结果展示:
- 客户端: