socket用法(TCP, UDP)

socket : 通络通信过程中,信息拼接的工具(中文:套接字)
开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉), 在bind方法之前加上这句话,可以让一个端口重复使用: sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

TCP: 服务端和客户端简单的一次性通信

# 服务端
import socket
# 1. 创建socket对象
sk = socket.socket()

# 2. 绑定对应IP和端口(注册网络,让其他主机能够找到)
sk.bind(("127.0.0.1",9000))  # 127.0.0.1 代表本地IP

# 3. 开启监听
sk.listen()

# 4. 建立三次握手 addr接收客户端的IP和端口号
conn,addr = sk.accept()

# 5. 收发数据(recv里面的参数单位是字节,表示一次最多接收多少数据)
res = conn.recv(1024)
print(res)
print(res.decode("utf-8"))

# 6. 四次挥手
conn.close()

# 7. 退还端口
sk.close()



# 客户端
import socket

# 1. 创建一个socket对象
sk = socket.socket()

# 2. 与服务器进行连接
sk.connect(("127.0.0.1",9000))

# 3. 发送数据
sk.send("今天下了一天的雨".encode("utf-8"))

# 4. 关闭连接
sk.close()

 

TCP: 服务端和客户端循环通信

# TCP循环通信:
# 服务端
import socket
# 1. 创建一个socket对象
sk = socket.socket()
# 2. 在网络中注册主机(绑定IP和端口)
sk.bind(("127.0.0.1",9000))
# 3. 监听
sk.listen()
# 4. 建立三次握手
# 5. 接收数据
while True:   # 保证服务端永不关闭,随时能跟客户端连接
    conn, addr = sk.accept()
    while True:
        res = conn.recv(1024)
        print(res.decode("utf-8"))
        str1 = input("服务端想给客户端发送: ")
        conn.send(str1.encode("utf-8"))
        if str1.upper() == "Q":
            break

# 6.四次挥手
conn.close()
# 7.退还端口
sk.close()



# 客户端
import socket
# 1. 建立一个socket对象
sk= socket.socket()
# 2. 连接服务端
sk.connect(("127.0.0.1",9000))
# 3. 发送数据
while True:
    strvar = input("客户端想给服务端发送的消息为: ")
    sk.send(strvar.encode("utf-8"))
    res = sk.recv(1024)
    if res.upper() == b"Q":
        break
    print(res.decode("utf-8"))

# 4.断开连接
sk.close()

 

TCP的黏包现象: 

