# 什么是 Socket?
# Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
# Python 中,我们用 socket()函数来创建套接字,语法格式如下:
# socket.socket([family[, type[, proto]]])
- family: 套接字家族可以使AF_UNIX或者AF_INET
- type: 套接字类型可以根据是面向连接的还是非连接分为
SOCK_STREAM
或SOCK_DGRAM
- protocol: 一般不填默认为0.
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件
一个简单的socket通信实例
import socket # 简单的socket例子。 # 声明网络协议,并创建socket对象。 client = socket.socket() # 链接服务器 client.connect(('127.0.0.1',8088)) while True: user_input = input('>>:').strip() if not user_input:continue if user_input == 'exit': break # 发送数据,为了支持中文encode. client.send(user_input.encode('utf-8')) # 接收服务返回的数据 ret = client.recv(1024) print('recv:',ret.decode()) # 关闭socket。 client.close()
import socket # 声明网络协议,并创建socket对象。 server = socket.socket() # 绑定监听端口 server.bind(('127.0.0.1',8088)) # 绑定监听端口 # 监听 server.listen() #监听 """ 第一个循环是持续等待请求数据。 第二个循环是持续回复客户端信息。 如果客户端传输数据为空,则断开对客户端的回复,跳回等待接收请求部分。 """ while True: print('开始等待请求...') # 等待请求,connect是客户端连接服务器生产的实例,addr是地址信息 connect,addr = server.accept() print(connect, addr) while True: # 接收客户端发送的数据 client_data = connect.recv(1024) # 如果发送数据为空,则跳出继续接待客户端部分,回到等待请求部分。 if not client_data: break print('recv:',client_data.decode()) # 给客户端发送回复信息。 connect.send(client_data)
一个利用socket通信实现SSH执行linux命令的例子
2.1 客户端向服务器发送执行命令
2.2 服务器接受客户端命令并执行命令,获取执行后结果
2.3 服务器向客户端发送执行命令结果的数据包大小。
2.4 客户端接收数据大小,并向服务器端发送可以接收数据的确认信息。
2.5 服务器接收到确认信息,给客户端发送数据。
2.6 客户端接收并做处理显示结果
import socket # 简单的socket通信实现ssh例子。 # 声明网络协议,并创建socket对象。 client = socket.socket() # 链接服务器 client.connect(('127.0.0.1',8088)) while True: user_input = input('>>:').strip() if not user_input:continue if user_input == 'exit': break # python3 支持发送字节 client.send(user_input.encode()) # 接收服务返回的数据。 ret_size = client.recv(1024) print(ret_size) client.send('be ready'.encode('utf-8')) # 向服务器发送可以接受数据了。 reply_size = 0 reply_data = b'' while reply_size != int(ret_size): ret_data = client.recv(1024) # 这里必要len()解码后的长度。因为服务端len()的便是未编码的长度。注意!!!! # 注意这里中文字符和英文字符len()长度问题。 reply_size += len(ret_data.decode()) reply_data += ret_data else: print(reply_size) print('over') print(reply_data.decode()) # 关闭socket。 client.close()
import socket,os # 声明网络协议,并创建socket对象。 server = socket.socket() # 绑定监听端口 server.bind(('127.0.0.1',8088)) # 绑定监听端口 # 监听 server.listen() #监听 """ 第一个循环是持续等待请求数据。 第二个循环是持续回复客户端信息。 如果客户端传输数据为空,则断开对客户端的回复,跳回等待接收请求部分。 """ while True: print('开始等待请求...') # 等待请求,connect是客户端连接服务器生产的实例,addr是地址信息 connect,addr = server.accept() print(connect, addr) while True: # 接收客户端发送的数据 client_data = connect.recv(1024) # 如果发送数据为空,则跳出继续接待客户端部分,回到等待请求部分。 if len(client_data) == 0: break res = os.popen(client_data.decode()).read() if not res: res = 'common not found' # 给客户端发送回复信息。 # 如果是linux上,注意socket 粘包的情况。 connect.send(str(len(res)).encode('utf-8')) # 先发送数据大小,保证客户端效验数据的完整性。 """ 为了解决socket粘包的情况,在发送数据包大小和发送数据之间,再和客户端做一个交互, 把缓冲区隔开,socket粘包就完美解决。 """ client_ack = connect.recv(1024) connect.send(res.encode('utf-8'))# 发送数据。
通过Socket实现文件下载 3.1 客户端发送下载请求。 3.2 服务器检查下载文件存不存在。 3.3 服务器向客服端发送文件大小。 3.4 客户端接收文件大小数据,向服务器端发送可以进行下一步信息。 3.5 服务器边打开文件,边发送数据,并在边打开文件期间生成MD5效验值。 3.6 客户端接收数据同时生成客户端文件MD5效验码,客户端接收到MD5效验值与客户端MD5做对比,客户端根据对比结果做操作,之后向服务器发送对比结果。 3.7 服务器接收到对比结果做对应操作。
import socket,hashlib,sys """socket客户端设置""" client = socket.socket() client.connect(('127.0.0.1',8999)) while True: input_value = input('<<:').strip() if not input_value:continue if input_value.startswith('get'): if ' ' not in input_value or not input_value.split(' ')[1]: print('command error') continue file_name = input_value.split(' ')[1] """[发送] 用户输入值""" client.send(input_value.encode('utf-8')) """[接收] 应答,如果不报错,是文件大小,报错则打印报错信息""" data_size = client.recv(1024).decode() if len(data_size)==0 or 'error' in data_size: print(data_size.decode()) continue """[发送] 准备接收数据信息""" client.send('be_ready'.encode('utf-8')) """ [接收] 数据并且生成文件MD5 注意:为了减少两端的交互,所以在发送数据之后着发送了文件MD5值。 但是data_size只是文件大小,那么怎么分开接收文件数据和文件MD5呢? 解: 因为文件MD5是在数据末尾部分,所以while循环接收数据的最后一次,一定文件数据和文件MD5混合成的, 只要区分开最后一次中,文件数据占了多少,client.recv()接收这一部分即可。 而,再client.recv()一次,就只剩下文件MD5了。 """ rev_size = 0 data_size = int(data_size) m = hashlib.md5() f = open('1_'+file_name,'wb') # 测试用文件名。 while rev_size < data_size: if data_size-rev_size > 1024: # 说明不是最后一次 size = 1024 else: # 最后一次,剩下的文件数据多少就收多少 size = data_size-rev_size file_data = client.recv(size) m.update(file_data) rev_size += len(file_data) f.write(file_data) f.close() client_file_md5 = m.hexdigest() server_file_md5 = client.recv(1024).decode() print('client:{0} - server:{1}'.format(client_file_md5,server_file_md5)) """[发送] 文件MD5效验对比结果,""" if client_file_md5 == server_file_md5: md5_send_data = 'success' client.send(md5_send_data.encode('utf-8')) print('文件下载成功...') else: md5_send_data = 'error' client.send(md5_send_data.encode('utf-8')) print('文件出现损坏...请重新下载...') """如果失败重新走一遍上面流程""" else: print('error command') continue client.close()
import socket,os,hashlib """socket服务器设置""" server = socket.socket() server.bind(('127.0.0.1',8999)) server.listen() while True: print('服务器等待请求...') conn,addr = server.accept() while True: """处理请求""" """[接收] 文件数据""" client_data = conn.recv(1024).decode() if len(client_data) == 0 or ' ' not in client_data or not client_data.split(' ')[1]: conn.send('error file not found or command error'.encode('utf')) continue if client_data.startswith('get'): file_name = client_data.split(' ')[1] print('file_name',file_name) """检测文件是否存在""" fileexist = os.path.isfile(file_name) if not fileexist: conn.send('error file not found'.encode('utf-8')) continue else: """[发送] 文件大小""" file_size = os.stat(file_name).st_size conn.send(str(file_size).encode('utf-8')) """[接收] 客户端确认接收数据信息""" be_ready_info = conn.recv(1024).decode() #wait for ack if be_ready_info != 'be_ready': continue m = hashlib.md5() try: """[发送] 文件数据""" f = open(file_name, 'rb') for line in f: m.update(line) # 合并文件用于生成MD5效验数据 conn.send(line) # 发送文件数据 f.close() """[发送] 文件MD5效验值,这里可能会出现粘包的情况,所以在client中接收数据时,做了一些判断""" conn.send(m.hexdigest().encode('utf-8')) """[接收] 文件MD5效验值对比结果""" response_md5_value = conn.recv(1024).decode() # wait for ack print(response_md5_value) # please your action break except Exception as e: conn.send('error file open found'.encode('utf-8')) continue else: conn.send('error command'.encode('utf-8')) continue