Loading

Python网络编程

Socker套接字

Socket又称套接字,是进程间通信的一种方式,网络化的应用程序在开始任何通讯之前都必须先创建套接字。我们每天浏览网页,聊天,收发Email等都是基于Socket来完成通信的

Python中的网络编程

Python中提供了socket模块。模块中的socket()函数被用来创建套接字。套接字也有自己的一套函数来提供基于套接字的网络通信。

创建socker

使用socket.socket()函数来创建套接字,语法如下:

socket(family, type, proto, fileno=None)
  • family: 套接字家族,可以是AF_UNIX或者AF_INET
  • type: 套接字类型,可以是面向连接的(TPC协议)或者是非连接的(UDP协议),分为SOCK_STREAM或SOCK_DGRAM。
  • proto: 所用的协议,一般不填默认为0

套接字对象常用(内建)方法

服务器端套接字函数:

函数 描述
s.bind() 绑定地址(主机号,端口号对)到套接字
s.listen(backlog) 开启TCP监听,backlog参数用来指定最多允许多少个客户端连接到服务器,它的值至少为1。
s.accept() 被动接受TCP客户端连接,等待连接的到来

客户端套接字函数:

函数 描述
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数:

函数 描述
s.recv(bufsize) 接收TCP数据,数据以bytes类型返回,bufsize为要接收的最大数据量
s.send() 发送TCP数据
s.sendall() 完整发送TCP数据
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址,返回值通常是元组
s.getsockname() 返回套接字自己的地址,通常是一个元组
s.setsockopt(level, optname, value) 设置给定套接字选项的值,一般用来设置端口号复用
s.getsockopt() 返回套接字选项的值
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒,值为None表示没有超时期。
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None
s.fileno() 返回套接字的文件描述符
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设置为阻塞模式
s.makefile() 创建一个与该套接字相关联的文件

基于TCP协议的socket通信

TCP通信时,一定要先建立相关的连接,才能发送数据。

客户端发送消息,服务端接收消息:

服务端:server.py

import socket

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

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

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

# 4.建立三次握手
conn, addr = sk.accept()

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

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

# 7.释放端口
sk.close()

客户端:client.py

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

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

# 3.发送数据,二进制的字节流
sk.send("今天是周末啊".encode("utf-8"))

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

说明:当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放。

解决方法:

  • 更改服务端端口号

  • 设置端口号复用,也就是说让服务端程序退出后端口号立即释放,设置如下:

    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    

TCP网络应用程序的注意事项:

  • 当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接。
  • TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的。
  • TCP服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序。
  • 关闭accept返回的套接字意味着和这个客户端已经通信完毕。
  • 关闭listen后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经连接成功的客户端还能正常通信。

基于TCP协议的socket循环通信

server.py

import socket

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

# 让当前端口重复绑定多个程序(仅在测试阶段使用)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 2.在网络中注册主机(绑定ip和端口号)
sk.bind(("127.0.0.1", 9000))

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

while True:
    # 4.建立三次握手
    conn, addr = sk.accept()
    # 5.收发数据
    while True:
        try:
            res = conn.recv(1024)
            if res.lower() == b"q":
                print("客户端退出聊天了")
                break
            print(f"来自客户端的消息:{res.decode('utf-8')}")
            to_client_value = input("请输入服务端要给客户端发送的消息:").encode("utf-8")
            conn.send(to_client_value)
        except ConnectionResetError:
            print("客户端连接中断")
            break

    # 6.四次挥手
    conn.close()
# 7.释放连接
sk.close()

client.py

import socket

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

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

# 3.收发数据
while True:
    to_server_data = input("请输入您要发送的数据:").strip().encode("utf-8")
    # 如果客户端发送空的内容,服务都就会阻塞,所以无论哪一端,都不能发送空的消息
    if not to_server_data:
        print("发送内容不能为空")
        continue
    sk.send(to_server_data)
    if to_server_data.lower() == b"q":
        break
    res = sk.recv(1024)
    print(f"来自服务端的消息:{res.decode()}")


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

粘包现象

使用tcp协议在发送数据时,会出现粘包现象。

在客户端和服务器端会有一个数据缓冲区,缓冲区用来临时保存数据,为了能够完整的接收到数据,因此缓冲区会设置的比较大,在收发数据较频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度,导致客户端和服务端有可能把多条数据当成是一条数据进行截取,造成粘包。

使用strcut模块解决粘包

  • pack()函数:可以把任意长度的数字转化成具有4个字节的固定长度字节流。
  • unpack()函数:把4个字节的值恢复成原本的数字,返回的是元组。

server.py

import socket
import struct

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(("127.0.0.1", 9000))
sk.listen(3)

conn, addr = sk.accept()
msg = input("请输入要发送的数据:").encode()

# 先把长度发过去
res = struct.pack("i", len(msg))
conn.send(res)

# 发送第二个数据
conn.send(msg)
# 发送第三个数据
conn.send("hello".encode())

conn.close()
sk.close()

client.py

import socket
import struct
import time

sk = socket.socket()
sk.connect(("127.0.0.1", 9000))
time.sleep(0.1)

# 接收第一次发送过来的数据(数据的大小)
n = sk.recv(4)
tup = struct.unpack("i", n)
n = tup[0]

# 接收第二次真正的数据
res1 = sk.recv(n)
# 接收第三次真正的数据
print(res1.decode())

res2 = sk.recv(1024)
print(res2.decode())

# 关闭连接
sk.close()

基于UDP协议的socket通信

基于udp协议的socket通信无须建立连接,先开启服务端或者客户端都可以。

udp_server.py

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(("127.0.0.1", 9000))
while True:
    from_client_data = server.recvfrom(1024)  # 阻塞,等待来消息
    print(from_client_data)
    print(f"来自客户端{from_client_data[1]}: {from_client_data[0].decode('utf-8')}")
    send_client_data = input("请输入要发送给客户端的消息:")
    server.sendto(send_client_data.encode("utf-8"), from_client_data[1])

udp_client.py

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
    send_server_data = input("请输入要发送给服务端的消息:").strip()
    client.sendto(send_server_data.encode("utf-8"), ("127.0.0.1", 9000))  #
    data, addr = client.recvfrom(1024)
    print(f"来自服务端{addr}的消息: {data.decode('utf-8')}")

posted @ 2021-07-05 09:53  charlatte  阅读(53)  评论(0编辑  收藏  举报