网络编程socket

时间:2023-03-09 20:35:47
网络编程socket

socket

socket解释

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。

应用程序通常通过"套接字"向网络发出请求或者应答网络请求。说白了就是一种通信机制。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。

socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

socket和file的区别:

  • file模块是针对某个指定文件进行【打开】【读写】【关闭】

  • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

网络编程socket

更多功能

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()

  套接字的文件描述符

s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
s.listen(backlog) 开始监听传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 s.connect(address) 连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接同一台机器上的服务器,可以将hostname设为‘localhost’。如果连接出错,返回socket.error错误。 s.connect_ex(adddress) 功能与connect(address)相同,但是成功返回0,失败返回errno的值。 s.accept() 接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。 s.close() 关闭套接字。 s.fileno() 返回套接字的文件描述符。 s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port) s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。 s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 s.recv(bufsize[,flag]) 接受套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。 s.recvfrom(bufsize[.flag]) 与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 s.send(string[,flag]) 将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 s.sendall(string[,flag]) 将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 s.sendto(string[,flag],address) 将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。 s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 s.setsockopt(level,optname,value) 设置给定套接字选项的值。 s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())普通的非套接字实例的函数 getdefaulttimeout()返回默认的套接字超时时间(以秒为单位)。None表示不设置任何超时时间。 gethostbyname(hostname) 将主机名(如“www.baidu.com”)转换为IPv4地址,IP地址将以字符串的形式返回,如“8.8.8.8”。不支持IPv6 gethostname() 返回本地机器的主机名。

更多功能

建立连接

单一的服务端与客户端建立连接

server 服务端

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5) while True:
conn,address = sk.accept()
print(address,conn)

client 客户端

import socket

obj = socket.socket()

obj.connect(('127.0.0.1', 9999,))

obj.close()

实例:

(聊天机器人)

服务端 :接受客户端的请求并返回给客户端数据。完成对话(聊天机器人)

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'))
while True:
ret_bytes = conn.recv(1024)
ret_str = str(ret_bytes,encoding='utf-8')
if ret_str == 'q':
break
conn.sendall(bytes(ret_str + "你好", encoding='utf-8'))

客户端:发送内容服务端会返回数据

import socket

obj = socket.socket()

obj.connect(('127.0.0.1', 9999,))

ret_bytes = obj.recv(1024)
ret_str = str(ret_bytes, encoding='utf')
print(ret_str)
while True:
inp = input('请输入要发送的内容:')
if inp =='q':
obj.sendall(bytes(inp, encoding='utf-8'))
break
else:
obj.sendall(bytes(inp, encoding='utf-8'))
ret = str(obj.recv(1024), encoding='utf-8')
print(ret)
obj.close()

FTP上传

服务端

  发送返回值,防止粘包

import socket

sk = socket.socket()  #创建服务器套接字
sk.bind(('127.0.0.1',9999,)) #绑定ip和端口,是元组
sk.listen(5) #监听链接;数值是表示一次性最多监听5个 while True:
#连接,客户端地址信息
conn,address =sk.accept() #在此阻塞,等待客户端的发送请求。accept阻塞
conn.sendall(bytes("欢迎登录老男孩FTP", encoding='utf-8')) #sendall表示发送数据,给客户端(只能发送字节)
#先接收文件大小,然后再开始接收
file_size = str(conn.recv(1024), encoding='utf-8') #recv接受数据 conn.sendall(bytes("ack",encoding='utf-8')) ###发送一个返回值,进行一次交互,防止粘包 # print(file_size)
total_size = int(file_size) #文件总大小
has_recv = 0 #已经接收的文件大小
f = open('new.png','wb')
#接收文件内容,直到获取完毕
while True:
if total_size == has_recv:
break
data = conn.recv(1024) #接收
f.write(data)
has_recv += len(data) f.close() #关闭服务端

客户端

  接受服务端发来的返回值,防止粘包

import socket
import os obj = socket.socket() #创建客户端套接字
obj.connect(('127.0.0.1', 9999)) #尝试连接服务器 ret_bytes = obj.recv(1024) #在此阻塞,等待服务端发送数据。recv接收数据
ret_str = str(ret_bytes, encoding='utf-8')
print(ret_str) size = os.stat('f.png').st_size
obj.sendall(bytes(str(size),encoding='utf-8')) #发送 obj.recv(1024) ####接收服务端发来的返回值,防止粘包。 with open('f.png', 'rb') as f:
for line in f:
obj.sendall(line) #发送数据 obj.close() #关闭客户端

