琪齐

空想终日彷徨行动方可无惧!

导航

网络编程socket

Posted on 2016-06-11 14:52  琪齐  阅读(305)  评论(0编辑  收藏  举报

socket

socket解释

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

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

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

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

socket和file的区别:

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

  • 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源码剖析

内部调用流程为:

  • 启动服务端程序
  • 执行 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("2003","utf8"))  #返回一个值做一次交互,表示文件存在
            is_continue=str(conn.recv(1024),"utf8") #等待客户端操作
            if is_continue=="2004":
                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("2002","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=="2003":
        inp=input("文件存在,是否续传?Y/N").strip()
        if inp.upper()=="Y":  #
            sk.sendall(bytes("2004","utf8"))  #告诉服务端进行断点续传 
            result_continue_pos=str(sk.recv(1024),"utf8")  #
            print(result_continue_pos)
            has_sent=int(result_continue_pos)  #已经上传文件的大小

        else:
            sk.sendall(bytes("2005","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