0
点赞
收藏
分享

微信扫一扫

49.网络编程

梦想家们 2022-01-13 阅读 67

文章目录

1.客户端与服务端

客户端:(client)响应服务器向客户提供本地服务的程序。
服务端: (server)对客户端机器提供数据服务程序。
服务器:运行服务端的计算机被称为服务器。

1.1常用的两种架构

1. cs架构 client server  客户端-服务器模式。
2. bs架构 browser server 浏览器-服务器模式。

cs 架构要求
1.用户操作系统安装客户端,产商操作系统部署服务端。
2.每个用户需要独立安装软件,服务端升级也要每个用户升级客户端。

2.socket抽象层

img

socket:应用层与TCP/IP协议通信的中间抽象出来的一组接口,这个接口称为socket抽象层。
socket = ip + port。
ip 标识互联网中主机位置。
port 标识主机上的一个应用程序。
ip地址是配置到网卡上的,而port书应用程序开启的,ip与port的绑定标识了互联网中独一无二的一个应用程序。

3.套接字的工作流程

image-20210723162836865

3.1 套接字方法

导入scoket模块,Python自带模块.
.scoket()  创建scoket对象
.bind()    绑定(主机,端口号)到套接字。
.listen()  开启TCP监听,定义半链接池内的可以容纳请求服务的数量
.accept()  被动接受TCP客户端的连接,为阻塞态,等待链接的到来。返回链接对象和链接的地址。

客服端套接字方法
.connect() 主动初始化TCP服务器连接

通用方法
.recv()  接收TCP数据,在括号内写上接收字符的个数。
.send()  发送TCP数据,在括号内填入需要传输的数据。

* 基于网络传输发送的都是二进制数据。在Python中可以把tybe类型看成是二进制类型。
		
.close() 关闭套接字。

4.基于TCP协议通信

TCP是基于链接的,必须先启动服务端,然后再启动客户端去链接服务器。

4.1服务端

# A1.调用socket() 实例化一个服务端对象。

import socket                          
server = socket.socket()                

# A2.TCP服务端对象调用bind()。
# 方法绑定监听的端口,给socket赋值一个ip地址个端口(以元组类型赋值)。

server.bind(('127.0.0.1', 8080))        

# A3.当服务器有了ip和端口号,调用listen(),方法进行监听,设置半链接池。

server.listen(5)

# A4.服务器对象调用accept()阻塞,等待客户端连接,如果成功则会返回两个参数。
# 1. sock 当前连接对象 (双向通道)  2. addr 客服端的地址 ip + 端口。

sock, addr = server.accept()           

# A5.服务端接收TCP协议信息,设置接收数据的最大字节数1024。byte类型。

date = sock.recv(1024)
print(date)                             

# A6.服务端对信息进行处理,并将处理的结果调用send()发送TCP协议信息,给客户端。

sock.send(date.upper())  # 小写转大写(只含有英文字符的byte类型可以直接进行转换)

# A7. 关闭与客户端通信的套接字。

sock.close()

# A8.关闭服务端套接字。

server.close()

4.2客户端

# B1.此时客户端调用socket()实例化一个对象。

import socket  
client = socket.socket()  

# B2.客户端对象调用connect()方法绑定TCP服务端对象监听的位置,连接成功。

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

# B3.客户端对象调用send()方法发送数据请求, byte类型。

client.send(b'hello')

# B4.客户端接收信息,设置接收数据的最大字节数1024。byte类型。
date = client.recv(1024)
print(date) 

# B5.客户端套接字关闭,结束。
client.close()

5.循环通信

5.1服务端

import socket

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

# 成功建立了连接
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))

# 循环通信
while True:
    # 接收消息

    data = sock.recv(1024)
    if len(data) == 0:  # 当客服端关闭的时候会收到 空的type数据.
        break  # 输入空格的话,空格被转为二进制,data的长度不会为0.
    print('收到消息客户端端信息: %s' % data.decode('utf8'))

    # 发送消息
    in_data = input('发送的消息>>>:').strip()
    sock.send(in_data.encode('utf8'))

