zoukankan      html  css  js  c++  java
  • python3.0_day9_scoket基础之篇

    一、socket简单介绍

      socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

      socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

      1、socket与file区别: 

    • file模块是针对某个指定文件进行【打开】【读写】【关闭】
    • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

      2、socket与TCP/IP关联及区别?

      socket:只负责处理数据,不负责数据传输,数据传输是按照TCP/IP协议进行传递

      TCP/IP协议:主机如何接入互联网以及接入互联网两台主机通信标准

      

      TCP/IP协议四层架构:数据传输格式:ethernet报头+IP报头+TCP报头+http数据包

      socker抽象层:只负责数据处理,不负责数据传递,数据按照TPC/IP协议进行传输

      3、简单数据传输:

      A:socket-server

    import  socket
    ip_port = ('127.0.0.1',1009)
    # #绑定协议,生成套接字
    sk=socket.socket()
    ##绑定ip+协议+端口:用来唯一标识一个进程,ip_port必须是元组格式
    sk.bind(ip_port)
    #定义最大可以挂起胡链接数
    sk.listen(5)
    conn,addr = sk.accpet() #conn:表示与客户端连接线路 while True: print('等待客户端发送消息、、、') #接收客户端发来的消息 client_data = conn.recv(1024) #recv:阻塞, print("----->",type(client_data)) print(client_data) #发消息 send_data = client_data.upper() #将客户端接收消息转换至大写字母 print(send_data) #打印出要发给客户端信息 conn.sendall(send_data) #将信息发送给客户端 conn.close() #关闭连接

       B:socket_clint

    #python3.0:只支持字节方式传递,python2.0:可以按照字符串方式传递
    #客户端
    import  socket
    ip_port = ('127.0.0.1',1009)  #(绑定IP+端口---》必须是一个元祖形式)
     #绑定协议,生成套接字
    sk = socket.socket()
    #链接服务器,如果服务器已经存在一个正常的链接,那么挂起
    sk.connect(ip_port)
    #发消息
    send_data = input(">>:").strip()  #去除输入空的
    #客户端发送消息至服务器端
    sk.sendall(bytes(send_data,encoding='utf-8')) #python3.0:只支持字节方式传递,python2.0:可以按照字符串方式传递
    #接收服务器端返回数据
    server_reply = sk.recv(1024)
    # print("---->",type(server_reply))
    #将字节转换至字符串形式
    print(str(server_reply,encoding='utf-8'))
    # print(server_reply)
    sk.close()  #关闭连接
    

       【1、套接字错误:python3.5版本后套接字只能发字节数(bytes),不能发字符串,在python2.7中支持发字符串形式(str)

       2、退出只在客户端退出就ok了

       3、S.accept()和s.recv()是堵塞(基于链接正常)

       4、listen(n):n代表:能挂起的链接数,如果n=1,代表可以链接的一个,挂起一个,第三个拒绝链接】   

      二、socket参数

      绑定协议,生成套接字规则:sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 

    参数一:地址簇
      socket.AF_INET IPv4(默认)
      socket.AF_INET6 IPv6
    
      socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
    参数二:类型
      socket.SOCK_STREAM  流式socket , for TCP (默认)
      socket.SOCK_DGRAM   数据报式socket , 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 可靠的连续数据包服务
    参数三:协议
      0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
    

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

      套接字的文件描述符

      1、客户端与服务器多次传输数据

      A:客户端  

    #客户端
    #增加客户端重复发送数据
    import  socket
    ip_port = ('127.0.0.1',1009)
     #绑定协议,生成套接字
    sk = socket.socket()
    #链接服务器,如果服务器已经存在一个正常的链接,那么挂起
    sk.connect(ip_port)
    #发消息
    while True:
    	send_data = input(">>:").strip()  #去除输入空的
    	#客户端发送消息至服务器端
    	sk.sendall(bytes(send_data,encoding='utf-8')) #python3.0:只支持字节方式传递,python2.0:可以按照字符串方式传递
    	#接收服务器端返回数据
    	server_reply = sk.recv(1024)
    	#将字节转换至字符串形式
    	print(str(server_reply,encoding='utf-8'))
    sk.close()  #关闭套接字
    

       B:服务器端:  

    import  socket
    ip_port = ('127.0.0.1',1009)
    # #绑定协议,生成套接字
    sk=socket.socket()
    ##绑定ip+协议+端口:用来唯一标识一个进程,ip_port必须是元组格式
    sk.bind(ip_port)
    #定义最大可以挂起胡链接数
    sk.listen(5)
    conn, addr = sk.accept()  # conn:表示与客户端连接线路
    while True:
    	print('等待客户端发送消息、、、')
    	#接收客户端发来的消息
    	client_data = conn.recv(1024) #recv:阻塞,
    	print("----->",type(client_data))
    	print(client_data)
    	#发消息
    	send_data = client_data.upper() #将客户端接收消息转换至大写字母
    	print(send_data)  #打印出要发给客户端信息
    	conn.sendall(send_data)  #将信息发送给客户端
    conn.close()  #关闭连接
    

       2、客户端与服务器多次传输数据(优化2)

      客户端:  

    #客户端
    #增加客户端重复发送数据
    #增加:如果客户端输入字符串长度为0,程序则会跳出本次循环
    import  socket
    ip_port = ('127.0.0.1',1009)
     #绑定协议,生成套接字
    sk = socket.socket()
    #链接服务器,如果服务器已经存在一个正常的链接,那么挂起
    sk.connect(ip_port)
    #发消息
    while True:
    	send_data = input(">>:").strip()  #去除输入空的
    	# 如果发送字符长度为0,则终止下面程序运行
    	# if len(send_data) == 0:break  #当服务器端为配置此项,客户端开始输入字符长度为0,则服务器端出现死循环
    	if len(send_data) == 0:continue  #当客户端输入字符串长度为0,则跳出本次循环,执行下面语句
    	#客户端发送消息至服务器端
    	sk.sendall(bytes(send_data,encoding='utf-8')) #python3.0:只支持字节方式传递,python2.0:可以按照字符串方式传递
    	if send_data == 'exit':break
    	#接收服务器端返回数据
    	server_reply = sk.recv(1024)
    	#将字节转换至字符串形式
    	print(str(server_reply,encoding='utf-8'))
    sk.close()  #关闭套接字
    

        服务器端:  

    #服务器端
    #增加:异常处理逻辑,捕获异常信息
    import  socket
    ip_port = ('127.0.0.1',1009)
    # #绑定协议,生成套接字
    sk=socket.socket()
    ##绑定ip+协议+端口:用来唯一标识一个进程,ip_port必须是元组格式
    sk.bind(ip_port)
    #定义最大可以挂起胡链接数
    sk.listen(5)
    conn, addr = sk.accept()  # conn:表示与客户端连接线路
    while True:
    	try:
    		print('等待客户端发送消息、、、')
    		#接收客户端发来的消息
    		client_data = conn.recv(1024) #recv:阻塞,
    		print("----->",type(client_data))
    		if str(send_data,encoding='utf-8')== 'exit': break
    		print(client_data)
    		#发消息
    		send_data = client_data.upper() #将客户端接收消息转换至大写字母
    		print(send_data)  #打印出要发给客户端信息
    		conn.sendall(send_data)  #将信息发送给客户端
    	except Exception:
    		break
    conn.close()  #关闭连接
    

       3、 服务器与多个客户端连接,当其中一个客户端断开之后,服务器继续连接下一个请求

      A:服务器:  

    #解决问题:服务器连接多个请求时,当一个请求断开后,服务器继续连接下一个请求
    #增加代码:在服务器端,对conn进行重复连接,并在运用try  except
    #服务器
    import socket
    import  subprocess
    ip_dum =('127.0.0.1',9999)
    #买手机
    s = socket.socket()   #建立socket连接
    # print(s)  <socket.socket fd=220, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
    #买手机卡
    s.bind(ip_dum)
    #开机
    s.listen(5)   #设置拦截数量
    #等待电话
    while True:    #循环连接
    	conn,addr = s.accept()  #conn表示
    #接消息
    	while True:
    		try:   #解决功能:当客户端断开后,重新链接下一个请求
    			recv_data = conn.recv(1024)
    			print("--->",type(recv_data))
    			if len(recv_data) ==0:break   #当客户端输入长度为0,则跳过本次循环
    			#if str(recv_data,encoding='utf-8') == 'exit':break
    			#发信息     如果客户端输出错误信息,则将错误信息提示,并
    			#p = subprocess.Popen(str(recv_data,encoding='utf-8'),shell=True,stdout=subprocess.PIPE)
    			send_data = recv_data.upper()
    			print(send_data)
    			conn.sendall(send_data)
    		except Exception:
    			break   #异常时,直接退出
    #挂电话
    conn.close()
    

        B:客户端01:

    #客户端:
    import socket
    ip_dum = ('127.0.0.1',9999)
    #买手机
    s = socket.socket()
    print(s)
    #拨号
    s.connect(ip_dum)
    #发消息
    
    while True:
    	send_data =input(">>:").strip()
    	if len(send_data)=='exit':break
    	if len(send_data) ==0:continue   #当客户端输入长度为0,则跳过本次循环
    	s.send(bytes(send_data,encoding='utf-8'))
    	if send_data == 'exit':break
    	#接收消息
    	rece_data = s.recv(1024)
    	print(str(rece_data,encoding='utf-8'))
    #关闭
    s.close()
    

       C:客户端02  

    #客户端:
    import socket
    ip_dum = ('127.0.0.1',9999)
    #买手机
    s = socket.socket()
    print(s)
    #拨号
    s.connect(ip_dum)
    #发消息
    while True:
    	send_data =input(">>:").strip()
    	if len(send_data)=='exit':break
    	if len(send_data) ==0:continue   #当客户端输入长度为0,则跳过本次循环
    	s.send(bytes(send_data,encoding='utf-8'))
    	if send_data == 'exit':break
    	#接收消息
    	rece_data = s.recv(1024)
    	print(str(rece_data,encoding='utf-8'))
    #关闭
    s.close()
    

       4、socket与windows简单交互

      A:服务器  

    #解决功能:用socket与windows进行交互,并将windows编码格式gbk-->str--->utf-8传给的客户端
    #客户端输错命令时服务器端给错误提示并将结果返回给客户端
    #服务器
    import socket
    import subprocess  #执行命令
    ip_dum =('127.0.0.1',9999)
    #买手机
    s = socket.socket()   #建立socket连接
    # print(s)  <socket.socket fd=220, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
    #买手机卡
    s.bind(ip_dum)
    #开机
    s.listen(5)   #设置拦截数量
    #等待电话
    while True:    #
    	conn,addr = s.accept()  #conn表示
    #接消息
    	while True:
    		try:   #解决功能:当客户端断开后,重新链接下一个请求
    			recv_data = conn.recv(1024)
    			print("--->",type(recv_data))
    			if len(recv_data) ==0:break   #当客户端输入长度为0,则跳过本次循环
    			#if str(recv_data,encoding='utf-8') == 'exit':break
    			#发信息     如果客户端输出错误信息,则将错误信息提示,
    			p = subprocess.Popen(str(recv_data,encoding='utf-8'),shell=True,stdout=subprocess.PIPE)#执行系统命令,windows平台
    			ret = p.stdout.read()  #获取标准输出,获取windows输出数字并赋值给ret                                 #命令的标准输出是gbk编码,需要转换至utf8
    			if len(ret)==0:  ##执行错误命令,标准输出为空,
    				send_data='cmd error'
    			else:
    				send_data = str(ret,encoding='gbk')  #windows下输出gbk方式
    			print(send_data)
    			conn.sendall(bytes(send_data,encoding='utf-8'))  #j将gbk编码--》str---》字节utf8编码,python3.5:支持字符码形式进行传递值,将返回数据发送给客户端
    		except Exception:
    			break   #异常时,直接退出
    #挂电话
    conn.close()
    

       B:客户端:  

    #客户端:
    import socket
    ip_dum = ('127.0.0.1',9999)
    #买手机
    s = socket.socket()
    print(s)
    #拨号
    s.connect(ip_dum)
    #发消息
    
    while True:
    	send_data =input(">>:").strip()
    	if len(send_data)=='exit':break
    	if len(send_data) ==0:continue   #当客户端输入长度为0,则跳过本次循环
    	s.send(bytes(send_data,encoding='utf-8'))
    	if send_data == 'exit':break
    	#接收消息
    	rece_data = s.recv(1024) #recv:堵塞,当服务器返回一个空值,客户端则recv堵塞
    	print(str(rece_data,encoding='utf-8'))
    #关闭
    s.close()

     三、socker粘包解析

      A:socker-server  

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import socket
    import subprocess #导入执行命令模块
    ip_port=('127.0.0.1',9999) #定义元祖
    #买手机
    s=socket.socket()  #绑定协议,生成套接字
    s.bind(ip_port)    #绑定ip+协议+端口:用来唯一标识一个进程,ip_port必须是元组格式
    s.listen(5)        #定义最大可以挂起胡链接数
    #等待电话
    while True:  #用来重复接收新的链接
        conn,addr=s.accept()   #接收客户端胡链接请求,返回conn(相当于一个特定胡链接),addr是客户端ip+port
        #收消息
        while True: #用来基于一个链接重复收发消息
                try: #捕捉客户端异常关闭(ctrl+c)
                    recv_data=conn.recv(1024) #收消息,阻塞
                    if len(recv_data) == 0:break #客户端如果退出,服务端将收到空消息,退出
    
                    #发消息
                    p=subprocess.Popen(str(recv_data,encoding='utf8'),shell=True,stdout=subprocess.PIPE) #执行系统命令,windows平
                                                                                                          # 台命令的标准输出是gbk编码,需要转换
                    res=p.stdout.read()   #获取标准输出
                    if len(res) == 0:   #执行错误命令,标准输出为空,
                        send_data='cmd err'
                    else:
                        send_data=str(res,encoding='gbk')  #命令执行ok,字节gbk---->str---->字节utf-8
    
                    send_data=bytes(send_data,encoding='utf8')
    
                    #解决粘包问题
                    ready_tag='Ready|%s' %len(send_data)
                    conn.send(bytes(ready_tag,encoding='utf8')) #发送数据长度
                    feedback=conn.recv(1024)  #接收确认信息
                    feedback=str(feedback,encoding='utf8')
    
                    if feedback.startswith('Start'):
                        conn.send(send_data)  #发送命令的执行结果
                except Exception:
                    break
        #挂电话
        conn.close()
    

       B:socker-cilent

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import socket
    ip_port=('127.0.0.1',9999)
    #买手机
    s=socket.socket()
    #拨号
    s.connect(ip_port)  #链接服务端,如果服务已经存在一个好的连接,那么挂起
    
    while True:        #基于connect建立的连接来循环发送消息
        send_data=input(">>: ").strip()
        if send_data == 'exit':break
        if len(send_data) == 0:continue
        s.send(bytes(send_data,encoding='utf8'))
    
        #解决粘包问题
        ready_tag=s.recv(1024) #收取带数据长度的字节:Ready|9998
        ready_tag=str(ready_tag,encoding='utf8')
        if ready_tag.startswith('Ready'):#Ready|9998
            msg_size=int(ready_tag.split('|')[-1])  #获取待接收数据长度
        start_tag='Start'
        s.send(bytes(start_tag,encoding='utf8')) #发送确认信息
    
        #基于已经收到的待接收数据长度,循环接收数据
        recv_size=0
        recv_msg=b''
        while recv_size < msg_size:
            recv_data=s.recv(1024)
            recv_msg+=recv_data
            recv_size+=len(recv_data)
            print('MSG SIZE %s RECE SIZE %s' %(msg_size,recv_size))
    
        print(str(recv_msg,encoding='utf8'))
        #挂电话
    s.close()
    

       三、socket-server 

      1、SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进 程” 专门负责处理当前客户端的所有请求。

      

       2、python3.0与python2.7关于socketserver差异:

      A:python3.0中导入socketserver中server为小写

      B:python2.7中打入SocketServer中Server需要大写

      2)socketserver实现多并发:

      socketserver :  

    import  socketserver
    #每一次新连接进来就实例化Myserver类
    class Myserver(socketserver.BaseRequestHandler):#继承父类:socketserver.BaseRequestHandler中所有方法
    	def handle(self):  #方法名字必须是:handle,因为基类已写好
    		self.request.sendall(bytes("欢迎致电中国银行",encoding='utf-8'))  #server ---->client
    		while True:
    			data = self.request.recv(1024) #接收消息
    			print("[%s] says:%s"%(self.client_address,data.decode()))
    			self.request.sendall(data.upper()) #self.request ==conn  建立连接并将信息发送给客户端
    if __name__ =='__main__':
    	server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),Myserver)  #将Myserver实例化
    	server.serve_forever()  #serve_forever循环等待新连接
    

        socketclient:  

    import socket
    ip_port = ('127.0.0.1',9999)
    sk = socket.socket() 
    sk.connect(ip_port)
    sk.settimeout(5)
    #发消息
    welcome_msg = sk.recv(1024)
    print("from server:",welcome_msg.decode())
    while True:
    	send_data = input(">>:").strip()
    	if len(send_data)=='0':continue
    	sk.sendall(bytes(send_data,encoding='utf-8'))
    	#收消息
    	recv_data = sk.recv(1024)
    	print(str(recv_data,encoding='utf-8'))
    
    sk.close()
    

       3)解决多并发时其中一个客户端断了,服务器仍然继续工作,不出现死循环

        解决方法:在接收客户端发来的消息中,判断消息的长度是否为0,如为0则break

    import  socketserver
    #每一次新连接进来就实例化Myserver类
    class Myserver(socketserver.BaseRequestHandler):#继承父类:socketserver.BaseRequestHandler中所有方法
    	def handle(self):  #方法名字必须是:handle,因为基类已写好
    		self.request.sendall(bytes("欢迎致电中国银行",encoding='utf-8'))  #server ---->client
    		while True:
    			data = self.request.recv(1024) #接收消息
    			print("--->",len(data))  #打印接收消息长度
    			if len(data) =='0':break   #判断接收消息长度为0,则结束
    			print("[%s] says:%s"%(self.client_address,data.decode()))
    			self.request.sendall(data.upper()) #self.request ==conn  建立连接并将信息发送给客户端
    if __name__ =='__main__':
    	server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),Myserver)  #将Myserver实例化
    	server.serve_forever()  #serve_forever循环等待新连接
    

       4)

      

  • 相关阅读:
    Fix Installing .NET Framework 3.5 failed Error Code 0x800F0954 on Windows 10
    RHEL8安装五笔输入法
    Enable EPEL and Local Repository on RHEL8
    Why is Yum Replaced by DNF?
    检查Linux服务器是否被攻击的常用命令及方法
    IDEA 主题
    IDEA 如何显示一个类中所有的方法
    Appium 安装以及安装过程中遇到的问题
    Maven 如何发布 jar 包到 Nexus 私库
    java泛型的基本使用
  • 原文地址:https://www.cnblogs.com/lcj0703/p/5639192.html
Copyright © 2011-2022 走看看