一、socket

socket概念

socket(俗称:套接字)本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

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

socket和file的区别:

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

建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。

代码示例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

ip_port = ('127.0.0.1',9999)
sk = socket.socket() #创建socket对象
sk.bind(ip_port)  #绑定地址
sk.listen(5)  #开启端口监听, #设置5个监听,(监听的队列为5)那么超过5个,就会将第6个拒绝掉

while True:  #使用死循环实现不间断监听
    print ('server waiting...')
    # 客户端向服务端发送sk.connect(ip_port),连接服务器通过该语句在此处形成阻塞,知道有客户端发起连接请求,并同意客户端的连接。并返回双方的连接通道,和客户端的地址
    conn,addr = sk.accept()  #阻塞。阻塞时长:永远,直到有客户端连接
#获取数据时,使用连接通道去接收。 #每次接收的最大字节数为1024。
    client_data = str(conn.recv(1024),encoding="utf-8")
    print (client_data)
    conn.sendall(bytes('不要回答,不要回答,不要回答',encoding="utf-8"))
    conn.close()
socket server
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
ip_port = ('127.0.0.1',9999)  #定义的是一个元组

sk = socket.socket()  #创建socket对象
sk.connect(ip_port)  #连接服务器

sk.sendall(bytes('请求占领地球',encoding="utf-8"))  #发送数据到服务器。注意只能是字节。

server_reply = str(sk.recv(1024),encoding="utf-8")   #定义是每次接收长度最长为1024字节。
print (server_reply)

sk.close()
socket client

注意:所有的web框架都是基于socket建立的。

web服务器的应用:

#!/usr/bin/env python
#coding:utf-8
import socket
  
def handle_request(client):
    buf = client.recv(1024)
    client.send("HTTP/1.1 200 OK\r\n\r\n")  #发送请求头
    client.send("Hello, World")  #发送内容
  
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost',8080))
    sock.listen(5)
  
    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()
  
if __name__ == '__main__':
  main()

更多功能

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 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data = sk.recv(1024)
    print( data)




import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = input('数据:').strip()
    if inp == 'exit':
        break
    sk.sendto(bytes(inp,encoding="utf-8"),ip_port)

sk.close()
UDP demo

方法解析:

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

  套接字的文件描述符

练习:

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'



import socket
sk = socket.socket() #
sk.bind(('127.0.0.1',9999,))
sk.listen(5)
#接收客户端请求

# 连接,客户端地址信息
while True:
    conn,address = sk.accept()#accept阻塞 如果客户端不连接的话,就会阻塞
    conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8')) # py2 直接传即可,而3是字节类型
    while True:
        ret = conn.recv(1024)
        ret_str = str(ret,encoding="utf-8")
        if ret_str == "q":
            break
        conn.sendall(bytes(ret_str+"",encoding="utf-8"))
    print(conn,address)
服务端
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'

import socket
obj =  socket.socket()

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

ret_bytes = obj.recv(1024)  # 这一步如果服务端不sendall 就不会反应,会阻塞
ret_str = str(ret_bytes,encoding="utf-8")
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 = str(obj.recv(1024),encoding="utf-8")
        print(ret_str)

obj.close() # 关闭
# 阻塞
客户端

2.文件传输

#############服务端
#
!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' import socket sk = socket.socket() sk.bind(('127.0.0.1',9999,)) #绑定端口,为服务器完成该任务指定端口 sk.listen(5) #接收客户端请求 while True: # 连接,客户端地址信息 conn,address = sk.accept()#accept阻塞 如果客户端不连接的话,就会阻塞 conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8')) # py2 直接传即可,而3是字节类型
#先去获取文件大小。通过获取文件大小来判断文件传输结束了没有,传完了就可以断开连接。从而避免了服务器的长时间占用 file_size = str(conn.recv(1024),encoding="utf-8") conn.sendall(bytes("ack", encoding='utf-8')) #获取文件大小后,回复确认 #为的解决粘包问题
"""
在该程序里,如果没有获取文件大小后,回复确认这一步,那么就是客户端传了一个文件大小的数值,然后就接着传文件内容,因服务器接收数据时,是内容从端口进入服务器,
会进入缓冲区中,然后操作系统再每隔固定时间去缓冲区中后去数据并解析。这时就有可能出现问题:我们本想文件大小传给服务器后,然后服务器就获取到这个值,然后服务器
再获取文件内容,但是,此时会可能出现,文件内容和文件大小因间隔时间太短,服务器还没接收文件大小,文件内容也跟着传了服务器缓冲区,那么服务器在接收到数据后,
就会不知道如何出来。这种现象即为:粘包
"""
print(file_size) has_recv = 0 #指定接收到的文件的大小 f = open("n.jpg","wb") while True: if int(file_size) == has_recv: #如果接收到的文件内容大小等于文件大小 break print(111) data = conn.recv(1024) f.write(data) has_recv += len(data) #因为文件内容传输的时候就是字节。所以直接使用字节长度,就是接收到的文件大小
#######客户端
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'

