socket 编程

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

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

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

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

 

-osi 七层:

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

socket在软件架构怎么描述 socket结构_套接字

 

 

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

 

 

socket概念(socket也是套接字)

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

套接字分为2种:

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

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

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

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

用于网络编程

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

 

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

socket在软件架构怎么描述 socket结构_套接字_02

  

例如:

服务端:

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 底层工作原理

 

socket在软件架构怎么描述 socket结构_socket在软件架构怎么描述_03

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

服务端代码:

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 收发消息原理刨析

 

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

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

服务端同样如此。

 

socket在软件架构怎么描述 socket结构_socket在软件架构怎么描述_04

 

服务端多次接收双向连接

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

 客户端代码如上,不变。

 

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


socket在软件架构怎么描述 socket结构_套接字_05

 

socket在软件架构怎么描述 socket结构_套接字_06

 

 基于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 在自己这段的缓冲区为空时,就收一个空

socket在软件架构怎么描述 socket结构_服务端_07

 

 

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

实操结果如下:

socket在软件架构怎么描述 socket结构_socket在软件架构怎么描述_08

 

 粘包

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

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

socket在软件架构怎么描述 socket结构_套接字_09

 

 

解决粘包

方式一:比较低端一些

服务端:

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在软件架构怎么描述 socket结构_套接字_10

 

 

socket在软件架构怎么描述 socket结构_客户端_11

 

 

socket在软件架构怎么描述 socket结构_服务端_12

 


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)

 

 作业

 

socket在软件架构怎么描述 socket结构_客户端_13