码迷,mamicode.com
首页 > 其他好文 > 详细

<Socket>Socket网络编程

时间:2020-07-28 22:26:39      阅读:69      评论:0      收藏:0      [点我收藏+]

标签:关闭   拼接   puts   发送   lse   文件   多个   多线程   coding   

Socket层

技术图片

Socket有一个缓冲区,缓冲区是一个流,先进先出,发送和取出的可自定义大小的,如果取出的数据未取完缓冲区,则可能存在数据怠慢。造成粘包的问题

黏包问题:文件大小和文件内容,一起在缓冲区发送给服务端,就会产生粘包的现象

Socket发送两条连续数据时,可能最终会拼接成一条进行发送

解决方法一:

两条数据间进行延时发送,如【tiem.sleep(0.5) #延时0.5s】

解决方法二:

每次发送后等待对方确认接收信息数据,发送一条后就立即接收等待

解决方法三:

设定接收数据大小,发送端每次发送需要发送的数据的数据大小,接收端通过设置【recv(xx)】只接收确定的大小

(在client发送文件大小后面加一个recv,直到server端回复,才开始发送文件)

Socket基本使用:

简单的服务器:

import socket
sk = socket.socket()
sk.bind((‘127.0.0.1‘,8898))  #把地址绑定到套接字
sk.listen()          #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024)  #接收客户端信息
print(ret)       #打印客户端信息
conn.send(b‘hi‘)        #向客户端发送信息
conn.close()       #关闭客户端套接字
sk.close()        #关闭服务器套接字(可选)

简单的客户端:

import socket
sk = socket.socket()           # 创建客户套接字
sk.connect((‘127.0.0.1‘,8898))    # 尝试连接服务器
sk.send(b‘hello!‘)
ret = sk.recv(1024)         # 对话(发送/接收)
print(ret)
sk.close()            # 关闭客户套接字

  

Soket进阶:socket上传文件

  1. 先发送需要发送的文件的大小
  2. 然后用for循环一直发送文件        
  3. 一直到接受的字节大小等于发送的字节大小

服务器进阶:实现客户循环连接及数据循环收发

import socket

sk = socket.socket()
sk.bind((‘127.0.0.1‘, 9999,))
sk.listen(5)

while True:
	conn, address = sk.accept()
	conn.sendall(bytes(‘欢迎上传文件‘, encoding=‘utf-8‘))
	file_size = str(conn.recv(1024), encoding=‘utf-8‘)
	conn.sendall(bytes(‘服务端:已经接收到文件大小,开始接收文件。。。‘, encoding=‘utf-8‘))
	print(file_size)

	totle_size = int(file_size)
	has_recv = 0
	f = open(‘1.pdf‘,‘wb‘)
	while True:
		if totle_size == has_recv:
			break
		data = conn.recv(1024)
		f.write(data)
		has_recv += len(data)
	f.close()

  

客户端进阶:

import os
import socket
obj = socket.socket()
obj.connect((‘127.0.0.1‘,9999))
 
ret_bytes = obj.recv(1024)
ret_str = str(ret_bytes,encoding=‘utf-8‘)
print(ret_str)
 
size = os.stat(‘《疯狂Python讲义》.pdf‘).st_size
obj.sendall(bytes(str(size),encoding=‘utf-8‘))
# 收到服务器确认信息,才开始发送信息
print(str(obj.recv(1024),encoding=‘utf-8‘))
 
 
with open(‘《疯狂Python讲义》.pdf‘,‘rb‘) as f:
    for line in f:
        obj.sendall(line) 

 

IO多路复用:

可以监听多个文件描述符(文件句柄)(Socket对象),一旦文件句柄出现变化,即可感知

send和sendall的区别

  • send不一定一次发送完了,返回发送的字节数
  • sendall里面是一个for循环,一直调用send   

sever端:

import socket

sk1 = socket.socket()
sk1.bind((‘127.0.0.1‘, 8001,))
sk1.listen()

sk2 = socket.socket()
sk2.bind((‘127.0.0.1‘, 8002,))
sk2.listen()

sk3 = socket.socket()
sk3.bind((‘127.0.0.1‘, 8003,))
sk3.listen()

inputs = [sk1, sk2, sk3]
import select

while True:
	# select自动监听sk1-sk3三个对象,一旦某个句柄发生变化
	# 如果有人链接,就会发生变化
	# 最后一个参数每次循环,最多等待一秒
	# r_list表示发生变化链接,e_list表示发生错误的链接,w_list有什么值,存储什么值
	r_list, w_list, e_list = select.select(inputs, [],inputs, 1)
	# print(r_list, w_list, e_list)
	# 链接上就发送一个hello
	for sk in r_list:
		conn,address = sk.accept()
		conn.sendall(bytes(str(‘hello world!‘),encoding=‘utf-8‘))
		conn.close()

	for sk in e_list:
		inputs.remove(sk)

  

client端:

import socket
obj = socket.socket()
obj.connect((‘127.0.0.1‘,8001))
content = str(obj.recv(1024),encoding=‘utf-8‘)
print(content)
obj.close()

  

IO多路复用(改进):一个sk实现IO多路复用

sever端:

import socket

# 本质还是一个一个处理
sk1 = socket.socket()
sk1.bind((‘127.0.0.1‘, 8001,))
sk1.listen()

inputs = [sk1]
import select