IO多路复用

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

服务端

import socket

sk1 = socket.socket()
sk1.bind(('127.0.0.1',8801))
sk1.listen() sk2 = socket.socket()
sk2.bind(('127.0.0.1',8802))
sk2.listen() sk3 = socket.socket()
sk3.bind(('127.0.0.1',8803))
sk3.listen() inputs = [sk1,sk2,sk3, ] import select
while True:
# #[sk1,sk2,sk3],selsct内部自动监听sk1,sk2,sk3三个对象,一旦某个句柄发生变化
# #如果有连接 sk1
# #r_list = [sk1,sk2,sk3]
# r_list,w_list,e_list = select.select(inputs,[],[],1)
# for sk in r_list:
# #每一个连接对象
# conn,address = sk.accept()
# conn.sendall(bytes("hello",encoding="utf-8"))
# conn.close() r_list, w_list, e_list = select.select(inputs, [], inputs, 1) print('正在监听的socket对象%d' % len(inputs))
print(r_list)
for sk1_or_conn in r_list:
#每一个连接对象
if sk1_or_conn == sk1:
#表示有新用户来连接
conn, address = sk1_or_conn.accept()
inputs.append(conn)
else:
#有老用户发消息
try:
data_bytes = sk1_or_conn.recv(1024)
data_str = str(data_bytes,encoding="utf-8")
sk1_or_conn.sendall(bytes(data_str+"好",encoding="utf-8"))
except Exception as ex:
inputs.remove(sk1_or_conn)

客户端1

import socket

obj = socket.socket()
obj.connect(("127.0.0.1", 8801)) content = str(obj.recv(1024),encoding="utf-8")
print(content) obj.close()

客户端2

import socket

obj = socket.socket()
obj.connect(("127.0.0.1", 8802)) content = str(obj.recv(1024),encoding="utf-8")
print(content) obj.close()

读写分离

服务端

import socket

sk1 = socket.socket()
sk1.bind(('127.0.0.1',8801))
sk1.listen() sk2 = socket.socket()
sk2.bind(('127.0.0.1',8802))
sk2.listen() sk3 = socket.socket()
sk3.bind(('127.0.0.1',8803))
sk3.listen() inputs = [sk1,sk2,sk3, ]
outputs = []
message_dict = {} import select while True:
r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1) print('正在监听的socket对象%d' % len(inputs))
print(r_list)
for sk1_or_conn in r_list:
#每一个连接对象
if sk1_or_conn == sk1:
#表示有新用户来连接
conn, address = sk1_or_conn.accept()
inputs.append(conn)
message_dict[conn] = []
else:
#有老用户发消息
try: data_str = str(data_bytes, encoding="utf-8")
message_dict[sk1_or_conn].append(data_str)
outputs.append(sk1_or_conn)
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)

客户端

import socket

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

socketserver

从而实现并发处理多个客户端请求的Socket服务端
 使用:
  1,创建类,必须继承(socketserver.BaseRequestHandler)
  2,handle方法
  3,server_forever 循环着等待着客户端连接

服务端

  并发处理多个客户端请求     

import socketserver  #进行并发处理,接受多个客户端的请求

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
conn = self.request
conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8'))
while True:
ret_bytes = conn.recv(1024)
ret_str = str(ret_bytes,encoding='utf-8')
if ret_str == 'q':
break
conn.sendall(bytes(ret_str + " 你好", encoding='utf-8')) if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999),MyServer) #MYserver就是创建的类
server.serve_forever() #循环着等待着客户端连接

客户端

  多进程的与服务端连接

import socket

obj = socket.socket()

obj.connect(('127.0.0.1', 9999,))

ret_bytes = obj.recv(1024)
ret_str = str(ret_bytes, encoding='utf')
print(ret_str)
while True:
inp = input('请输入要发送的内容:')
if inp =='q':
obj.sendall(bytes(inp, encoding='utf-8'))
break
else:
obj.sendall(bytes(inp, encoding='utf-8'))
ret = str(obj.recv(1024), encoding='utf-8')
print(ret)
obj.close()

ThredingTCPServer分析

ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

1,ThreadingTCPServer基础

  • 创建一个继承自 SocketServer.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer

2,ThreadingTCPServer源码剖析

网络编程socket

