socket网络编程

socket

是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多
都是基于Socket来完成通信的,例如我们每天浏览网页、刷朋友圈、收发email等等。要解决网络上两台主机之间的进程通信问题,
首先要唯一标识该进程,在 TCP/IP 网络协议中,就是通过(IP地址,协议,端口号)三元组来标识进程的,解决了进程标识问题,
就有了通信的基础了。


函数 socket.socket 创建一个 socket,返回该 socket 的描述符,将在后面相关函数中使用。该函数带有两个参数:

Address Family:(地址簇)
1,AF_INET(用于 Internet 进程间通信)
2,AF_UNIX(用于同一台机器进程间通信)


Type:(套接字类型)
1,SOCKET_STREAM(流式套接字,主要用于 TCP 协议)
2,SOCKET_DGRAM(数据报套接字,主要用于SOCKET_DGRAM(数据报套接字,主要用于 UDP 协议)

先来看以下socket都有那些参数呢?

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

 

sk.bind(address)

  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

      backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
      这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

  接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

  连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

  同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

  关闭套接字

sk.recv(bufsize[,flag])

  接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])

  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])

  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

      内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)

  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)

  设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

  返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

  返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

  套接字的文件描述符

# 在python3以后,无论是收还是发,必须是字节类型。

一,简单的soket的例子

1 import socket
2 client = socket.socket()  # 声明socket类型,同时生成socket连接对象
3 client.connect(("localhost", 6969))  # 要连接的的主机名或者IP地址,端口号
4 
5 client.send(b"Hello World!")  # python3+ 发送的只能是字节
6 data = client.recv(1024)  # 接收字节的大小
7 
8 print(data)
9 client.close()  # 关闭连接
socket客户端
 1 import socket
 2 server = socket.socket()
 3 server.bind(("localhost", 6969))  # 绑定要监听的端口,记住bind里面是一个元组
 4 server.listen()
 5 print("等风来!")
 6 
 7 conn, addr = server.accept()  # 等风来
 8 # conn就是客户端连进来而在服务器端为其生成的一个连接实例
 9 print(conn, addr)
10 
11 print("风来了!")
12 data = conn.recv(1024)
13 print("receive:", data)
14 conn.send(data.upper())
15 server.close()
socket服务端

二,上述只能发一次,能不能发一次,收一次呢?!不间断的接收呢?接着看。

 1 import socket
 2 client = socket.socket()  # 声明socket类型,同时生成socket连接对象
 3 client.connect(("localhost", 6969))  # 要连接的的主机名或者IP地址,端口号
 4 while True:          #--------------------->增加一个while循环
 5     inp = input("请输入:")
 6     if len(inp) == 0: # 如果为空时,直接跳出此次循环,继续让输入。否则服务端卡会卡在conn.recv那,等着接收。
 7         continue
 8     client.send(bytes(inp, encoding="utf-8"))  # python3+ 发送的只能是字节
 9     data = client.recv(1024)  # 接收字节的大小
10     print(str(data, encoding="utf-8"))
11 client.close()  # 关闭连接
客户端
 1 import socket
 2 server = socket.socket()
 3 server.bind(("localhost", 6969))  # 绑定要监听的端口
 4 server.listen()
 5 print("等风来!")
 6 
 7 conn, addr = server.accept()  # 等风来
 8 # conn就是客户端连进来而在服务器端为其生成的一个连接实例
 9 print(conn, addr)
10 print("风来了!")
11 while True:     # --------------------------->也增加了一个while循环
12     data = conn.recv(1024)
13     print("receive:", str(data, encoding="utf-8"))
14     conn.send(data.upper())
15 server.close()
服务端

三,这次看着不错!但是又有新的问题来了,上述只能满足一个客户端不间断的收发,如果有多个客户端呢?!用户能排队跟server进行收发操作,而且用户挂断对服务端没有任何影响。客户端随时可以连接服务端,进行消息的收发,往下面看。

 1 import socket
 2 ip_port = ("127.0.0.1", 9000)
 3 client = socket.socket()
 4 client.connect(ip_port)
 5 while True:
 6     data = input(">>>").strip()
 7     if len(data) == 0:   # 判断,输入为空重新输入
 8         continue
 9     if data == "exit": # 如果,客户端输入字符'exit',客户端退出,此时在window上,服务端recv到的字符为空,