sock.close()
server.close()

5.2客户端

import socket

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

# 循环通信
while True:
    # 发送信息, 当用户输入q的时候,关闭服务端
    in_data = input('发送的消息("按下q关闭通信")>>>:').strip()
    if in_data == 'q':
        break
    client.send(in_data.encode('utf8'))

    # 接收信息
    data = client.recv(1024)
    print(data.decode('utf8'))

client.close()

5.3测试

-----------与顾客的聊天记录-----------
收到消息客户端端信息: 1
发送的消息>>>:2
收到消息客户端端信息: 3
发送的消息>>>:4
Process finished with exit code 0
-----------与客服的聊天记录-----------
发送的消息("按下q关闭通信")>>>:1
收到消息服务端端信息: 2
发送的消息("按下q关闭通信")>>>:3
收到消息服务端端信息: 4
发送的消息("按下q关闭通信")>>>:q  
Process finished with exit code 0
服务端在与客服端正常通信中,如果客服端正常断开,服务端会一直接到空数据包,byte类型数据 b'',
加上一个判断语句,如果收到空的数据包就表示客户端断开了,则使用break退出通信循环。

5.4直接回车

send()的data包不可以空.

在测试代码的时候直接输入回车,data为空.
send()发数据,data为空,数据就不会被发送出去,这行程序登入执行完了,到下一句去了.
服务端接收不到数据一直等待客服端发信息,另一边客户端等待服务端发信息.
就像两个人打电话,都等对方开口.噶在这里了.

image-20220112201316945

# 客户端代码
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('与客服的聊天记录'.center(30, '-'))
# 循环通信
while True:
    # 发送信息
    in_data = input('发送的消息("按下q关闭通信")>>>:').strip()

    if len(in_data) == 0:  # 防止客户端输入空
        print('输入不能为空')
        continue

    if in_data == 'q':
        break

    client.send(in_data.encode('utf8'))

    # 接收信息
    data = client.recv(1024)
    print('收到消息服务端端信息: %s' % data.decode('utf8'))

client.close()

image-20220112202846963

-----------与客服的聊天记录-----------
发送的消息("按下q关闭通信")>>>:(直接回车)
输入不能为空
发送的消息("按下q关闭通信")>>>:q

5.5客户端非正常断开连接

image-20220112185617107

对报错的代码行进行针对性的异常捕获,做出处理.避免服务器直接宕机.
# 服务端
import socket

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

# 成功建立了连接
sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))

# 循环通信
while True:
    # 接收消息
    try:
        data = sock.recv(1024)

    except ConnectionResetError as f:
        print(f)
        break
    if len(data) == 0:
        break
    print('收到消息客户端端信息: %s' % data.decode('utf8'))

    # 发送消息
    in_data = input('发送的消息>>>:').strip()
    sock.send(in_data.encode('utf8'))

sock.close()
server.close()

image-20220112203249111

6.循环连接

服务器正常情况下是24 * 365天运行的.
客服端断开,就重新进入监听的状态,去服务其它的客户端.
# 服务端重启频繁报端口占用错误&添加兼容性代码(mac linux)
# 在bind前加
from socket import SOL_SOCKET, SO_REUSEADDR
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  
# 服务端
import socket

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

# 成功建立了连接
while True:  # 循环连接
    sock, addr = server.accept()
    print('与顾客的聊天记录'.center(30, '-'))

    # 循环通信
    while True:
        # 接收消息
        try:
            data = sock.recv(1024)
        except ConnectionResetError as f:
            print(f)
            break
        if len(data) == 0:
            break
        print('收到消息客户端端信息: %s' % data.decode('utf8'))

        # 发送消息
        in_data = input('发送的消息>>>:').strip()
        sock.send(in_data.encode('utf8'))

    sock.close()

