网络编程(二)-socket套接字

1、socket概念

2、理解socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

2.1站在人的角度看待socket模块

socket就是一个模块。通过调用模块中已经实现的方法建立两个进程之间的连接和通信。要确定一个计算机要有ip地址和port端口,因此socket也可以说成ip+port。

因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。

4、socket通信代码

#服务端
import socket

server = socket.socket() # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8080))# bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)# 开机    半连接池

conn, addr = server.accept()# 接听电话  等着别人给你打电话     阻塞
data = conn.recv(1024)# 听别人说话 接收1024个字节数据          阻塞
print(data)
conn.send(b'hello boby~') # 给别人回话

conn.close()
server.close()
#客服端
import socket

client = socket.socket()# 拿电话
client.connect(('127.0.0.1',8080)) # 拨号   写的是对方的ip和port

client.send(b'hello world')# 对别人说话
data = client.recv(1024)# 听别人说话

print(data)

client.close() # 挂电话

4、通信循环

服务端:

import socket


server = socket.socket()
server.bind(('127.0.0.1',8080))#bangding yige jiekou
server.listen(5)#半连接

conn ,addr =server.accept()
#循环对话
while True:
    data = conn.recv(1024)#接收数据
    print(data)
    conn.send(data.upper())#将接收的数据变成大写

客户端:

import socket

client = socket.socket()#默认tcp
client.connect(('127.0.0.1',8080))#与哪个服务端连接

while True:

    msg = input('msg:>>>').encode('utf-8')
    #先写send与recv取决于服务端先写的什么
    client.send(msg)
    data = client.recv(1024)
    print(data)

服务端断开报错:

Traceback (most recent call last):
  File "C:/untitled5/服务端.py", line 11, in <module>
    data = conn.recv(1024)#接收数据
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

该行报错conn.recv

 #conn类似于双向通道。与客户端的双向通道,现在客户端断开,因此拿着一个失效的通道,在接受值,因此报错

但我们不能控制用户的操作。只能在代码层面解决问题。

此时我们知道1、错误的类型,但不知道2、错误合适发生————异常处理

while True:
    try:
        data = conn.recv(1024)#接收数据
        print(data)
        conn.send(data.upper())#将接收的数据变成大写
    except ConnectionResetError as m :
        print(m)

此时一直打印报错信息、

[WinError 10054] 远程主机强迫关闭了一个现有的连接。
[WinError 10054] 远程主机强迫关闭了一个现有的连接。
[WinError 10054] 远程主机强迫关闭了一个现有的连接。
[WinError 10054] 远程主机强迫关闭了一个现有的连接。
[WinError 10054] 远程主机强迫关闭了一个现有的连接。

应该break掉循环

while True:
    try:
        data = conn.recv(1024)#接收数据
        print(data)
        conn.send(data.upper())#将接收的数据变成大写
    except ConnectionResetError as m :
        print(m)
        break

健壮性补充:

客户端输入空:

#加入判断
while True:

    msg = input('msg:>>>').encode('utf-8')
    if len(msg)==0:
        continue
    #先写send与recv取决于服务端先写的什么
    client.send(msg)
    data = client.recv(1024)
    print(data)

引出问题:服务端是24小时不停服务!以及固定的ip和端口。现在写的服务端只满足第二点

如何同时满足这两个要求?

我们应该在客户端断开之后,继续跑到门口接客!即把"旧的"conn"双向连接通过"conn.close()"关闭,建立新的双向连接

# 完整的客户端、服务端
import socket

"""
服务端
    固定的ip和port
    24小时不间断提供服务
"""
server = socket.socket()  # 生成一个对象
server.bind(('127.0.0.1',8080))  # 绑定ip和port
server.listen(5)  # 半连接池

while True:
    conn, addr = server.accept()  # 等到别人来  conn就类似于是双向通道
    print(addr)  # ('127.0.0.1', 51323) 客户端的地址
    while True:
        try:
            data = conn.recv(1024)
            print(data)  # b''  针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b''
            if len(data) == 0:break
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

import socket


client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    data = client.recv(1024)
    print(data)

现在产生一个状态,开多个客户端,服务端始终只会和第一个客户交互,除非第一个客户端停止,才继续跟下一个客户交互!

半连接池的概念

server.listen(5),允许最大等待数。有一个小房间正在干活,外面还等5个

subproccess模块回顾

import subprocess
cmd = input('请输入指令>>>')
res = subprocess.Popen(
     cmd,                     #字符串指令:'dir','ipconfig',等等
    shell=True,              #使用shell,就相当于使用cmd窗口
    stderr=subprocess.PIPE,  #标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到
    stdout=subprocess.PIPE,  #标准输出,正确指令的输出结果被它拿到
)
print(res.stdout.read().decode('gbk'))
print(res.stderr.read().decode('gbk'))

res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
且只能从管道里读一次结果,PIPE称为管道

