首页 > 其他 > 详细

socket 基础

时间:2020-04-20 20:43:31      阅读:61      评论:0      收藏:0      [点我收藏+]

一 引子

内存空间相互之间是隔离的,物理层面隔离的;

硬盘空间是共享的,文件对应的硬盘空间

客户端和服务端在网络通信时,
客户端和服务端都只是操作系统之上的应用程序而已,应用程序不能直接来操作网卡来发送或接受数据,应用程序都是通过发送系统调用,让操作系统来操作计算机硬件。

应用程序发送和接收的数据都是存在于计算机的缓存中的:

发送端的应用软件发送数据也只是将 数据发送到自己的计算机缓存中,由本机操作系统操作网卡将缓存中数据发送给接收端;

socket应用程序收发原理,如图所示:

技术分享图片

二 客户端/服务端架构

应用层=》 客户端应用程序 服务端应用软件
(传输层。。。物理层)->socket
=> 操作系统 操作系统
计算机硬件 计算机硬件

三 socket层

socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口,在设计模式中,Socket 其实就是一个门面模式,它把负载的 TCP/IP协议族隐藏在 Socket 接口后面,对用户来收,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。socket已经为我们封装好了tcp/ip协议,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

技术分享图片

四 套接字工作流程

基于 tcp 协议通信的套接字工作流程如图所示:

技术分享图片

服务端首先初始化 socket,即生成socket对象,并与 ip 地址和端口号绑定,同时对端口进行监听(listen),调用 accept进入阻塞态,等待客户端连接。客户端初始化一个 socket 对象,然后 connect(连接)服务器,如果连接成功,这时客户端与服务端的链接就建立起来了,客户端发送数据请求,服务端接收请求并处理请求,然后把回应数据送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

1 基于tcp的套接字

? 注:tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

tcp服务端:

ss = socket() #创建服务器套接字
ss.bind()      #把地址绑定到套接字
ss.listen()      #监听链接
inf_loop:      #服务器无限循环
    cs = ss.accept() #接受客户端链接
    comm_loop:         #通讯循环
        cs.recv()/cs.send() #对话(接收与发送)
    cs.close()    #关闭客户端套接字
ss.close()        #关闭服务器套接字(可选)

? tcp客户端:

tcp客户端

1 cs = socket()    # 创建客户套接字
2 cs.connect()    # 尝试连接服务器
3 comm_loop:        # 通讯循环
4     cs.send()/cs.recv()    # 对话(发送/接收)
5 cs.close()            # 关闭客户套接字

socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信

基础版

#服务端

import socket

# 1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议=》tcp协议  

# 2、绑定手机卡
phone.bind((‘127.0.0.1‘,8081)) # 0-65535, 1024以前的都被系统保留使用

# 3、开机
phone.listen(5) # 5指的是半连接池的大小
print(‘服务端启动完成,监听地址为:%s:%s‘ %(‘127.0.0.1‘,8080))
# 4、等待电话连接请求:拿到电话连接conn
conn,client_addr=phone.accept()
# print(conn)
print("客户端的ip和端口:",client_addr)

# 5、通信:收\发消息
data=conn.recv(1024) # 最大接收的数据量为1024Bytes,收到的是bytes类型
print("客户端发来的消息:",data.decode(‘utf-8‘))
conn.send(data.upper())

# 6、关闭电话连接conn(必选的回收资源的操作)
conn.close()

# 7、关机(可选操作)
phone.close()


#客户端

import socket

#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议=》tcp协议

#2、拨通服务端电话
phone.connect((‘127.0.0.1‘,8081))

#3、通信
import time
time.sleep(10)
phone.send(‘hello egon 哈哈哈‘.encode(‘utf-8‘))
data=phone.recv(1024)
print(data.decode(‘utf-8‘))

#4、关闭连接(必选的回收资源的操作)
phone.close()

上述程序为基础版,存在两个问题,如下:

问题一:

tcp协议(它是流式协议)下:
基于tcp协议通信时,客户端和服务端是建立连接的,tcp协议是流式协议,它的数据是面向字节流的,是一个整体。
client.send:发数据时不会阻塞,它只是将数据发送到自己计算机的缓存中,当发送数据为空时,即缓冲中发送数据大小时为0的,操作系统接收到了发送数据的请求,发现缓存中数据为空时,是不会发送数据的。此时服务端是没有接收到数据的,一直处于wait状态.所以看到的现象就是client.recv阻塞在那里,client.recv:收数据时会阻塞 ,它是跟自己计算机要数据,此时自己计算机缓存中接收数据为空 。
同样:
sever.recv(1024):只是从自己计算机的缓存中拿数据

客户端发送十次,服务端只接受一次也是可以的,因为不管接收端还是发送端都只是跟自己计算机的缓存要数据,
所以从计算机底层来看无论收发数据都是根对方是没有关系的,都是跟自己的缓存有关系

问题二:

基于tcp协议通信时:
当客户端异常断开时(即没有四次挥手断开),此时服务端还是存于wait状态,客户端异常断开,服务端会一直接收到空数据,此时服务端(unix系统)会处于死循环状态,或者服务器(windows系统)抛出异常,所以服务端程序要避免由于接到到空数据而进入死循环或者抛出异常

改进版:加上链接循环与通信循环,解决上述两个问题

# 服务端应该满足的特点:
# 1、一直提供服务
# 2、并发地提供服务
import socket

# 1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议=》tcp协议

# 2、绑定手机卡
phone.bind((‘127.0.0.1‘,8080)) # 0-65535, 1024以前的都被系统保留使用