server.close()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJLKfSgj-1642005029164)(https://s2.loli.net/2022/01/13/apS1VDWELzb7tlN.png)]

当客户端断开后,重新监听,等待下一个客户端连接.
# 服务端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('与客服的聊天记录'.center(30, '-'))
# 循环通信
while True:
    # 发送信息
    in_data = input('发送的消息("按下q关闭通信")>>>:').strip()

    if len(in_data) == 0:
        print('输入不能为空')
        continue

    if in_data == 'q':
        break

    client.send(in_data.encode('utf8'))

    # 接收信息
    data = client.recv(1024)
    print('收到消息服务端端信息: %s' % data.decode('utf8'))

client.close()

image-20220112210450561

7.半连接池

半连接池内是等待服务的用户请求(可以排队的最大连接个数),目前只能同时服务一个用户.
半连接池内满了.其他的用户就无法进行访问了.

image-20220112211010164

第一个客服端结束访问后,开始服务第二个客户端,这是时候半连接池才腾出一个可以排队的位置.

8.UDP通信流程

image-20210723162933330

8.1参数方法

udp协议是无连接的,先启动谁都没问题。
 参数 (type=socket.SOCK_DGRAM),设置通信UDP协议。
设置客户务端的socket 参数 (socket.AF_INET, socket.SOCK_DGRAM),设置通信UDP协议。
.recvfrom() 接收UDP数据。
.sendto()   发送UDP数据。

8.2 UDP服务端

import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8090))

date, client_addr = server.recvfrom(1024)
print(date)
print(client_addr)
server.sendto(date.upper(), client_addr)

server.clase()

8.3 UDP客户端

import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

msg =input('输入:').strip()
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8090))
date, sever_addr = client.recvfrom(1024)
print(date)

client.close()

8.4 循环通信

import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8090))

while  True:
    date, client_addr = server.recvfrom(1024)
    print(date)
    print(client_addr)
    server.sendto(date.upper(), client_addr)

server.clase()
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    msg =input('输入:').strip()
    client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8090))
    date, sever_addr = client.recvfrom(1024)
    print(date)

client.close()

clase()
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    msg =input('输入:').strip()
    client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8090))
    date, sever_addr = client.recvfrom(1024)
    print(date)

client.close()

9.黏包

TCP协议有两个特性:
1.TCP是基于流式传输的,数据管道的数据没有被完全取出,会先去上次没有取完的值.
2.当数据量比较小且时间间隔比较短的多次数据,
  那么TCP会自动打包成一个数据包发送.
  
在建立通行后.recv()控制每次接收数据的字节个数.
数据不会丢失,但是取值的范围不对导致顺序错乱.

9.1特性1

# 服务端
import socket

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

sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))

# 循环通信
while True:
    data = sock.recv(5)
    print('收到消息客户端端信息: %s' % data.decode('utf8'))

    # 发送消息
    in_data = input('发送的消息>>>:').strip()
    sock.send(in_data.encode('utf8'))
# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))
print('与客服的聊天记录'.center(30, '-'))
# 循环通信
while True:
    # 发送信息
    in_data = input('发送的消息>>>:').strip()
    client.send(in_data.encode('utf8'))

    # 接收信息
    data = client.recv(5)
    print('收到消息服务端端信息: %s' % data.decode('utf8'))

image-20220112215240042

数据值比recv()设置的值过小的时候,下次取值,从上次没取完的开始取.

9.2特性2

import socket

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

sock, addr = server.accept()
print('与顾客的聊天记录'.center(30, '-'))


data = sock.recv(11)
print('收到消息客户端端信息: %s' % data.decode('utf8'))
import socket

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


client.send(b'he')
client.send(b'llo')
client.send(b'word')
-----------与顾客的聊天记录-----------
收到消息客户端端信息: hello word
数据值比recv()设置的值过大的时候,短时间发送多个小数据会打包成一个数据进行传输.
这个组合包最大值为recv()设置的值.

9.3报头

报头的作用:能够标识即将到来的数据具体信息.
在报头中携带字节的具体数值,准确的设置recv()的值.
避免 recv()的值 过大/过小带来的黏包现象.