内部调用流程为:

  • 启动服务端程序
  • 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
  • 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给self.RequestHandlerClass
  • 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
  • 当客户端连接到达服务器
  • 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
  • 执行 ThreadingMixIn.process_request_thread 方法
  • 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

文件续传

服务端

import socket
import os BASE_DIR=os.path.dirname(os.path.abspath(__file__)) #文件名的上一级目录 home=os.path.join(BASE_DIR,"home/yuan") #在拼接路径
ip_port=("127.0.0.1",8998)
sk=socket.socket()
sk.bind(ip_port)
sk.listen(5) while True:
print("waiting ....")
conn,addr=sk.accept()
conn.sendall(bytes("欢迎登录","utf8"))
flag=True
while flag:
client_bytes=conn.recv(1024) #拿到客户端发来的文件名文件大小文件路径这三个参数
client_str=str(client_bytes,"utf8") #把获得的字节转换字符串 func,file_name,file_byte_size,target_path=client_str.split("|",3) #分解发来的三个参数
file_byte_size=int(file_byte_size) #
path=os.path.join(home,file_name) #拼接的路径 has_received=0 if os.path.exists(path): #判断是否有文件
conn.sendall(bytes("","utf8")) #返回一个值做一次交互,表示文件存在
is_continue=str(conn.recv(1024),"utf8") #等待客户端操作
if is_continue=="":
has_file_size=os.stat(path).st_size #得到上次上次传送的文件大小
conn.sendall(bytes(str(has_file_size),"utf8")) #服务端发送给客户端,告诉上次传送的文件大小
has_received+=has_file_size #把之前上传的文件与续传的文件进行想加 f=open(path,"ab") #打开文件进行追加
else:
f=open(path,"wb") #否则就进行写入
else:
conn.sendall(bytes("","utf8")) #
f=open(path,"wb") while has_received<file_byte_size: #判断发送过的文件与要上传文件进行比较,如果小于文件的大小就一直进行上传直到上传完成
try:
data=conn.recv(1024) #对出现客户端进行中断的处理
if not data:
raise Exception
except Exception:
flag=False
break f.write(data)
has_received+=len(data)
print("ending")
f.close()

客户端

import socket
import re,os,sys ip_port=("127.0.0.1",8998)
sk=socket.socket()
sk.connect(ip_port)
print(str(sk.recv(1024),"utf8")) #def bar(num=1, sum=100): # rate = float(num) / float(sum)
# rate_num = int(rate * 100)
# temp = '\r%d %%' % (rate_num, )
# sys.stdout.write(temp)
# sys.stdout.flush()
while True:
inp=input("please input:").strip() #拿到输入值
func,file_path=inp.split("|",1) #用|分开方法名和路径
local_path,target_path=re.split("\s*",file_path,1) #通过正则的\s*(空格) 一个或是多个空格分开本地路径和目标路径
file_byte_size=os.stat(local_path).st_size #获取文件大小
file_name=os.path.basename(local_path) #拿到文件名称 post_info="post|%s|%s|%s"%(file_name,file_byte_size,target_path) #拼接文件名、文件大小和文件路径
sk.sendall(bytes(post_info,"utf8")) #拼接后上传到服务端 result_exist=str(sk.recv(1024),"utf8") #接受服务端传过来的文件存在的返回值
has_sent=0
if result_exist=="":
inp=input("文件存在,是否续传?Y/N").strip()
if inp.upper()=="Y": #
sk.sendall(bytes("","utf8")) #告诉服务端进行断点续传
result_continue_pos=str(sk.recv(1024),"utf8") #
print(result_continue_pos)
has_sent=int(result_continue_pos) #已经上传文件的大小 else:
sk.sendall(bytes("","utf8")) #不进行续传 file_obj=open(local_path,"rb") #在绝对路径下读取内容
file_obj.seek(has_sent) #寻找到指针的位置(上次传到哪就跳到哪,如果没有传就从开始进行读取) while has_sent<file_byte_size:
data=file_obj.read(1024)
sk.sendall(data) #发送给服务端
has_sent+=len(data) #一次最多传送1024,超过那就进行累加 #进度条
def bar(num=1, sum=100): rate = float(num) / float(sum)
rate_num = int(rate * 100)
temp = '\r%d %%' % (rate_num, )
sys.stdout.write(temp)
sys.stdout.flush() bar(has_sent,file_byte_size)
file_obj.close()
print("上传成功")

详细请参考:http://www.cnblogs.com/wupeiqi/articles/5040823.html