zoukankan      html  css  js  c++  java
  • 网络编程:socket 编程

    socket 编程

    -客户端/服务器架构 :即 C/S架构

    1,硬件C/S 架构(打印机)

    2, 软件C/S 架构(web服务)

    C/S架构与socket的关系:socket就是为了完成C/S架构的开发

     

    -osi 七层:

    应用层--运输层--网络层--链路层--物理层

     

     socket 抽象层在应用层和运输层之间

     

     

    socket概念(socket也是套接字)

    socket是应用层和TCP/IP协议中间通信的软件层,它是一组接口,在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议封装隐藏在socket接口后,让socket去组织数据,以符合指定协议,所以只需遵循socket规定去编程就可以。

    套接字分为2种:

    -基于文件型的套接字家族 AF_UNIX

    用于一台机器的不同程序之间

    linux 一切皆文件,基于文件的套接字调用的是底层的文件系统来取数据,2个套接字进程运行在同一个机器,可以通过访问同一个文件系统来间接完成通信

    -基于网络类型的套接字家族 AF_INET

    用于网络编程

    通过网络来实现2个程序通讯

     

    socket 基于tcp运行流程图如下:

      

    例如:

    服务端:

    import socket

    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #socket.SOCK_STREAM 是基于流的通讯方式,也就是TCP
    #socket.AF_INET 代表是网络嵌套家族类型

    phone.bind(('192.168.1.4',8000))
    #括号内写IP地址+端口 自己电脑IP是 192.168.1.4 ,监听端口是8000

    phone.listen(5) #代表同时可以接5个电话

    conn,addr =phone.accept()
    msg=conn.recv(1024)# 收消息 ,1024代表可以接收多少字节的信息
    print('客户端发来的消息是',msg)
    conn.send(msg.upper())#发消息

    conn.close()
    phone.close()

    客户端

    import  socket

    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    phone.connect(('192.168.1.4',8000))

    phone.send('hello'.encode('utf-8'))#发消息
    data =phone.recv(1024) #收消息
    print('收到服务端发来的消息',data)

    运行结果是:

    服务端:客户端发来的消息是 b'hello'

    客户端:收到服务端发来的消息 b'HELLO'

     

    socket 底层工作原理

     

      客户端服务端循环发送接收消息

    服务端代码:

    from socket import *

    ip_import =('192.168.1.3',8000)
    back_log = 5
    buffer_size = 1024
    tcp_server = socket(AF_INET,SOCK_STREAM)
    tcp_server.bind(ip_import)
    tcp_server.listen(back_log)

    conn,add = tcp_server.accept()
    print('双向链接是',conn)
    print('客户端的地址是',add)

    while True:
    msg = conn.recv(buffer_size)
    print('客户发来的是',msg.decode('utf-8'))
    conn.send(msg.upper())

    conn.close()
    tcp_server.close()

     服务端运行结果是:

    双向链接是 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.3', 8000), raddr=('192.168.1.3', 52951)>
    客户端地址是 ('192.168.1.3', 52951)
    客户发来的是 nihao
    客户发来的是 hi
    客户发来的是 how are you

     客户端代码:

    from socket import *

    ip_port=('192.168.1.3',8000)
    back_log=5
    buffer_size=1024

    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)

    while True:
    client_msg = input('>>:').strip()
    if not client_msg: continue
    tcp_client.send(client_msg.encode('utf-8'))
    print('客户端已发送消息')
    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))

     

     客户端结果如下:

    >>:nihao
    收到服务端发来的消息 NIHAO
    >>:hi
    收到服务端发来的消息 HI
    >>:how are you
    收到服务端发来的消息 HOW ARE YOU

     注意:如果输入可以是空格,但不可以是空,是空的话会继续要求输入。

     

     socket 收发消息原理刨析

     

    客户端发消息:是从应用程序发送到用户态内存,然后发送到内核态内存然后再通过网卡发出

    客户端消息:是从网卡进入内核态内存然后发送到用户态内存

    服务端同样如此。

     

     服务端多次接收双向连接

    windows 系统和linux/mac系统下,socket 不同之处:

    断开客户端,windows 系统下服务端会报错,而linux/mac系统下,服务端接收的是空

     windows 系统下:

    服务端:from socket import *


    ip_port =('192.168.1.3',8001)
    back_log = 5
    buffer_size = 1024

    tcp_server = socket(AF_INET,SOCK_STREAM)
    tcp_server.bind(ip_port)
    tcp_server.listen(back_log)
    while True:
    conn,add = tcp_server.accept()
    print('接收的链接是',conn)
    print('地址是',add)

    while True:
    print('服务端开始运行了')
    try:
    data = conn.recv(buffer_size)

    print('客户发来的是',data.decode('utf-8'))
    conn.send(data.upper())
    print('服务端已发送',data.upper())
    except Exception:
    break
    conn.close()
    tcp_server.close()

     客户端

    from socket import *

    ip_port=('192.168.1.3',8001)
    back_log=5
    buffer_size=1024

    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)

    while True:
    client_msg = input('>>:').strip()
    if not client_msg: continue
    tcp_client.send(client_msg.encode('utf-8'))
    print('客户端已发送消息')
    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))

    tcp_client.close()

     

    linux 系统下:

    from socket import *

    ip_port =('192.168.1.3',8001)
    back_log = 5
    buffer_size = 1024

    tcp_server = socket(AF_INET,SOCK_STREAM)
    tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    #在bind 之前进行socket 设置,使其不卡在time_wait占用地址

    tcp_server.bind(ip_port)
    tcp_server.listen(back_log)
    while True:
    conn,add = tcp_server.accept()
    print('接收的链接是',conn)
    print('地址是',add)

    while True:
    print('服务端开始运行了')
    try:
    data = conn.recv(buffer_size)
    if not data: break
    print('客户发来的是',data.decode('utf-8'))
    conn.send(data.upper())
    print('服务端已发送',data.upper())
    except Exception:
    break

    tcp_server.close()

     客户端代码如上,不变。

     

    总结:服务端,客户端基本要求:

     

     

     基于udp 的套接字

    udp没有连接

     服务端

    from socket import *
    ip_port = ('192.168.1.5',8080)
    buffer_size = 1024


    udp_server = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报
    udp_server.bind(ip_port)


    while True:
    data,addr = udp_server.recvfrom(buffer_size)
    print(data.decode('utf-8'))
    udp_server.sendto(data.upper(),addr)
    udp_server.close()

     

     客户端

    from socket import *
    ip_port = ('192.168.1.5',8080)
    buffer_size = 1024


    udp_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报

    while True:
    msg = input('>>:').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr = udp_client.recvfrom(buffer_size)
    print(data.decode('utf-8'))
    udp_client.close()

     

    运用基于udp的套接字,来制作时间服务器(ntp),代码如下:

    时间服务端:

    from socket import *
    import time
    ip_port = ('192.168.1.5',8080)
    buffer_size = 1024

    time_server = socket(AF_INET,SOCK_DGRAM)
    time_server.bind(ip_port)

    while True:
    data,addr = time_server.recvfrom(buffer_size)
    if not data:
    fmt = '%Y-%m-%d-%X'
    else:
    fmt = data.decode('utf-8')

    back_time = time.strftime(fmt)
    time_server.sendto(back_time.encode('utf-8'),addr)
    time_server.close()

    时间客户端:

    from socket import *
    ip_port = ('192.168.1.5',8080)
    buffer_size = 1024


    time_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报

    while True:
    msg = input('>>:').strip()
    time_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr = time_client.recvfrom(buffer_size)
    print('服务器的标准时间是:',data.decode('utf-8'))
    time_client.close()

     recv 在自己这段的缓冲区为空时,会阻塞

    recvfrom 在自己这段的缓冲区为空时,就收一个空

     

     基于tcp实现远程命令

     subprocess 模块

    代码:变量名=subprocess.Popen(命令,shell=True,

                stderr=subprocess.PIPE,

                stdin=subprocess.PIPE,

                stdout=subprocess.PIPE)

    
    

     将命令结果封装在管道(PIPE)中,stdin 代表输入,stdout 代表输出,stderr代表报错。

    想要读取内容:代码:变量名.stdrr.read()

     服务端

    from socket import *
    import subprocess

    ip_port = ('192.168.1.5',8080)
    back_log = 5
    buffer_size = 1024

    tcp_sever = socket(AF_INET,SOCK_STREAM)
    tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) #设置socket,使其不卡在time wait,占用地址
    tcp_sever.bind(ip_port)
    tcp_sever.listen(back_log)

    while True:
    conn,addr = tcp_sever.accept()
    print('新的客户端连接',addr)

    while True:
    try:
    #收消息
    cmd = conn.recv(buffer_size)
    if not cmd:break
    print('收到客户端的命令',cmd)

    #执行命令,得到命令的结果cmd_res
    res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
    stderr=subprocess.PIPE,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE)
    err = res.stderr.read()

    if err:
    cmd_res = err
    else:
    cmd_res = res.stdout.read()
    #发消息
           if not cmd_res:
               cmd_res = '操作成功'.encode('utf-8') #此代码代表,如果cmd_res 为空的话,也会显示结果
                conn.send(cmd_res)
    except Exception as e:
    print(e)
    break

    tcp_sever.close()

     

    客户端 

     

    from socket import *

    ip_port = ('192.168.1.5',8080)
    back_log = 5
    buffer_size = 1024

    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)

    while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))
    cmd_res = tcp_client.recv(buffer_size)
    print('命令的执行结果是',cmd_res.decode('utf-8'))

    tcp_client.close()

    实操结果如下:

     粘包

     注意:只有tcp 会粘包,udp不会粘包。

    粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。

    解决粘包

    方式一:比较低端一些

    服务端:

    from socket import *
    import subprocess

    ip_port = ('192.168.1.5',8080)
    back_log = 5
    buffer_size = 1024

    tcp_sever = socket(AF_INET,SOCK_STREAM)
    tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    tcp_sever.bind(ip_port)
    tcp_sever.listen(back_log)

    while True:
    conn,addr = tcp_sever.accept()
    print('新的客户端连接',addr)

    while True:
    try:
    #收消息
    cmd = conn.recv(buffer_size)
    if not cmd:break
    print('收到客户端的命令',cmd)

    #执行命令,得到命令的结果cmd_res
    res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
    stderr=subprocess.PIPE,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE)
    err = res.stderr.read()

    if err:
    cmd_res = err
    else:
    cmd_res = res.stdout.read()
    #发消息
    if not cmd_res:
    cmd_res = '操作成功'.encode('utf-8')

    length =len(cmd_res)
    conn.send(str(length).encode('utf-8'))
    client_ready = conn.recv(buffer_size)
    if client_ready == b'ready':
    conn.send(cmd_res)
    except Exception as e:
    print(e)
    break

    tcp_sever.close()

    客户端

    from socket import *

    ip_port = ('192.168.1.5',8080)
    back_log = 5
    buffer_size = 1024

    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)

    while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))

    length= tcp_client.recv(buffer_size)
    tcp_client.send(b'ready')
    length = int(length.decode('utf-8'))

    recv_msg = b''
    recv_size = 0
    while recv_size < length:
    recv_msg +=tcp_client.recv(buffer_size)
    recv_size = len(recv_msg)

    print('命令的执行结果是',recv_msg.decode('utf-8'))

    tcp_client.close()

     方式二:比较高端一些 

     服务端:

    from socket import *
    import subprocess
    import struct

    ip_port = ('192.168.1.2',8080)
    back_log = 5
    buffer_size = 1024

    tcp_sever = socket(AF_INET,SOCK_STREAM)
    tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    tcp_sever.bind(ip_port)
    tcp_sever.listen(back_log)

    while True:
    conn,addr = tcp_sever.accept()
    print('新的客户端连接',addr)

    while True:
    try:
    #收消息
    cmd = conn.recv(buffer_size)
    if not cmd:break
    print('收到客户端的命令',cmd)

    #执行命令,得到命令的结果cmd_res
    res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
    stderr=subprocess.PIPE,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE)
    err = res.stderr.read()

    if err:
    cmd_res = err
    else:
    cmd_res = res.stdout.read()
    #发消息
    if not cmd_res:
    cmd_res = '操作成功'.encode('utf-8')

    length =len(cmd_res)
    conn.send(struct.pack('i',length))
    conn.send(cmd_res)
    except Exception as e:
    print(e)
    break

    tcp_sever.close()

     客户端:

    from socket import *
    import struct
    from functools import partial

    ip_port = ('192.168.1.2',8080)
    back_log = 5
    buffer_size = 1024

    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)

    while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))

    length_data= tcp_client.recv(4)
    length = struct.unpack('i',length_data)[0]

    recv_msg = ''.join(iter(partial(tcp_client.recv, buffer_size), b''))

    print('命令的执行结果是',recv_msg.decode('utf-8'))

    tcp_client.close()

     

     总结:

     

     

                                                         

    socket 并发socketserver

    socketserver-tcp套接字 并发

    并发:即多个客户端与服务端的同时交互

     服务端:

    import socketserver


    class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
    # print('conn is:', self.request) # self.request相当于conn
    # print('addr is:', self.client_address) # self.client_address相当于addr

    while True:
    try:
    # 收消息
    data = self.request.recv(1024)
    if not data: break
    print('收到客户端的消息是:', data,self.client_address)

    # 发消息
    self.request.send(data.upper())
    except Exception as e:
    print(e)
    break

    if __name__ == '__main__':
    s = socketserver.ThreadingTCPServer(('192.168.1.2',8081),MyServer)
    s.serve_forever()

     客户端:

    from socket import *

    ip_port = ('192.168.1.2', 8081)
    buffer_size = 1024

    tcp_client = socket(AF_INET, SOCK_STREAM)
    tcp_client.connect(ip_port)

    while True:
    msg = input('>>: ').strip()
    if not msg: continue
    tcp_client.send(msg.encode('utf-8'))
    print('客户端已经发送消息')

    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息', data.decode('utf-8'))

    tcp_client.close()

    socketserver-udp套接字 并发

     

    对于tcp来说,self.request = conn

    但是对于udp来说,self.request =(data,udp的套接字对象

     

    服务端:

    import socketserver

    class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
    #self.request[0] 是data
    #self.request[1]是udp的socket套接字
    #self.client_address 是conn
    #收消息
    print('收到客户端的消息是',self.request[0],self.client_address)
    #发消息
    self.request[1].sendto(self.request[0].upper(),self.client_address)


    if __name__ == '__main__':
    s=socketserver.ThreadingUDPServer(('192.168.1.2',8082),MyServer) #多线程
    s.serve_forever()

    客户端:

    from socket import *
    ip_port=('192.168.1.2',8082)
    buffer_size=1024

    udp_client=socket(AF_INET,SOCK_DGRAM) #数据报

    while True:
    msg=input('>>: ').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr=udp_client.recvfrom(buffer_size)
    # print(data.decode('utf-8'))
    print(data)

     认证客户端合法性

    服务端

    from socket import *
    import os
    import hmac
    secret_key = b'ni hao ma'

    def conn_auth(conn):
    '''
    认证客户端连接
    :param conn:
    :return:
    '''
    print('开始验证连接的合法性')
    msg = os.urandom(32)
    conn.sendall(msg)
    h=hmac.new(secret_key,msg)#加严
    digest=h.digest()
    respone=conn.recv(len(digest))
    return hmac.compare_digest(respone,digest)#对比respone 与digest 是否一致



    def data_handler(conn, bufize=1024):
    if not conn_auth(conn):
    print('该连接不合法,关闭')
    conn.close()
    return
    print('连接合法,开始通信')

    while True:
    data = conn.recv(bufize)
    if not data: break
    conn.send(data.upper())



    def server_handle(ip_port,bufize,backlog=5):
    '''
    只处理连接
    :param ip_port:
    :param bufize:
    :param backlog:
    :return:
    '''
    tcp_socket_server = socket(AF_INET, SOCK_STREAM)
    tcp_socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 在bind 之前进行socket 设置,使其不卡在time_wait占用地址
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(backlog)
    while True:
    conn, addr = tcp_socket_server.accept()
    print('conn是%s,addr是%s' % (conn, addr))
    data_handler(conn, bufize)
    tcp_socket_server.close()

    if __name__ == '__main__':
    ip_port =('192.168.1.2',8080)
    bufize = 1024
    server_handle(ip_port,bufize)

     客户端:

    from socket import *
    import os,hmac

    secret_key = b'ni hao ma'

    def conn_auth(conn):

    '''
    验证客户端到服务器的连接合法性
    :param conn:
    :return:
    '''
    msg = conn.recv(32)
    h = hmac.new(secret_key, msg)
    digest = h.digest()
    conn.sendall(digest)


    def client_handler(ip_port,bufsize=1024):
    tcp_socket_client = socket(AF_INET, SOCK_STREAM)
    tcp_socket_client.connect(ip_port)
    conn_auth(tcp_socket_client)

    while True:
    #发消息
    cmd = input('>>:').strip()
    if not cmd: continue
    if cmd == 'quit': break
    tcp_socket_client.send(cmd.encode('utf-8'))
    #收消息
    data=tcp_socket_client.recv(bufsize)
    print(data.decode('utf-8'))
    tcp_socket_client.close()

    if __name__ == '__main__':
    ip_port = ('192.168.1.2', 8080)
    bufize = 1024
    client_handler(ip_port,bufize)

     

     作业

     

     

     

     

     

     

     

     

  • 相关阅读:
    如何实现shell并发 一个入门级可控多线程shell脚本方案
    Android SDK 开发指南
    Android SDK上手指南:知识测试
    JavaScript apply
    chrome 调试
    jQuery file upload上传图片出错分析
    jQuery插件开发
    yarn
    What is 'typeof define === 'function' && define['amd']' used for?
    jQuery .closest()
  • 原文地址:https://www.cnblogs.com/wode110/p/14878750.html
Copyright © 2011-2022 走看看