9.4struct模块

 # 报头的长度必须是固定的
 常用的模式整数类型b、h、i、q分别
 表示1个字节、2个字节、4个字节、8个字节,对应的大写字母表示无符号整数.
import struct

#                      1111111111 是字节数
res = struct.pack('i', 1111111111)
# i 模式能制作的 四个字节的包头 struct.error: argument out of range

print(len(res))  # 4
res1 = struct.unpack('i', res)[0]  # 解析出字节
print(res1)  # 1111111111

9.5简易报头

# 服务端
import socket
import struct

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

sock, addr = server.accept()

res = sock.recv(4)
len_data = struct.unpack('i', res)[0]  # 解析出真实的数据大小

data = sock.recv(len_data)  # 真实的数据是多大的字节这个len_data就是多少
print(data)
# 客户端
import socket
import struct

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

# 发送的数据
data = b'123456789'

# 制作报头 固定 4个字节
res = struct.pack('i', len(data))
client.send(res)

# 发送真正的数据
client.send(data)

image-20220112230538797

9.6字典报头

import struct

res = struct.pack('i', 2147483648)  
# i 模式能制作的 四个字节的包头  最大表示范围为 2147483647个字节 == 2G

"""
Traceback (most recent call last):
  File "F:/synchro/Project/套接字/1.py", line 3, in <module>
    res = struct.pack('i', 2147483648)
struct.error: argument out of range
"""
import struct
import json

# 组织成字典格式
d = {
    'file_name': '好好学习.mv',
    'file_size': 1099511627776,  # 一个TB 防止数据过大 i 模式无法的四个字节无法存储
    'file_desc': '真的很好看!!!'
}

d = json.dumps(d)  # 序列化字典可以统计字符个数,传输后能反序列化使用这个字典.


res = struct.pack('i', len(d))  # i 模式能制作的 四个字节的报头
print(len(res))
res1 = struct.unpack('i', res)[0]  # 解析出变量d 的数据大小为193个字节
print(res1)

# 最后在发真实的数据 1099511627776
# 另一端通过反序列化拿到字典的值.设置rece.

​ 实例

import socket
import struct
import subprocess
import json

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

sock, addr = server.accept()

while True:
    cmd = sock.recv(1024)  # cmd 命令

    # cmd操作模块 执行cmd命令
    sub = subprocess.Popen(cmd.decode('utf8'), shell=True,
                           stdout=subprocess.PIPE,  # 收集命令执行后正确的结果
                           stderr=subprocess.PIPE  # 收集命令执行后错误的结果
                           )
    # 读出来的数据就是二进制,中国的操作系统默认是使用gkb编码格式
    data = sub.stdout.read() + sub.stderr.read()

    # 1.组织字典
    dic = {
        'k1': 'v1',
        'size': len(data),
        # ...
    }
    # 2.转为json格式字符串
    dic_json = json.dumps(dic)

    # 3.制作接收报头 (字典的数据) 4
    len_dic = struct.pack('i', len(dic_json))

    # 4.发送字典的大小
    sock.send(len_dic)

    # 5.发送字典(字典内包含真实数据的大小)
    sock.send(dic_json.encode('utf8'))

    # 6.发送真实的数据
    sock.send(data)
import json
import socket
import struct

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

while True:
    # 1.在cmd输入命令
    cmd = input('输入执行的命令>>>:')
    client.send(cmd.encode('utf8'))

    # 2.接收报头
    res = client.recv(4)
    # 3.接收字典大小
    len_dic = struct.unpack('i', res)[0]
    # 3.接收字典
    dic_json = client.recv(len_dic)

    # 4.loads会自动解码在反序列化
    dic = json.loads(dic_json)
	# 可以获取字典的任何数据 ...
    # 5.接收真实的数据
    data = client.recv(dic.get('size'))
    print(data.decode('gbk'))
输入执行的命令>>>:ipconfig

Windows IP 配置
....

image-20220113002008024

举报

相关推荐

0 条评论