现在,多打印了一行stuout.read().decode('gbk'),蓝色箭头显示多了一空行

基于网络执行subproccess的结果

客户端

import socket
import subprocess
client = socket.socket()
client.connect(('127.0.0.1',50801))

while True:
    msg = input(":>>>")
    if len(msg) == 0: continue
    client.send(msg.encode('utf-8'))  # 对别人说话

    data = client.recv(1024)  # 听别人说话
    print(len(data))
    print(data.decode('gbk'))


# client.close() # 挂电话

服务端

import socket
import subprocess

server = socket.socket()  # 生成一个对象
server.bind(('127.0.0.1',50801))  # 绑定ip和port
server.listen(2)  # 半连接池

while True:
    conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
    while True:
        try:
            data = conn.recv(1024)  # 听别人说话 接收1024个字节数据          阻塞
            if len(data) == 0:break
            cmd = data.decode('utf-8')
            obj = subprocess.Popen(cmd,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE,)
            res = obj.stdout.read() + obj.stderr.read()

            print(len(res))
            conn.send(res)  # 给别人回话
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()
# server.close()

TCP粘包问题:

数据取不干净。同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

TCP的一个特点:

会将数据量比较小的并且时间间隔比较短的数据
一次性打包发送给对方.

现在尝试解决一下

继续扩大recv的容量试试

那么根据上述理论,需要知道每一次数据的准确小大, 就可以避免粘包问题。
如果只知道数据很大,又不知道设置多大合适。此时若将recv设置的过大,内存会报错

解决粘包思路

recv设置好每一个数据的大小。需要一个东西来告诉我们这个数据有多大,但我们也不知道这个人有多大!
思路找一个人告诉我这个数据多大!
找一个探字,如果探子的长度是固定的,比如4b,6bytes。那么conn.recv(4)就确定了,那么问题就解决了!
如何固定探字长度?引出struct的概念。将探子的长度固定(struct模块打包数据):

struct模块如何使用?
import struct

res= '23werfgfreghfcgfrefghffewgsfbg'
res1= struct.pack('i',len(res))
print(len(res1))

此时无论数据多大,打包之后都是4!

现在有一个问题:如何取回被打包的数据?(解包)
res2 = strcut.unpack('i',res1)[0]
#res2为真实数据的长度

客户端

import socket
import struct

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    msg= input('请输入指令:》》》》').strip()
    if len(msg)==0:
        continue
    msg = client.send(msg.encode('utf-8'))
    header_dic = client.recv(4)
    data_len = struct.unpack('i',header_dic)[0]
    data=b''
    len_recv=0
    while len_recv<data_len:
        recv_data = client.recv(1024)
        data += recv_data
        len_recv +=len(recv_data)
    print(data.decode('gbk'))
client.close()

服务端

import json
import socket
import struct
import subprocess

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)


while True:
    conn, addr = server.accept()
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            cmd = data.decode('utf-8')
            obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res= obj.stdout.read()+obj.stderr.read()
            print(len(res))

            header = struct.pack('i',len(res))

            conn.send(header)
            conn.send(res)
        except Exception as e:
            print(e)
            break
    conn.close()



# server.close()

又产生了一个问题:

即使是体积最大的Q模式,接收数据长度如果太大,也接收不了,那如何解决呢?

res1 = struct.pack('q',1232323234234312444)
#报错:
#struct.error: argument out of range

模拟通信最完整版

#客户端
import json
import socket
import struct

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    msg = input('>>>').encode('utf-8')
    # 发送二进制命令
    client.send(msg)
    d_header = client.recv(4)#接收字典报头
    #解压字典报头
    dict_size = struct.unpack('i',d_header)[0]
    #有了字典大小,就可以接受字典数据
    b_j_dict = client.recv(dict_size)
    real_dict = json.loads(b_j_dict.decode('utf-8'))

    recv_size = 0
    real_data =b''
    while recv_size<real_dict.get('filesize'):
        data = client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))


#服务端
import json
import socket
import struct
import subprocess

server=socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,adrr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            cmd=cmd.decode('utf-8')
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res=obj.stdout.read() + obj.stderr.read()#不加.read()就是二进制数据
            print(len(res))
            d={'name':'sll','filesize':len(res),'info':222212121}
            json_d = json.dumps(d)
            d_header=struct.pack('i',len(json_d))
            conn.send(d_header)#解压的结果就是字典的长度
            #有字典的长度就可以发送字典数据了
            conn.send(json_d.encode('utf-8'))
            #字典数据内有真实数据长度
            #现在就可以发送真实数据了
            conn.send(res)
        except BaseException as e:
            print(e)
            break
    conn.close()

总结:

使用struct以及字典的目的就是确定发送文件的大小,通过while循环一次依次接收值

posted @ 2019-08-08 18:48  xg1321  阅读(179)  评论(0编辑  收藏  举报