首页 > 其他分享 >socket学习

socket学习

时间:2022-10-08 18:23:54浏览次数:41  
标签:socket 学习 接字 接收 data 服务端 客户端

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

一、socket简述

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

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

socket和file的区别:

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

1、客户端小实例:

import  socket   #导入socket模块
  
client = socket.socket()  #创建socket实例
client.connect(("localhost",6969))  #建立连接
send_data = "hello word!"   #发送的字符串
send_data = send_data.encode() #因为发送是bytes类型,所以这边先转码成bytes类型
client.send(send_data)  #发送数据
data = client.recv(1024)  #接收服务端的数据,这边设置接收1024字节  1kb=1024字节
print("server rece:",data.decode())
client.close()   #关闭与服务端的链接

2、服务端小实例:

import socket
  
sever = socket.socket()  #创建服务端实例
sever.bind(("localhost",6969))   #绑定客户端ip和端口
sever.listen()   #监听端口
print("server  waiting  connect.........")
conn,addr = sever.accept()   #接收客户端,并且返回连接标志位(conn)实例,和对方的ip地址(addr)
data = conn.recv(1024)   #接收客户端发过来的数据,接收的也是bytes类型的数据
print("成功建立链接")
print("client data:",data.decode())
conn.send(data.upper())  #将刚刚接收到数据,发送至服务端,注意这边发送的也是字节类型,是bytes类型
sever.close()  #关闭服务端

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

二、Socket方法

1、socket方法:

函数描述
服务器端套接字
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvform() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件

2、服务器端

#coding=utf-8
#创建TCP服务器
import socket
import time
from time import ctime
 
HOST = '127.0.0.1'
PORT = 8080
BUFSIZE=1024
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
sock.bind((HOST, PORT))  
sock.listen(5)
addr=(HOST,PORT)
while True:
    print('waiting for connection...')
    sock,addr =sock.accept()
    print('...connected from:',addr)
    while True:
        data =sock.recv(BUFSIZE).decode()
        print('date=',data)
        if not data:
            break
        sock.send(('[%s] %s' %(ctime(),data)).encode())
sock.close()

3、客户端

#coding=utf-8
#创建TCP客户端
 
import socket
 
HOST = '127.0.0.1'
PORT = 8080
BUFSIZE = 1024
ADDR=(HOST,PORT)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT)) 
 
while True:
    data = input('> ')
    if not data:
        break
    sock.send(data.encode())
    data = sock.recv(BUFSIZE).decode()
    if not data:
        break
    print(data)
 
sock.close()

三、服务端)对多(客户端)通信

默认情况下,socket是阻塞的,意思就是服务端在接收到一个客户端的请求后必须要处理完,下一个客户端才可以连接,为了确保一对多的连接,可以采用多线程、socketServer、selectIO多路复用等方式实现一对多通信

客户端

可以使用多个客户端来测试服务端

import socket

HOST = "192.168.xx.xxx"
PORT = 4001
BUFFERSIZE = 1024
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect((HOST, PORT))
    client.send(b"hello")
    data = client.recv(BUFFERSIZE)
    print(data)

多线程方式

import socket, time,threading

HOST = ""
PORT = 4001
BUFFERSIZE = 1024

def recv_msg(server):
    conn, addr = server.accept()
    print(f"addr:{addr}")
    data = conn.recv(BUFFERSIZE)
    time.sleep(10)
    print(f"data:{data}")
    conn.send(data)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.bind((HOST, PORT))
    server.listen(10)
    while True:
        t = threading.Thread(target=recv_msg, args=(server,))
        t.daemon = True
        t.start()

重写socketServer

import socketserver

class MySocketServer(socketserver.BaseRequestHandler):
    """
        def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass

    """

    def setup(self) -> None:
        print(f"request:{self.request}")
        print(f"client_address:{self.client_address}")
        print(f"server:{self.server}")
        return super().setup()
    
    def handle(self) -> None:
        data = self.request.recv(BUFFERSIZE)
        print(f"data:{data}")
        time.sleep(10)
        self.request.send(data)
        return super().handle()
# 与默认的socket一样,也会阻塞
# server = socketserver.TCPServer((HOST, PORT), RequestHandlerClass=MySocketServer)
# 使用类似创建线程的方式,避免阻塞, Windows、Linux通用
server = socketserver.ThreadingTCPServer((HOST, PORT), RequestHandlerClass=MySocketServer)
# 使用创建进程的方式。避免阻塞,只适用于Linux
# server = socketserver.ForkingTCPServer((HOST, PORT), RequestHandlerClass=MySocketServer)
# serve_forever(self, poll_interval=0.5):每隔固定时间进行轮训, 默认0.5
server.serve_forever()

select IO多路复用

import select, socket
from queue import Queue



HOST = ""
PORT = 4001
BUFFERSIZE = 1024

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置共用地址端口,防止出现冲突
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.setblocking(False)
server.bind((HOST, PORT))
server.listen(10)