import socket
obj =  socket.socket()
obj.connect(("127.0.0.1",9999))

ret_bytes = obj.recv(1024)  # 这一步如果服务端不sendall 就不会反应,会阻塞
ret_str = str(ret_bytes,encoding="utf-8")
print(ret_str)

import os
file_size = os.stat("f.jpg").st_size   #获取文件大小,得到的数字是:字节数
print(file_size)
obj.sendall(bytes(str(file_size),encoding="utf-8"))
obj.recv(1024) # 接收确认包,解决粘包。 无需确认是什么包
with open("f.jpg","rb") as f:
    for line in f:
        obj.sendall(line)   #一行一行的发送
obj.close() # 关闭

3、实现简单的ssh

#_*_coding:utf-8_*_
__author__ = 'Alex Li'


import socket
import os


server = socket.socket() #获得socket实例
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server.bind(("localhost",9999)) #绑定ip port
server.listen()  #开始监听

while True: #第一层loop
    print("等待客户端的连接...")
    conn,addr = server.accept() #接受并建立与客户端的连接,程序在此处开始阻塞,只到有客户端连接进来...
    print("新连接:",addr )
    while True:
        data = conn.recv(1024)
        if not data:
            print("客户端断开了...")
            break #这里断开就会再次回到第一次外层的loop
        print("收到命令:",data)
        res = os.popen(data.decode()).read() #py3 里socket发送的只有bytes,os.popen又只能接受str,所以要decode一下   #执行shell命令的
        if len(res) == 0:
            res = "cmd exec success,has not output!".encode("utf-8")
        conn.send(str(len(res)).encode("utf-8")) #发送数据之前,先告诉客户端要发多少数据给它
        print("等待客户ack应答...")
        client_final_ack = conn.recv(1024) #等待客户端响应
        print("客户应答:",client_final_ack.decode())
        print(type(res))
        conn.sendall(res.encode("utf-8")) #发送端也有最大数据量限制,所以这里用sendall,相当于重复循环调用conn.send,直至数据发送完毕

server.close()
server
import socket

client = socket.socket()

client.connect(("localhost",9999))

while True:
    msg = input(">>:").strip()
    if len(msg) == 0:continue
    client.send( msg.encode("utf-8") )
    res_return_size  = client.recv(1024) #接收这条命令执行结果的大小
    print("getting cmd result , ", res_return_size)
    total_rece_size = int(res_return_size.decode())
    client.send(b"ready go")
    now_recv_size=0
    while now_recv_size<total_rece_size:
        data=client.recv(1024)
        now_recv_size+=len(data)
        print(data.decode())
    #print(data.decode()) #命令执行结果

client.close()
client

socketserver 实现1个进程相应多个请求

内部 select 多线程 等待

2 版本SocketServer  3 版本 socketserver

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'

# import socketserver
import SocketServer


# class Mysock(socketserver.BaseRequestHandler):
class Mysock(SocketServer.BaseRequestHandler):   #必须创建的类,继承的类也不能变

    def handle(self): # 必须叫这个方法名称,socketserver内写死了要调这个方法
        # self 封装了属性,可以生成多个服务端响应请求
        # self.request, self.client_address,self.servers
        conn = self.request   #创建连接通道
        print("客户端地址:",self.client_address)  #获取客户端的ip地址
        conn.sendall(bytes("欢迎来点",encoding="utf-8"))
        while True:
            ret = conn.recv(1024)
            ret_str = str(ret,encoding="utf-8")
            if ret_str == "q":
                break
            conn.sendall(bytes(ret_str+"",encoding="utf-8"))
        print(conn)

if __name__ == '__main__':
    # server = socketserver.TCPServer(('127.0.0.1',9999,),Mysock) #如果使用的是TCPServer()那么就和socket方法一样了。只能一对一
    # server = socketserver.ThreadingTCPServer(('127.0.0.1',9999,),Mysock) # 继承baserver init 其需要两个参数  。使用ThreadTCPServer()就是为了实现多线程
    server = SocketServer.ThreadingTCPServer(('127.0.0.1',9999,),Mysock) # 继承baserver init 其需要两个参数
    server.serve_forever()  #创建永久连接
SERVER
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'

import socket
obj =  socket.socket()

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

ret_bytes = obj.recv(1024)  # 这一步如果服务端不sendall 就不会反应,会阻塞
ret_str = str(ret_bytes,encoding="utf-8")
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 = str(obj.recv(1024),encoding="utf-8")
        print(ret_str)

obj.close() # 关闭
client

注意点:1.使用socketserver,必须写类,类里面的handle方法也必须实现。

           2.socketserver可以实现一个服务器连接多个客户端。socket只能一对一连接,一个连接不断开,其他连接只能一直等待,直到当前连接断开

 

posted on 2017-06-13 18:07  进_进  阅读(150)  评论(0)    收藏  举报