10         break            # 客户端会卡住,服务端也会卡住,等着客户端发。
11     client.send(bytes(data, encoding="utf-8"))
12     data_rev = client.recv(102400)
13     print(str(data_rev, encoding="utf-8"))
14 client.close()
客户端
 1 import socket
 2 ip_port = ("127.0.0.1", 9000)
 3 server = socket.socket()
 4 server.bind(ip_port)
 5 server.listen(5)  # 开机,listen里面的值后面需要注意
 6 while True:
 7     print("waiting...")
 8     conn, addr = server.accept()   # 等电话
 9     while True:
10         try:
11             data_rev = conn.recv(102400)
12             if len(data_rev) == 0:
13                 print("客户端正常退出了!")
14                 break
15             conn.send(data_rev.upper())
16             print(data_rev)
17         except Exception:
18             print("客户端非法挂断") # 比如,用户直接关闭客户端程序
19             break
20     conn.close()  # 挂断电话
21 server.close()  # 关机了
服务端

总结:

1.基于python3.5版本的socket只能收发字节(python2.7可以发送str)
2.退出只在客户端退出就ok了
3.client.accept, client.recv()是阻塞的(基于链接正常)
4.listen(n) n代表:能挂起的连接数,如果n=1,代表可以链接一个,挂起一个,第三个则会被拒绝

四,如何对上述代码进行改造,让其支持远程执行命令?

 1 import socket
 2 ip_port = ("127.0.0.1", 9000)
 3 client = socket.socket()
 4 client.connect(ip_port)
 5 while True:
 6     data = input("cmd >>>").strip()
 7     if len(data) == 0:   # 判断,输入为空重新输入
 8         continue
 9     if data == "exit":  # 如果,客户端输入字符'exit',客户端退出,此时在window上,服务端recv到的字符为空
10         break
11     client.send(bytes(data, encoding="utf-8"))
12     data_rev = client.recv(102400)
13     print(str(data_rev, encoding="utf-8"))
14 client.close()
客户端
 1 import socket
 2 import subprocess
 3 ip_port = ("127.0.0.1", 9000)
 4 server = socket.socket()
 5 server.bind(ip_port)
 6 server.listen(0)  # 开机
 7 while True:
 8     print("waiting...")
 9     conn, addr = server.accept()   # 等电话
10     while True:
11         try:
12             data_rev = conn.recv(1024)
13             if len(data_rev) == 0:
14                 print("客户端正常退出了!")
15                 break
16             p = subprocess.Popen(str(data_rev, encoding="utf8"), shell=True, stdout=subprocess.PIPE)
17             res = p.stdout.read() # 注意由于实在windows操作系统,这里为gbk编码的字节
18             res1 = str(res, encoding="gbk")  # 首先转换成字符串
19             if len(res) == 0: # 因为如果客户端输入了非法的命令返回结果则为空
20                 conn.send(bytes('cmd error ', encoding="utf-8"))
21             else:
22                 conn.send(bytes(res1, encoding="utf8")) 
23             print(data_rev)
24         except Exception:
25             print("客户端非法挂断")
26             break
27     conn.close()  # 挂断电话
28 server.close()  # 关机了
服务端

五, 此时上述代码也会有一个问题: 当客户端要在服务端执行的命令返回结果过长时,客户端接收不过来。此时客户端再执行新的命令时,服务端的返回结果依然是上一个命令的结果,知道把结果返回完,才开始执行客户端发出的紧接的那条长命令的命令。这种现象叫做粘包效应。如何解决?

思路一:
可以加大客户端的client.recv(102400)括号里面的值,但是这个值,不可以无限加大,最终会受限于
硬件本身,比如网卡的MTU值(网卡最大的传输单元)


思路二:由于客户端没有接收完毕,这里我们可以让客户端循环接收完毕,至于怎么判断客户端是否循环
接收完毕了呢?我们可以在服务端执行完计算出来,然后告知客户端这个命令的执行结果大小,客户端在循环
接收时的大小如果和服务端告知的大小相等,证明已经接收完毕。

 1 import socket
 2 import chardet  # 检测编码类型,这里没什么用
 3 ip_port = ("127.0.0.1", 9000)
 4 client = socket.socket()
 5 client.connect(ip_port) # 连接服务端,如果服务端已经存在一个连接,那么挂起
 6 
 7 while True:  # 基于connect建立的连接来循环发送消息
 8     data = input("cmd >>>").strip()
 9     if len(data) == 0:   # 判断输入是否为空,为空重新输入