# 依次轮询readable、writeable、exceptional
# 期望去读的服务端socket以及服务端接收到的客户端的socket
inputs = [server]
# 期望去写的socket(客户端)发送数据
outputs = []
# 存放发送接收消息的字典
message_queue = {}

"""
readable, writeable, exceptional = select.select(inputs, outputs, inputs, timeout)
    inputs: 监听可读的套接字 外部发来的数据,服务端接收的客户端
    outputs: 监听可写的套接字 监控并接收所有发出去的数据
    inputs: 监听错误信息 
    timeout: 监听的时间限制 默认是0.05
    readable: 可读列表 服务端socket, 以及默认接收的客户端
    writeable: 可写列表 运行过程产生的socket 服务端接收的客户端
    exceptional: 错误信息  异常列表
readable对应inputs中数据
writeable对应outputs数据
"""

while inputs:
    # 服务端socket,运行过程产生的socket:客户端,异常列表
    readable, writeable, exceptional = select.select(inputs, outputs, inputs)
    # 处理服务器端的socket及包括接收到的客户端socket
    for s in readable:
        if s is server:
            # 服务端本身
            conn, addr = s.accept()
            # 设置非阻塞
            conn.setblocking(False)
            # 将接受到的socket添加到
            inputs.append(conn)
            message_queue[conn] = Queue()
        else:
            # 服务端接收的客户端的socket:inputs.append(conn)
            data = s.recv(BUFFERSIZE)
            print(f"{s.getpeername()}  recv data:{data}")
            if data:
                # 客户端与服务端保持连接,客户端向服务端发送数据
                message_queue[s].put(data)
                # 将接收的客户端添加到outputs用于服务端向客户端发送数据
                if s not in outputs:
                    outputs.append(s)
            else:
                # 无消息接收,客户端与服务端断开连接
                # 删除outputs中将要发送消息的socket
                if s in outputs:
                    outputs.remove(s)
                if s in inputs:
                    inputs.remove(s)
                # 关闭客户端socket
                s.close()
                # 清除消息字典中的对应socket
                del message_queue[s]

    # 连接到服务端的conn 服务端向客户端发送数据
    for s in writeable:

        try:
            message = message_queue.get(s, None)
            if message is not None:
                # 从消息字典中拿取数据
                data = message.get_nowait()
            else:
                # 消息字典中无对应数据, 客户端断开
                print(f"socket has no data , close")
                # if s in outputs:
                #     outputs.remove(s)
        except Exception as e:
            # 客户端断开
            print(f"send data error")
            print(f"{s.getpeername()} close")
        else:
            # 如果存在对应消息字典数据则发送
            if message is not None:
                s.send(data)
            else:
                print(f"socket has no data , close")

    # 异常处理
    for s in exceptional:
        print("exist exception")
        # 出现异常,从inputs删除socket
        inputs.remove(s)
        # 如果在outputs中存在则删除
        if s in outputs:
            outputs.remove(s)
        if s in inputs:
            inputs.remove(s)
        # 关闭socket
        s.close()
        # 删除socket相应的消息字典数据
        del message_queue[s]
        
    # print(readable)
    # print(writeable)
    # print(exceptional)

select服务端接收客户端的一次流程

select接收一次客户端流程
第一次轮询:服务端
    inputs:[服务端socket]
    outputs:[]
    readable:[服务端socket]
    writeable:[]
    exceptional:[]
    轮询监听的服务端socket,将接收到的客户端添加至监听可读列表 ,并对客户端创建消息队列并添加至消息字典中
    inputs:[服务端socket, 接收到的客户端socket]
第二次轮询:服务端接收到的客户端, 接收消息
    inputs:[服务端socket, 接收到的客户端socket]
    outputs:[]
    readable:[接收到的客户端socket]
    writeable:[]
    exceptional:[]
    轮询监听接收到的客户端的socket, 接收客户端的数据并将数据添加至对应消息队列中
    outputs:[接收到的客户端socket]
    readable:[]
第三次轮询:服务端接收到的客户端, 服务端发送消息给客户端
    inputs:[服务端socket, 接收到的客户端socket]
    outputs:[接收到的客户端socket]
    readable:[]
    writeable:[接收到的客户端socket]
    exceptional:[]
    接收到的客户端socket获取消息队列中的数据并向客户端发送数据

第四次轮询:
    inputs:[服务端socket, 接收到的客户端socket]
    outputs:[接收到的客户端socket]
    readable:[接收到的客户端socket]
    writeable:[接收到的客户端socket]
    exceptional:[]
    继续接收数据,由于没有数据则从可写(发送数据)列表、可读列表(服务端接收)中移除并关闭客户端socket、删除对应消息字典中的队列
    inputs:[服务端socket]
    outputs:[]
    readable:[接收到的客户端socket(closed)]
    writeable:[接收到的客户端socket(closed)]

 

标签:socket,学习,接字,接收,data,服务端,客户端
From: https://www.cnblogs.com/windyrainy/p/16769803.html

相关文章