python第八周:socket网络编程

时间:2021-09-05 12:15:32

1.socket网络编程

1.1概念:

网络套接字是跨计算机网络的连接的端点。今天,计算机之间的大多数通信都基于互联网协议;因此大多数网络套接字都是Internet套接字。更准确地说,套接字是一个句柄(抽象引用),本地程序可以将其传递给网络应用程序编程接口(API)以使用该连接,例如“在此套接字上发送此数据”。

例如,发送“Hello,world!”通过TCP到地址为1.2.3.4的主机的端口80,可以获得一个套接字,将其连接到远程主机,发送字符串,然后关闭套接字。

实现一个socket至少要分以下几步,(伪代码):

1.Socket socket = getSocket(type = "TCP"#设定好协议类型
2.connect(socket, address = "1.2.3.4", port = "80") #连接远程机器
3.send(socket, "Hello, world!") #发送消息
4.close(socket) #关闭连接

套接字API是一种应用程序编程接口(API),通常由操作系统提供,允许应用程序控制和使用网络套接字。 Internet套接字API通常基于Berkeley套接字标准。在Berkeley套接字标准中,套接字是文件描述符(文件句柄)的一种形式,由于Unix哲学“一切都是文件”,以及套接字和文件之间的类比:你可以读,写,打开和关闭。

套接字地址是IP地址和端口号的组合,很像电话连接的一端是电话号码和特定分机的组合。 套接字不需要有地址(例如仅用于发送数据),但如果程序将套接字绑定到地址,则套接字可用于接收发送到该地址的数据。 基于此地址,Internet套接字将传入的数据包传递到适当的应用程序进程或线程

Socket Families(地址簇)

socket.AF_UNIX unix本机进程间通信

socket.AF_INET IPV4 

socket.AF_INET6  IPV6

这些常量表示用于socket()的第一个参数的地址(和协议)系列。 如果未定义AF_UNIX常量,则不支持此协议。 根据系统的不同,可能会有更多常量可用。

Socket Types

socket.SOCK_STREAM  #for tcp

socket.SOCK_DGRAM  #for udp

socket.SOCK_RAW   #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通

          过IP_HDRINCL套接字选项由用户构造IP头。

socket.SOCK_RDM  #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。

          SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。

socket.SOCK_SEQPACKET #废弃了

这些常量表示套接字类型,用于socket()的第二个参数。 根据系统的不同,可能会有更多常量可用。 (只有SOCK_STREAM和SOCK_DGRAM似乎通常很有用。)

1.2 Socket 参数

(1)socket.socket(family=AF_INETtype=SOCK_STREAMproto=0fileno=None)  必会

使用给定的地址系列,套接字类型和协议号创建一个新套接字。 地址族应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 套接字类型应该是SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或者其他SOCK_常量之一。 协议号通常为零并且可以省略,或者在地址族是AF_CAN的情况下,协议应该是CAN_RAW或CAN_BCM之一。 如果指定了fileno,则忽略其他参数,从而返回具有指定文件描述符的套接字。 与socket.fromfd()不同,fileno将返回相同的套接字而不是重复。 这可能有助于使用socket.close()关闭分离的套接字。

(2)socket.socketpair([family[, type[, proto]]])

使用给定的地址系列,套接字类型和协议编号构建一对连接的套接字对象。 地址族,套接字类型和协议号与上面的socket()函数相同。 如果在平台上定义,则默认系列为AF_UNIX; 否则,默认为AF_INET。

(3)socket.create_connection(address[, timeout[, source_address]])

连接到侦听Internet地址(2元组(主机,端口))的TCP服务,并返回套接字对象。 这是一个比socket.connect()更高级的函数:如果host是非数字主机名,它将尝试为AF_INET和AF_INET6解析它,然后尝试依次连接到所有可能的地址,直到连接成功。 这样可以轻松编写与IPv4和IPv6兼容的客户端。
传递可选的timeout参数将在尝试连接之前设置套接字实例上的超时。 如果未提供超时,则使用getdefaulttimeout()返回的全局默认超时设置。
如果提供,则source_address必须是要连接的套接字的2元组(主机,端口)作为其源地址才能连接。 如果主机或端口分别为'或0,则将使用OS默认行为。

(4) socket.getaddrinfo(hostportfamily=0type=0proto=0flags=0) #获取要连接的对端主机地址

(5) sk.bind(address) 必会

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

(6) sk.listen(backlog) 必会

开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5。这个值不能无限大,因为要在内核中维护连接队列

(7)sk.setblocking(bool) 必会

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

(8) sk.accept() 必会

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

(9)sk.connect(address) 必会

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

(10)sk.connect_ex(address)

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

(11)sk.close() 必会

关闭套接字

(12)sk.recv(bufsize[,flag]) 必会

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

(13)sk.recvfrom(bufsize[.flag])

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

(14)sk.send(string[,flag]) 必会

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

(15)sk.sendall(string[,flag]) 必会

将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。

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

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

(17)sk.settimeout(timeout) 必会

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

(18)sk.getpeername()  必会

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

(19)sk.getsockname()

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

(20)sk.fileno() 

套接字的文件描述符

(21)socket.sendfile(fileoffset=0count=None)

发送文件 ,但目前多数情况下并无什么卵用。

1.3 通过socket实现简单的ssh

程序流程如下:

python第八周:socket网络编程

程序代码:

socket_server端:

import socket,os

server = socket.socket()
server.bind(("localhost",9999))
server.listen()
while True:
conn,addr = server.accept() #阻塞
while True:
print("wait for new cmd:")
cmd = conn.recv(1024).decode()
if not cmd:
print("客户端已断开")
break
cmd_result = os.popen(cmd).read()
if len(cmd_result) == 0:
cmd_result = "这条命令错误"
length = len(cmd_result.encode("utf-8"))
length = len(cmd_result.encode("utf-8"))
conn.send(str(length).encode("utf-8"))
conn.recv(1024)
conn.send(cmd_result.encode("utf-8"))
server.close()

socket_client端:

import socket

client = socket.socket()
client.connect(("localhost",9999))
while True:
cmd = input("输入指令:")
if len(cmd) == 0:
continue
client.send(cmd.encode("utf-8"))
length = client.recv(1024).decode()
print("命令结果大小:",length)
receive_size = 0
receive_data = b""
client.send(b"OK")
while receive_size < int(length):
data = client.recv(1024)
receive_size += len(data)
receive_data += data
print("命令结果实际大小:",receive_size)
print(receive_data.decode())
client.close()

程序运行结果:

#server端:
wait for new cmd:
ipconfig
wait for new cmd: #client端:
输入指令:ipconfig
命令结果大小: 1680
命令结果实际大小: 1680 Windows IP 配置 以太网适配器 以太网: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . : 无线局域网适配器 本地连接* 3: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . : 无线局域网适配器 本地连接* 12: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . : 以太网适配器 VMware Network Adapter VMnet1: 连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::7ddd:a3e4:9673:512e%7
IPv4 地址 . . . . . . . . . . . . : 192.168.74.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 以太网适配器 VMware Network Adapter VMnet8: 连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::4cc1:5dc2:37f:7e7b%17
IPv4 地址 . . . . . . . . . . . . : 192.168.43.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 无线局域网适配器 WLAN: 连接特定的 DNS 后缀 . . . . . . . :
IPv6 地址 . . . . . . . . . . . . : 2001:da8:215:8f01:8d1d:db29:3fd2:c6d6
临时 IPv6 地址. . . . . . . . . . : 2001:da8:215:8f01:dc75:865e:ccf6:e781
本地链接 IPv6 地址. . . . . . . . : fe80::8d1d:db29:3fd2:c6d6%10
IPv4 地址 . . . . . . . . . . . . : 10.122.252.64
子网掩码 . . . . . . . . . . . . : 255.255.192.0
默认网关. . . . . . . . . . . . . : fe80::274:9cff:fe7d:fadb%10
10.122.192.1 输入指令:

1.4 通过socket实现简单的ftp server

1.读取文件名
2.检测文件是否存在
3.打开文件
4.检测文件大小
5.发送文件大小和md5给客户端
6.等客户端确认
7.开始边读边发数据
8.md5
9.关闭文件

程序代码:
客户端:
# -*- coding:utf-8 -*-
#!/user/bin/env.python
#Author:Mr Wu '''FTP Server'''
import socket,os,hashlib server = socket.socket()
server.bind(("localhost",1999))
server.listen()
while True:
conn,addr = server.accept() #阻塞
while True:
data = conn.recv(1024).decode()
if not data:
print("客户端已断开连接!")
break
file_cmd,file_name = data.split()
if os.path.isfile(file_name):
file_size = os.stat(file_name).st_size
conn.send(str(file_size).encode("utf-8"))
conn.recv(1024) #避免粘包
m = hashlib.md5()
f = open(file_name,"rb")
print("开始发送文件.....")
for line in f:
m.update(line)
conn.send(line)
f.close()
print("发送md5.......")
conn.send(m.hexdigest().encode("utf-8"))
server.close()
服务端:
# -*- coding:utf-8 -*-
#!/user/bin/env.python
#Author:Mr Wu import socket,os,hashlib client = socket.socket()
client.connect(("localhost",1999))
while True:
cmd = input("输入文件名[格式:get 文件名]>>>:").strip()
if len(cmd) == 0:
continue
if cmd.startswith("get"):
file_name = cmd.split()[1]
client.send(cmd.encode("utf-8"))
data = client.recv(1024)
file_total_size = int(data.decode())
client.send(b"OK")
file_size = 0
f = open(file_name,"wb")
m = hashlib.md5()
while file_size < file_total_size:
last_size = file_total_size - file_size
if last_size < 1024:
size = last_size
else:
size = 1024
'''避免粘包'''
data = client.recv(size)
m.update(data)
f.write(data)
file_size =+ len(data)
f.close()
md5 = m.hexdigest()
received_md5 = client.recv(1024).decode()
if md5 == received_md5:
print("文件md5一致,传输成功!")
else:
print("文件传输错误!")
else:
print("输入格式错误!")
continue
运行结果:
server端:
开始发送文件.....
发送md5.......
client端:
输入文件名[格式:get 文件名]>>>:get test.py
文件md5一致,传输成功!

2.socketServer的使用
2.0socketserver主要包含的类:
class socketserver.TCPServer(server_addressRequestHandlerClassbind_and_activate=True)
它使用Internet TCP协议,该协议在客户端和服务器之间提供连续的数据流。 如果bind_and_activate为true,
则构造函数会自动尝试调用server_bind()和server_activate()。 其他参数将传递给BaseServer基类。
class socketserver.UDPServer(server_addressRequestHandlerClassbind_and_activate=True)
这使用数据报,这些数据报是可能无序到达或在传输过程中丢失的离散信息包。 参数与TCPServer相同。
class socketserver.UnixStreamServer(server_addressRequestHandlerClassbind_and_activate=True)
class socketserver.UnixDatagramServer(server_addressRequestHandlerClass,bind_and_activate=True)
这些不经常使用的类类似于TCP和UDP类,但使用Unix域套接字; 它们不适用于非Unix平台。 参数与TCPServer相同。
这四个类同步处理请求; 必须在下一个请求开始之前完成每个请求。 如果每个请求需要很长时间才能完成,这是不合适的,
因为它需要大量计算,或者因为它返回了客户端处理速度慢的大量数据。 解决方案是创建一个单独的进程或线程来处理每个请求;
ForkingMixIn和ThreadingMixIn混合类可用于支持异步行为。
继承图中有五个类,其中四个代表四种类型的同步服务器:

2.1 使用方法:
(1)你必须自己创建一个处理类,并且这个类要继承BaseRequestHandler,并且还要重写父类里的handle()
(2)你必须实例化TCPServer,并且传递server ip和你上面创建的请求处理类给这个TCPServer
(3)server.handle_request() #只处理一个请求
server.serve_forever() #处理多个连接请求,永远执行
2.2 SocketServer程序示例:

# -*- coding:utf-8 -*-
#!/user/bin/env.python
#Author:Mr Wu import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
'''每一个请求过来都会实例化MyTCPHandler'''
def handle(self):
'''与客户端所有的交互都是在handle里完成的'''
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
#if not self.data:
# print("客户端断开连接")
# break
#just send the same data,but upper-cased
self.request.send(self.data.upper())
except ConnectionResetError as e:
print(e)
break
if __name__ == "__main__":
HOST,PORT = "localhost",9999
#create the server,binding to localhost on port 9999
server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)
'''
activate the server:this will keep running until you
interrupt the program with Ctrl-C
'''
server.serve_forever() #处理多个请求,即可以连接多个客户端
未完待续。。。。。。。。