11-Python网络编程

socket包介绍

Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

创建一个TCP协议的socket:

socket.socket()

创建一个UDP协议的socket:

socket.socket(type = socket.SOCK_DGRAM)

socket包的特点:

  • 使用bytes类型发送和接收数据。
  • 接收数据时:linux下若连接关闭,则接收空;Windows下连接关闭,则报错。

发送数据:

send()		#可能一次不会发送完(超过MTU)
sendall()	#循环调用send()将数据发送完(一般使用sendall即可)
sendto(msg1,client_addr)		#发送数据时指定对端地址

接收数据:

recv(1500)	#接收数据(最多1500字节)
recvfrom(1024)	#不仅能接收数据,还能获取对方ip(适用于未知ip的情况)

使用TCP传输数据

服务端:

import socket
sk = socket.socket()   #创建套接字对象
address = ('127.0.0.1', 8000)
sk.bind(address)       #绑定监听IP和端口
sk.listen(3)           # 最大连接数(超过则拒绝)
conn, addr = sk.accept()  #接收连接(返回一个元组)
conn.sendall(bytes("你好", "utf8"))    #发送数据(转为bytes发送)
s = conn.recv(1024)    #接收数据,最大接收1024B数据
print(str(s, "utf8"))  # 转为UTF-8输出
conn.close()
sk.close()

客户端:

import socket
sk = socket.socket()  #创建套接字对象
address = ('127.0.0.1', 8000)
sk.connect(address)   #连接到服务端
sk.sendall(bytes("你好", "utf8"))  #发送数据(转为bytes发送)
s = sk.recv(1024)     #接收数据,最大接收1024B数据
print(str(s, "utf8")) #转为UTF-8输出
sk.close()

解释:

  • 发送和接收的顺序无要求
  • sk.accept()和conn.close()包含三次握手和四次挥手,UDP协议是没有的。

使用UDP传输数据

服务端:

import socket
sk = socket.socket(type = socket.SOCK_DGRAM)   #UDP协议必须加上 type=socket.SOCK_DGRAM
sk.bind(('127.0.0.1',9000))
msg,client_addr = sk.recvfrom(1024)    #recvfrom用于不知道对方ip时,获取到ip给client_addr
msg = msg.decode('utf-8')
print(msg)
msg1 = input('>>>').encode('utf-8')    #输入数据
sk.sendto(msg1,client_addr)
sk.close()

客户端:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
inp = input('>>>').encode('utf-8')   #输入数据
sk.sendto(inp, ('127.0.0.1', 9000))
msg = sk.recv(1024).decode('utf-8')  #这里不需要使用recvfrom,因为知道对方ip地址
print(msg)
sk.close()

粘包

粘包:原本应该分离的多个数据包,变成了一个包
发送方粘包:缓冲区中存在多个数据包,合成一起发送
接收方粘包:来不及处理缓冲区中的包,多个数据包一起处理
因为算法原因,TCP会产生粘包现象,而UDP永远不会。

粘包现象

服务端:

import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8888))
sk.listen()
conn, addr = sk.accept()
while 1:
    conn.send(b'hello')
    conn.send(b'world')
    conn.close()
sk.close()

客户端:

import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8888))
while 1:
    msg1 = sk.recv(1024)  #接收服务器返回来的数据
    print('msg1:', msg1)
    msg2 = sk.recv(1024)
    print('msg2:', msg2)
sk.close()

结果:

msg1: b'hello'
msg2: b'world'
msg1: b'helloworld'	#产生粘包现象
msg2: b'hello'
msg1: b'world'
msg2: b'hello'
msg1: b'world'
msg2: b'hello'
msg1: b'worldhello'	#产生粘包现象
msg2: b'world'
msg1: b'hello'
msg2: b'world'

结果:

连续发送和接收数据,就有可能产生粘包。

解决粘包问题-struct模块

解决思路:

  • 发送方:先发送一个头部,告诉接收端应该接收多少数据,再发送真实数据。
  • 接收方:先接收一个头部,再根据头部信息,接收相应字节的数据。

struct模块中的方法简介:

pack(fmt, v1, v2, ...)     #按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)
unpack(fmt, string)        #按照给定的格式(fmt)解析字节流string,返回解析出来的tuple
calcsize(fmt)              #计算给定的格式(fmt)占用多少字节的内存

示例:

size = struct.pack('i', 50) #将整数50转化为4个字节的struct类型

格式(上例中的fmt)

FormatC TypePython字节数
xpad byteno value1
ccharstring of length 11
bsigned charinteger1
Bunsigned charinteger1
?_Boolbool1
hshortinteger2
Hunsigned shortinteger2
iintinteger4
Iunsigned intinteger or long4
llonginteger4
Lunsigned longlong4
qlong longlong8
Qunsigned long longlong8
ffloatfloat4
ddoublefloat8
schar[]string1

利用struct模块解决粘包问题

服务端:

import socket,struct
sk = socket.socket()
sk.bind(('127.0.0.1', 8888))
sk.listen()
conn, addr = sk.accept()
while 1:
    s = b'hello'
    size = struct.pack('i', len(s))
    conn.send(size)  # 先发送头部
    conn.send(s)  # 再发送真实数据
    s = b'world'

    size = struct.pack('i', len(s))
    conn.send(size)  # 先发送头部
    conn.send(s)  # 再发送真实数据
    conn.close()
sk.close()

客户端:

import socket,struct
sk = socket.socket()
sk.connect(('127.0.0.1', 8888))
while 1:
    size = sk.recv(4)  #先接收头部
    size = struct.unpack('i', size)[0]  #转化为整数
    msg1 = sk.recv(size).decode("UTF-8")#接收真实数据
    print('msg1:', msg1)
    
    size = sk.recv(4)  #先接收头部
    size = struct.unpack('i', size)[0]  #转化为整数
    msg2 = sk.recv(size).decode("UTF-8")#接收真实数据
    print('msg2:', msg2)
sk.close()

聊天小程序

服务端:

# server.py 服务端
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #DGRAM:datagram 数据报文的意思,象征着UDP协议的通信方式
udp_server_sock.bind(ip_port)   #你对外提供服务的端口就是这一个,所有的客户端都是通过这个端口和你进行通信的

while True:
    qq_msg,addr=udp_server_sock.recvfrom(1024)# 阻塞状态,等待接收消息
    print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
    back_msg=input('回复消息: ').strip()

    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
    udp_server_sock.recv(0)

客户端:

import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

qq_name_dic={
    'taibai':('127.0.0.1',8081),
    'Jedan':('127.0.0.1',8081),
    'Jack':('127.0.0.1',8081),
    'John':('127.0.0.1',8081),
}


while True:
    while 1:
        qq_name=input('请选择聊天对象: ').strip()
        if qq_name not in qq_name_dic:
            print('没有这个聊天对象')
            continue
        break
    while True:
        msg=input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
        if msg == 'q':
            break
        msg = '发给' + qq_name + ': ' + msg
        if not msg:continue
        udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])# 必须带着自己的地址,这就是UDP不一样的地方,不需要建立连接,但是要带着自己的地址给服务端,否则服务端无法判断是谁给我发的消息,并且不知道该把消息回复到什么地方,因为我们之间没有建立连接通道
        udp_client_socket.recv(0)
        back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)# 同样也是阻塞状态,等待接收消息
        print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))

udp_client_socket.close()

posted @ 2020-07-13 22:25  NetRookieX  阅读(1)  评论(0编辑  收藏  举报