# 3、开机
phone.listen(5) # 5指的是半连接池的大小
print(‘服务端启动完成,监听地址为:%s:%s‘ %(‘127.0.0.1‘,8080))

# 4、等待电话连接请求:拿到电话连接conn
# 加上链接循环
while True:
    conn,client_addr=phone.accept()

    # 5、通信:收\发消息
    while True:
        try:
            data=conn.recv(1024) # 最大接收的数据量为1024Bytes,收到的是bytes类型
            if len(data) == 0:
                # 在unix系统洗,一旦data收到的是空
                # 意味着是一种异常的行为:客户度非法断开了链接
                break
            print("客户端发来的消息:",data.decode(‘utf-8‘))
            conn.send(data.upper())
        except Exception:
            # 针对windows系统
            break

    # 6、关闭电话连接conn(必选的回收资源的操作)
    conn.close()

# 7、关机(可选操作)
phone.close()



#客户端

import socket

#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议=》tcp协议

#2、拨通服务端电话
phone.connect((‘127.0.0.1‘,8080))

#3、通信
while True:
    msg=input("输入要发送的消息>>>: ").strip() #msg=‘‘
    if len(msg) == 0:continue
    phone.send(msg.encode(‘utf-8‘))
    print(‘======?‘)
    data=phone.recv(1024)
    print(data.decode(‘utf-8‘))

#4、关闭连接(必选的回收资源的操作)
phone.close()

简单案例:使用 tcp 协议的socket 建立远程发送命令

#服务端

# coding:utf-8
import socket
import subprocess

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((‘127.0.0.1‘, 8081))
server.listen(4)
while True:
    print(‘服务端正在运行中...‘)
    conn, client_addr = server.accept()
    while True:
        try:
            data_bytes = conn.recv(1024)  # 最大接收数据为 1024bytes
            if len(data_bytes) == 0:
                break
            data = data_bytes.decode(‘utf-8‘)
            print(‘接收到客户端命令:‘, data)

            obj = subprocess.Popen(data, shell=True,
                             stdout=subprocess.PIPE, #正确的结果被放入该管道
                             stderr=subprocess.PIPE, #错误的结果被放入该管道)
                                   )
            #查看正确管道内的内容
            str_true = obj.stdout.read() #读出的是 bytes 类型
            #查看错误管道内的内容
            str_error = obj.stderr.read() ##读出的是 bytes 类型
            if str_true:
                print(‘发送了正确管道内的内容‘)
                conn.send(str_true)
            if str_error:
                print(‘发送了错误管道内的内容‘)
                conn.send(str_error)

        except Exception as e:
            break

    conn.close()

    
#客户端

# coding:utf-8
import socket
client = socket.socket()
client.connect((‘127.0.0.1‘, 8081))
print(‘客户端正在运行中...‘)
while True:
    cmd = input(‘请输入cmd命令:‘).strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode(‘utf-8‘))
    data_bytes = client.recv(1024)
    data = data_bytes.decode(‘utf-8‘)
    print(‘接收到服务端数据:‘, data)

client.close()

2 基于udp的套接字

udp是无连接的,先启动哪一端都可以,不会报错

udp 服务端

1 ss = socket()   #创建一个服务器的套接字
2 ss.bind()       #绑定服务器套接字
3 inf_loop:       #服务器无限循环
4     cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
5 ss.close()                         # 关闭服务器套接字

udp客户端:

cs = socket()   # 创建客户套接字
comm_loop:      # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
cs.close()                      # 关闭客户套接字

简单示例:

#服务端


import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind((‘127.0.0.1‘, 8080))
print(‘服务端正在运行中...‘)
while True:
    res = server.recvfrom(1024)  #拿到的是元组(数据,(地址))
    print(‘接收的客户端数据:‘, res) #(b‘qq‘, (‘127.0.0.1‘, 64933))
    data_bytes, addr = res
    print(‘不包含客户端地址的真实数据‘, data_bytes.decode(‘utf-8‘))
    if data_bytes.decode(‘utf-8‘) == ‘q‘:
        break
    server.sendto(data_bytes.upper(), addr)

print(‘服务端结束运行‘)
server.close()


#客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print(‘客户端正在运行中...‘)
while True:
    msg = input(‘请输入命令:‘).strip()
    if msg == ‘q‘:
        break
    data_send = msg.encode(‘utf-8‘)
    client.sendto(data_send, (‘127.0.0.1‘, 8080))
    res = client.recvfrom(1204)  #拿到的是元组(数据,(addr))
    print(‘接收到的服务端数据:‘, res) #(b‘QQ‘, (‘127.0.0.1‘, 8080))
    data_bytes,addr = res
    print(‘不包含服务端地址的真实数据:‘, data_bytes.decode(‘utf-8‘))

print(‘客户端结束运行‘)
client.close()

‘‘‘
当客户端输入命令为空时,服务端接收到的数据为(b‘‘, (‘127.0.0.1‘, 64933))
也就是说基于udp协议通信时想要,发送的数据可以为空,是因为发送的数据在缓存中时是元组类型,
元组中总是包含地址的,所以发送的数据长度永远不为空
‘‘‘

总结:

? 当客户端输入命令为空时,服务端接收到的数据为(b‘‘, (‘127.0.0.1‘, 64933)),也就是说基于udp协议通信时想要,发送的数据可以为空,是因为发送的数据在缓存中时是元组类型,元组中总是包含地址的,所以发送的数据长度永远不为空。

socket 基础

原文:https://www.cnblogs.com/xy-han/p/12739821.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!