10         continue
11     if data == "exit":  # 如果客户端输入字符'exit',客户端退出,此时在window上,服务端recv到的字符为空
12         break
13     client.send(bytes(data, encoding="utf-8"))  # 给服务端发送cmd命令
14     data = client.recv(1024)
15     rev_data = str(data, encoding='utf-8')
16     if rev_data.startswith("Size"):  # 接收服务端传送过来的执行结果的字节长度,为下面判断执行结果是否全部返回做准备
17         size = rev_data.split("/")[1]
18     else:   # 如果服务端发送过来的不是执行结果长度,就因该是'cmd error',此时需要用户重新输入,注意continue的运用
19         print(rev_data)
20         continue
21 
22     rev_message = ""
23     new_size = 0
24     while True:  # 循环接收服务端发送过来的执行结果,直到接受完为止
25         data1 = client.recv(1024)
26         rev_data1 = str(data1, encoding="utf-8")
27         rev_message += rev_data1  # 接收的字符串累加
28         new_size += len(data1)  # 接收的执行结果长度不断增大
29         print("TotalSize:", int(size), "ReceiveSize:", new_size)
30         #
31         if new_size == int(size):  # 如果接收的大小和原始执行结果的大小相等,说明传送完毕。
32             break
33     print(rev_message)
34 client.close()
客户端
 1 import socket
 2 import subprocess
 3 import chardet
 4 
 5 ip_port = ("127.0.0.1", 9000)  # 定义元组
 6 server = socket.socket()    # 绑定协议 ,生成套接字
 7 server.bind(ip_port)  # 绑定ip+协议+端口,用来表示唯一的进程,ip+port必须是元组格式
 8 server.listen(0)  # 开机,定义最大连接数
 9 while True:  # 用来重复接收新的连接
10     print("waiting...")
11     conn, addr = server.accept()   # 接收客户端连接请求,conn相当于一个链接,addr是客户端的ip+port
12     while True:  # 基于一个链接重复收发消息
13         try:   # 判断用户是不是异常退出
14             data_rev = conn.recv(1024)
15             if len(data_rev) == 0: # 客户端正常退出,exit时,接收的值为空!
16                 print("客户端正常退出了!")
17                 break
18             p = subprocess.Popen(str(data_rev, encoding="utf8"), shell=True, stdout=subprocess.PIPE)
19             res = p.stdout.read()  # 获取标准输出,注意由于是在windows操作系统,这里为gbk编码的字节
20             print("cmd res:", chardet.detect(res))
21             if len(res) == 0:  # 如果执行命令有误, 返回值为空
22                 conn.send(bytes('cmd error ', encoding="utf-8"))
23             else:
24                 data = str(res, encoding="GB2312")  # 执行命令成功,首先把gb2312转换成字符串
25                 send_data = bytes(data, encoding="utf-8")
26                 conn.send(bytes("Size/{}".format(len(send_data)), encoding="utf-8"))
27                 # 发送原始执行结果的长度,以便客户端判断是否接收完毕
28                 print("Size/{}".format(len(send_data)))
29                 conn.send(send_data)
30                 print("send_data", chardet.detect(send_data))
31                 # 判断字符编码,没什么用
32                 print(str(send_data, encoding="utf-8"))
33         except Exception:
34             print("客户端非法挂断")
35             break
36     conn.close()  # 挂断电话
37 server.close()  # 关机了
服务端

六, 上述代码尽管已经很完美了,但是好像还有一个重大的缺陷。就是不支持多用户同时进行访问,因为就是对并发的支持。
接着看下面如何做到多用户并行执行命令???

client端

import socket

ip_port = ('127.0.0.1', 8009)
client = socket.socket()
client.connect(ip_port)
data = client.recv(102400)
print(str(data, encoding="utf-8"))
while True:
	inp = input("cmd >>>").strip()
	client.send(bytes(inp, encoding='utf-8'))
	if len(inp) == 0:
		continue
	if inp == "exit":
		break
	rev_data = client.recv(102400)
	print(str(rev_data, encoding="utf-8"))

  

server端

import socketserver
import subprocess