while True:
	# select自动监听sk1-sk3三个对象,一旦某个句柄发生变化
	# 如果有人链接,就会发生变化
	# 最后一个参数每次循环,最多等待一秒
	# r_list表示发生变化链接,e_list表示发生错误的链接,w_list有什么值,存储什么值
	r_list, w_list, e_list = select.select(inputs, [], inputs, 1)
	# print(r_list, w_list, e_list)
	# 链接上就发送一个hello
	print("正在监听的socket对象%d,发生变化的%s" % (len(inputs), r_list))
	for sk in r_list:
		# 有新用户链接
		if sk == sk1:
			conn, address = sk.accept()
			inputs.append(conn)
		else:
			try:
				# 有老用户来链接
				data = sk.recv(1024)
				# 表示确实客户端确实发送数据了
				data_str = str(data, encoding=‘utf-8‘)
				sk.sendall(bytes(data_str, encoding=‘utf-8‘))
			# 表示客户端发送为空,客户端终止的时候
			except Exception as ex:
				inputs.remove(sk)
	for sk in e_list:
		inputs.remove(sk)

  

client端:

import socket
obj = socket.socket()
obj.connect((‘127.0.0.1‘,8001))

while True:
	inp = input(">>>")
	obj.sendall(bytes(inp,encoding=‘utf-8‘))
	ret = str(obj.recv(1024),encoding=‘utf-8‘)
	print(ret)
obj.close()

  

 

多路复用的读写分离:scrapy源码里就是这么写的

sever端:

import socket

# 本质还是一个一个处理
sk1 = socket.socket()
sk1.bind((‘127.0.0.1‘, 8001,))
sk1.listen()

inputs = [sk1, ]
outputs = []

message_dict = {}
import select

while True:
	# select自动监听sk1-sk3三个对象,一旦某个句柄发生变化
	# 如果有人链接,就会发生变化
	# 最后一个参数每次循环,最多等待一秒
	# r_list表示发生变化链接,e_list表示发生错误的链接,w_list有什么值,存储什么值
	r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1)
	# print(r_list, w_list, e_list)
	# 链接上就发送一个hello
	print("正在监听的socket对象%d,发生变化的%s" % (len(inputs), r_list))
	# 通过r_list和w_list实现读写分离
	# r_list只用来读取信息
	for sk in r_list:
		# 有新用户链接
		if sk == sk1:
			conn, address = sk.accept()
			inputs.append(conn)
			# 保存用户{‘小周‘:[]}
			message_dict[conn] = []
		else:
			try:
				# 有老用户来链接
				data = sk.recv(1024)
				# 表示确实客户端确实发送数据了
				data_str = str(data, encoding=‘utf-8‘)
				# 保存收到的消息
				message_dict[sk].append(data_str)
				# sk.sendall(bytes(data_str, encoding=‘utf-8‘))

				outputs.append(sk)
			# 表示客户端发送为空,客户端终止的时候
			except Exception as ex:
				inputs.remove(sk)
	# w_list保存谁给我发送过消息,
	# w_list只用来回复信息
	for conn in w_list:
		# 拿到接受的消息
		recv_str = message_dict[conn][0]
		# 每一次取出数据后,在删除,等待下一次信息
		del message_dict[conn][0]
		conn.sendall(bytes(recv_str+‘好‘, encoding=‘utf-8‘))
		outputs.remove(conn)
	for sk in e_list:
		inputs.remove(sk)

  

 

socket三阶段

  • socket,服务端只能处理一个请求

  • select + socket,伪并发

    • r_list:既读又写

    • r_list,w_list:读写分离

socket server 

select + socket + 多线程  = 真并发   

技术图片

每次客户端链接,都创建一个线程去处理 = 同时处理所有请求,并发实现

类图关系:

技术图片

源码流程

# -*- coding:utf-8 -*-
import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        # print self.request,self.client_address,self.server
        conn = self.request
        conn.sendall(‘欢迎致电 10086,请输入1xxx,0转人工服务.‘)
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == ‘exit‘:
                Flag = False
            elif data == ‘0‘:
                conn.sendall(‘通过可能会被录音.balabala一大推‘)
            else:
                conn.sendall(‘请重新输入.‘)


if __name__ == ‘__main__‘:
    # socket + select + 多线程
    # 创建IP,端口,类名
    # ThreadingTCPServer.init() => TCPServer.init() => BaseServer.init()
    # server对象
    #         self.server_address       (‘127.0.0.1‘,8009)
    #         self.RequestHandlerClass   MyServer
    #         self.socket                socket.socket()
    #         self.server_bind()         self.socket.bind()
    #         self.server_activate()     self.socket.listen()
    server = socketserver.ThreadingTCPServer((‘127.0.0.1‘,8009),MyServer)
    # server对象的serve_forever方法
    #  同样,在BaseServer中找到此方法,使用方法不一样,但是可以对应
    # while not self.__shutdown_request         while Ture
    # ready = selector.select(poll_interval)
    #            _ServerSelector()--->selectors.SelectSelector--->select()
    #           r, w, _ = self._select(self._readers, self._writers, [], timeout)
    # self._handle_request_noblock()
    #           request, client_address = self.get_request()  conn, address = sk.accept()
    # self.process_request        t = threading.Thread()
    # self.finish_request()=> self.RequestHandlerClass(request, client_address, self)   MyServer()
    #         self.request = request
    #         self.client_address = client_address
    #         self.server = server
    #         self.setup()
    #         try:
    #             self.handle()  ----》 MyServer.handle()  最终目的达到
    server.serve_forever()

  

 

 

  

<Socket>Socket网络编程

标签:关闭   拼接   puts   发送   lse   文件   多个   多线程   coding   

原文地址:https://www.cnblogs.com/shuimohei/p/13393329.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!