(1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。


(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包

黏包现象一:
在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
黏包现象二:
在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
总结:
发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

# TCP的黏包现象
# 服务端
import socket
sk = socket.socket()
sk.bind(("127.0.0.1",9000))
sk.listen()
coon,addr = sk.accept()

coon.send("下了一天的雨".encode())
coon.send("适合发呆".encode())
coon.close()
sk.close()


# 客户端
import socket
import time
sk = socket.socket()
sk.connect(("127.0.0.1",9000))
time.sleep(0.1)

res1 = sk.recv(1024)
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
# 下了一天的雨适合发呆  两个数据粘在了一起,这就是数据的黏包

sk.close()

TCP黏包解决方式:

# TCP的黏包现象
# 服务端
# 方式一:
import socket
sk = socket.socket()
sk.bind(("127.0.0.1",9000))
sk.listen()
coon,addr = sk.accept()
# 在发送真实数据之前,先发送真实数据的大小
coon.send("18".encode())
coon.send("下了一天的雨".encode())
coon.send("适合发呆".encode())
coon.close()
sk.close()


# 客户端
import socket
import time
sk = socket.socket()
sk.connect(("127.0.0.1",9000))
time.sleep(0.1)

res = sk.recv(2)
n = int(res.decode())

res1 = sk.recv(n)
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
# 下了一天的雨
# 适合发呆

sk.close()

##############
# TCP的黏包现象
# 服务端
# 方式二:
import socket
sk = socket.socket()
sk.bind(("127.0.0.1",9000))
sk.listen()
coon,addr = sk.accept()
# 在发送真实数据之前,先发送真实数据的大小
coon.send("00000018".encode())  # 以免客户端每次都要改第一次接收字节数
coon.send("下了一天的雨".encode())
coon.send("适合发呆".encode())
coon.close()
sk.close()


# 客户端
import socket
import time
sk = socket.socket()
sk.connect(("127.0.0.1",9000))
time.sleep(0.1)

res = sk.recv(8)  # 将第一次接受的大小固定
n = int(res.decode())

res1 = sk.recv(n)
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
# 下了一天的雨
# 适合发呆

sk.close()

# 但是方式一和方式二都太麻烦,每次都需要改动

TCP黏包解决方式三(推荐)

"""
pack: 把任意长度的数字转化成居右4个字节的固定长度的字节流

unpack: 把4个字节值恢复成原本的数字,返回是元组
# 整型用i表示, 浮点型用f表示, 字符串用s表示
"""

# TCP的黏包现象
# 服务端
# 方式三: 引入struct
import socket
import struct
sk = socket.socket()
sk.bind(("127.0.0.1",9000))
sk.listen()
coon,addr = sk.accept()

strvar = input("请输入数据:")
msg = strvar.encode()
length = len(msg)
res = struct.pack("i",length)  # 将要发送的第一个数据的长度先用struct进行压缩
coon.send(res)
coon.send(strvar.encode())
coon.send("适合发呆".encode())
coon.close()
sk.close()


# 客户端
import socket
import time
import struct
sk = socket.socket()
sk.connect(("127.0.0.1",9000))
time.sleep(0.1)

res = sk.recv(4)  # struct压缩的数据永远都是4个字节
tup = struct.unpack("i",res)
print(tup[0])  # 18

res1 = sk.recv(tup[0])
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
# 下了一天的雨
# 适合发呆

sk.close()

解决黏包场景: 应用场景在实时通讯时,需要阅读此次发的消息是什么
不需要解决黏包场景: 下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.

udp数据传输简单通信
# dup 数据传输
# 服务端
import socket
# 1. 创建一个socket对象 type=socket.SOCK_DGRAM表明创建的是UDP协议对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2. 绑定IP和端口号
sk.bind(("127.0.0.1",9000))

# 4. 接收消息,(UDP作为服务端的时候,第一次肯定是接收消息)
msg,cli_addr = sk.recvfrom(1024)
print(msg.decode())  # 今天天气咋样啊?
print(cli_addr) # ('127.0.0.1', 64877)

# 发送消息给客户端
msg = "下了一天的雨"
sk.sendto(msg.encode(),cli_addr)

# 5. 关闭连接
sk.close()


# 客户端
import socket
# 1. 创建socket对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2. 发送数据
msg = "今天天气咋样啊?"
sk.sendto(msg.encode(),("127.0.0.1",9000))

msg,ser_addr = sk.recvfrom(1024)
print(msg.decode())  # 下了一天的雨
print(ser_addr) # ('127.0.0.1', 9000)

# 3. 断开连接
sk.close()

udp 数据传输循环通信

# udp循环通信
# 服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)

sk.bind(("127.0.0.1",9000))

while True:
    while True:
        msg,cli_addr = sk.recvfrom(1024)
        print(msg.decode())

        msg = input("服务端给客户端发送的消息: ")
        sk.sendto(msg.encode(), cli_addr)
        if msg.upper() == "Q":
            break

sk.close()



# 客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)

while True:
    msg = input("客户端给服务端发送的消息: ")
    sk.sendto(msg.encode(),("127.0.0.1",9000))

    msg, ser_addr = sk.recvfrom(1024)
    if msg.upper() == b"Q":
        break
    print(msg.decode())

sk.close()

 

tcp协议:
缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包
优点:不限制数据包的大小,稳定传输不丢包

udp协议:
优点:接收时候数据之间有边界,传输速度快,不黏包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

 

posted on 2020-06-05 20:20  fdsimin  阅读(307)  评论(0编辑  收藏  举报