class MyServer(socketserver.BaseRequestHandler):
	def handle(self):
		# 注意必须是handle方法,否则不会正常执行,因为在继承BaseRequestHandler会首先执行一个handle方法
		# 而下面在继承时首先会继承自身的handle方法,可以结合BaseRequestHandler源码来看
		# print self.request,self.client_address,self.server
		conn = self.request  # 此时的self.request就相当于conn
		conn.sendall(bytes('欢迎致电 10086,请输入1xxx,0转人工服务.', encoding="utf-8"))
		flag = True
		while flag:
			print("waiting rev")
			try:
				data = conn.recv(1024)
				data = str(data, encoding='utf-8')
				res = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
				res1 = str(res.stderr.read(), encoding="gbk")  # 命令正确执行
				# print("error:", len(res1))
				res2 = str(res.stdout.read(), encoding="gbk")  # 命令错误
				# print("right:", len(res2))
				if data == 'exit':
					flag = False
				if len(res1) == 0:  # 当错误的结果为空时表示命令正确执行,返回正确的结果
					conn.sendall(bytes(res2, encoding="utf-8"))
				if len(res2) == 0:  # 当正确的结果为空时表示命令执行错误,返回错误的结果
					conn.sendall(bytes(res1, encoding="utf-8"))
				if len(res1) == 0 and len(res2) == 0:  # 如果执行的命令没有返回结果
					conn.sendall(bytes("cmd is not stdout", encoding="utf-8"))
				print(data)
			except Exception as tx:  # 客户端非法关闭
				print(tx)
				break

if __name__ == '__main__':
	server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), MyServer)
	# 每请求过来时都会实例化这个类
	server.serve_forever()

上述代码之所以能够支持多并发,是因为有socketserver的支持,那么socketserver又为什么能够支持多并发呢?原因如下:

1,IO多路复用

2,多线程,多进程,协程

七,socketserver

io多路复用

I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。


python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用select、poll、epol 从而实现
IO多路复用。

windows python:只支持select

Mac python: 只支持select

linux python:支持select、poll、epoll


select:监听的数量有限制1024,内部通过for循环来实现
poll: 监听的数量没有限制了,但是内部依然使用for循环来实现的,效率依然不高
epool:实现的机制发生了变化,不再是通过for一个一个去看了,而是只要有变化通知我即可,效率大为提高。nignx就是利用的epool,所以并发处理能力比较强!

注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。

 这些一般在写代码的时候用不到,但是以后在看源码的时候比如torado等,非常有用。不然以后会看不懂。

简单点来说,IO多路复用,用来监听socket对象内部是否变化了。

可以同时监听多个客户端的连接

select, poll, epoll
用来监听socket内部是否已经变化了。

什么时候会发生变化?连接或者收发消息时,会变化。

服务器端socket对象发生变化

服务器有两个sock对象:
sk: 有新连接进来了
conn:要发消息了

伪并发

1, 监听sk对象的变化

服务端

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9997))
sk.listen(5)

while True:
	rlist, wlist, e = select.select([sk], [], [], 1)
	# 监听sk(服务器)对象的变化,如果sk对象发生变化,表示有客户端来连接了,此时的rlist值为[sk]
	# 1代表超时时间。如果过1秒没有新连接,就是个空列表。
	print(rlist)
	for r in rlist:
		conn, address = r.accept()
		conn.sendall(bytes("hello !", encoding='utf-8'))

流程:监听socket对象,只要有新连接进来,就给他发送一条消息!省去了单个socket需要等待accept的过程。因为下面的for循环执行的前提是rlist不为空,就表示有人来连接才会执行。

客户端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9997))
r = sk.recv(102400)
print(str(r, encoding='utf-8'))

while True:
	inp = input(">>>")
	if len(inp) == 0:continue
	sk.sendall(bytes(inp, encoding='utf-8'))
	data = sk.recv(102400)
	print(str(data, encoding='utf-8'))

 

2, 同时监听sk和conn对象的变化

