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_INET, type=SOCK_STREAM, proto=0, fileno=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
(host, port, family=0, type=0, proto=0, flags=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
(file, offset=0, count=None)
发送文件 ,但目前多数情况下并无什么卵用。
1.3 通过socket实现简单的ssh
程序流程如下:
程序代码:
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.md59.关闭文件程序代码:客户端:
# -*- 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的使用
注:由于其涉及线程的相关知识(还未学习),在学习完线程之后,再来写socketServer的相关概念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() #处理多个请求,即可以连接多个客户端
未完待续。。。。。。。。
原文地址:https://www.cnblogs.com/BUPT-MrWu/p/9903544.html