服务端

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9997))
sk.listen(5)
inputs = [sk]   # 这个里面的sk为服务端自己的sk
while True:
	rlist, wlist, e = select.select(inputs, [], [], 1)
	# 监听sk(服务器)对象的变化,如果sk对象发生变化,表示有客户端来连接了,此时的rlist值为[sk]
	# 监听conn对象,如果conn发生变化,表示客户端于新消息发过来了,此时的rlist的值为[客户端]
	print(len(inputs), len(rlist))

	for r in rlist:
		if r == sk:  # 表示有新用户来连接了
			conn, address = r.accept()
			# conn是什么?其实也是一个socket对象,是专门为某sk连接创立的
			inputs.append(conn) 
			conn.sendall(bytes("hello !", encoding='utf-8'))
		else:  # 表示有人发消息了
			r.recv(1024)

流程:服务端本身监听自己的sk。

只要有新的客户端来连接时:
len(rlist) 0 ---> 1 ---> 0 此时len(inputs) 1---> 2 --->2 ....该值会一直累加
只要客户端发送消息时:
len(rlist) 0 ---> 1 ----> 0 此时len(inputs) n --> n ---> n .....该值不变

即当有新的客户端来连接,首先会监听到sk的变化,rlist中的值为[sk],当有已经连接上的客户端发送消息时,rlist中的值[sk, conn]

注意理解!!!

总结:但是此时并非正常的并发,因为现在的所谓“并发”在通过for循环来实现的。就是一个人可以聊多个QQ,但是并不能在同一个时间点跟多人聊天。

客户端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9997))
r = sk.recv(102400)
print(str(r, encoding='utf-8'))

while True:
	inp = input(">>>")
	if len(inp) == 0:continue
	sk.sendall(bytes(inp, encoding='utf-8'))

  

3, 如果客户端断开连接,socket又是如何变化了,对上述代码进行完善!
import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9997))
sk.listen(5)
inputs = [sk]
while True:
	rlist, wlist, e = select.select(inputs, [], [], 1)
	# 监听sk(服务器)对象的变化,如果sk对象发生变化,表示有客户端来连接了,此时的rlist值为[sk]
	# 监听conn对象,如果conn发生变化,表示客户端于新消息发过来了,此时的rlist的值为[客户端]
	print(len(inputs), len(rlist))

	for r in rlist:
		if r == sk:  # 表示有新用户来连接了
			conn, address = r.accept()
			# conn是什么?其实也是一个socket对象,是专门为某连接创立的
			inputs.append(conn)
			conn.sendall(bytes("hello !", encoding='utf-8'))
		else:  # 表示有人发消息了
			print("============")
			try:
				ret = r.recv(1024)
				if not ret:   # 对于linux操作系统,会返回空值
					raise Exception('客户端断开连接了')
			except Exception as e:   # 对于window操作系统,会直接报错
				print(e)
				inputs.remove(r)

  总结:上面这些是贯穿IO复用的基础的知识点

4, 其实上述例子中在收到消息时,直接就可以进行发送了,但是读写在一起了,如何做到读写分离呢?这时就需要用到第二个参数。
第二参数比较单一,只要第二个参数有什么值,wlist里面就有什么值,只要第二个参数里面有值,select就会一直变化。
import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9997))
sk.listen(5)
inputs = [sk, ]
outputs = []
while True:
	rlist, wlist, e = select.select(inputs, outputs, [], 1)   #  select中的第二个参数写什么值,wlist就获取到什么值
	# 监听sk(服务器)对象的变化,如果sk对象发生变化,表示有客户端来连接了,此时的rlist值为[sk]
	# 监听conn对象,如果conn发生变化,表示客户端于新消息发过来了,此时的rlist的值为[客户端]
	print(len(inputs), len(rlist), len(wlist), len(outputs))

	for r in rlist:
		if r == sk:  # 表示有新用户来连接了
			conn, address = r.accept()
			# conn是什么?其实也是一个socket对象,是专门为某连接创立的
			inputs.append(conn)
			conn.sendall(bytes("hello", encoding='utf-8'))
		else:  # 表示有人发消息了
			print("============")
			try:
				ret = r.recv(1024)
				# r.sendall(ret)   # 可以一收,一发,但是并没有做到读写分离
				if not ret:   # 对于linux操作系统,会返回空值
					raise Exception('客户端断开连接了')
				else:
					outputs.append(r)   # 把每次给我发消息的人都存到outputs这个列表里面
			except Exception as e:   # 对于window操作系统,会直接报错
				inputs.remove(r)

	for w in outputs:
		w.sendall(bytes("response", encoding='utf-8'))
		outputs.remove(w)   # 因为已经回过消息了,再次就不再回复

  

5, 上述例子也会有一个问题,就是在发送消息时,拿不到接收的消息?如何才能在回复时,回复的是:客户端发过来的消息+response呢?
import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9997))
sk.listen(5)
inputs = [sk, ]
outputs = []
messages = {}  # 存放 {对象1:[消息1,消息2], 对象2:[消息1,消息2] }
while True:
	rlist, wlist, e = select.select(inputs, outputs, [], 1)   #  select中的第二个参数写什么值,wlist就获取到什么值
	# 监听sk(服务器)对象的变化,如果sk对象发生变化,表示有客户端来连接了,此时的rlist值为[sk]
	# 监听conn对象,如果conn发生变化,表示客户端于新消息发过来了,此时的rlist的值为[客户端]
	print(len(inputs), len(rlist), len(wlist), len(outputs))

	for r in rlist:
		if r == sk:  # 表示有新用户来连接了
			conn, address = r.accept()
			# conn是什么?其实也是一个socket对象,是专门为某连接创立的
			inputs.append(conn)
			messages[conn] = []
			conn.sendall(bytes("hello", encoding='utf-8'))
		else:  # 表示有人发消息了
			print("============")
			try:
				ret = r.recv(1024)
				# r.sendall(ret)   # 可以一收,一发,但是并没有做到读写分离
				if not ret:   # 对于linux操作系统,会返回空值
					raise Exception('客户端断开连接了')
				else:
					outputs.append(r)   # 把每次给我发消息的人都存到outputs这个列表里面
					messages[r].append(ret)   # 哪个对象给我发消息,我就把消息存放到那个对象对应的列表里面
			except Exception as e:   # 对于window操作系统,会直接报错
				inputs.remove(r)
				del messages[r]    # 当断开连接的时候把对象对应的消息列表给删除了

	for w in wlist:
		msg = messages[w].pop()   # 这就是上一次发的消息
		resp = msg + bytes("response", encoding='utf-8')
		w.sendall(resp)
		outputs.remove(w)   # 因为已经回过消息了,再次就不再回复

 八,socketserver源码剖析(重点掌握)

import socketserver


class MyClass(socketserver.BaseRequestHandler):
	def handle(self):
		conn = self.request
		mes = "欢迎来到10086...."
		conn.sendall(bytes(mes, encoding='utf-8'))
		pass

obj = socketserver.ThreadingTCPServer(('127.0.0.1', 9998), MyClass)
obj.serve_forever()

第一步:加载类MyClass

第二步:实例化socketserver.ThreadingTCPServer类。
  1,首先会执行ThreadingTCPServer类的___init___方法,结果没有
  2,发现有两个父类,先调用左边的父类的ThreadingMixIn的__init__方法---> 也没有,再执行右边父类TCPServer的__init__方法

第三步:调用对象中的server_forever()方法
  1,首先开执行server_forever,自己本身没有,开始找父类,左边父类也没有,右边父类里面有并执行。
  2,这时发现server_forever方法中又调用了_handle_request_noblock(),开始找这个方法(记住每次找都要从最底部开始找),发现父 类的父类中BaseServer中有并执行。
  3,此时在_handle_request_noblock()中又调用了process_request()方法,这是还是一样要从根开始找,即执行ThreadingMixIn类中的这个方法,而不是BaseServer类中的这个方法。
  4,这时在process_request()又开始调用了finish_request()方法,一样从底部开始找,找到并执行BaseServer类中的这个方法。
  5,在finish_request()中开始执行RequestHandlerClass()这个方法,而这个方法正是用户传递的类MyClass,也就是说需要实例化MyClas s类
  6,执行首先执行MyClass类中__init__方法,发现没有,接着执行父类的__init__方法,而这个__init__中就开始调用handle()方法了,这时在MyClass类中定义的就有handle()方法,所以首先执行自己的handle()方法,这就是为什么只要用户一连接上来就会执行handle()方法的原因所在。

记住:必知必会,必须要用在源码里面找一遍执行流程,很能锻炼人!!!来解释:为什么执行上面这段代码时会首先执行MyClass中的handle方法。一定注意:不要完全相信pycharm定义的函数,pycharm定义的时候还没有那么智能!

觉得本文不错的,别忘了点赞支持以下哦!

原文地址:https://www.cnblogs.com/yang-ning/p/6375780.html