Python socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

创建套接字对象
s = socket.socket(socket_family,socket_type,protocal=0)
# socket_family 可以是 AF_INET 或 AF_INET6,socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM,protocol 一般不填,默认值为 0

服务端套接字函数
s.bind()            绑定(主机,端口号)到套接字
s.listen()          开始TCP监听
s.accept()          被动接受TCP客户的连接,(阻塞式)等待连接的到来

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

公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件

基于TCP的套接字

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

tcp服务端

import socket

ip_port = ('127.0.0.1', 8081)  # ip和port
BUFSIZE = 1024  # 一次接收的字节数
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # AF_INET == ipV4, SOCK_STREAM == tcp协议
s.setsockopt(SOL_SOCKET,SO_REUSEADDR, 1)  # 由于tcp断开连接的四次挥手time_wait的状态会占用地址,加这一句能够重用地址
s.bind(ip_port)  # 绑定ip和port
s.listen(5)  # 监听客户端的连接

while True:  # 新增接收链接循环,可以不停的接受连接
    conn, addr = s.accept()  # 生成连接对象conn, addr为客户端的地址
    # print(conn)
    # print(addr)
    while True:  # 新增通信循环,可以不断的通信,收发消息
        try:  # 捕获连接突然断开的异常,使程序能正常执行不会终止
            msg = conn.recv(BUFSIZE)  # 接收消息,此时消息为二进制数据
            print(msg, type(msg))
            conn.send(msg.upper())  # 向客户端回复消息
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()  # 关闭连接对象

s.close()  # 关闭服务端套接字, 可省略

tcp客户端

import socket

ip_port = ('127.0.0.1', 8081)  # 服务端地址和端口
BUFSIZE = 1024  # 一次接收的字节数
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # AF_INET == ipV4, SOCK_STREAM == tcp协议,与服务端一致
s.connect_ex(ip_port)  # 连接服务端
while True:  # 新增通信循环,客户端可以不断发收消息
    msg = input('>>: ').strip()
    if len(msg) == 0: 
        continue
    s.send(msg.encode('utf-8'))  # 发消息,只能发送字节类型
    feedback = s.recv(BUFSIZE)  # 收消息
    print(feedback.decode('utf-8'))

s.close()  # 关闭套接字

 基于UDP的套接字

udp是无链接的,先启动哪一端都不会报错

udp服务端

import socket

ip_port = ('127.0.0.1', 9000)  # 服务端地址和端口
BUFSIZE = 1024  # 一次接收的字节数
udp_server_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # AF_INET == ipV4, SOCK_DGRAM == udp协议

udp_server_client.bind(ip_port)  # 绑定ip和port

while True:
    msg, addr = udp_server_client.recvfrom(BUFSIZE)  # 循环接收消息,addr == 客户端地址
    print(msg, addr)

    udp_server_client.sendto(msg.upper(), addr)  # 发送消息

udp客户端

import socket

ip_port = ('127.0.0.1', 9000)  # 服务端地址和端口
BUFSIZE = 1024  # 一次接收的字节数
udp_server_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # AF_INET == ipV4, SOCK_DGRAM == udp协议,与
# 服务端一致
while True:  # 循环通信
    msg = input('>>: ').strip()
    if not msg:
        continue
    udp_server_client.sendto(msg.encode('utf-8'), ip_port)  # 发送消息
    back_msg, addr = udp_server_client.recvfrom(BUFSIZE)  # 接收消息
    print(back_msg.decode('utf-8'), addr)

粘包现象

只有基于tcp的socket才会出现粘包现象

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包

两种情况下会发生粘包

  • 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
  • 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

粘包现象的解决方式

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据


struct模块 

该模块可以把一个类型,如数字,转成固定长度的bytes

 struct模块用法示例代码

import struct
import binascii
import ctypes

values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')

print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)


s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)

print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))

s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))

发送时:

先发报头长度

再编码报头内容然后发送

最后发真实内容

 

接收时:

先手报头长度,用struct取出来

根据取出的长度收取报头内容,然后解码,反序列化

从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

socketserver实现并发

实现并发的类必须要继承socketserver.BaseRequestHandler类,要实现handle()方法

import socketserver
import struct
import json
import os
class FtpServer(socketserver.BaseRequestHandler):
    coding='utf-8'
    server_dir='file_upload'
    max_packet_size=1024
    BASE_DIR=os.path.dirname(os.path.abspath(__file__))
    def handle(self):
        print(self.request)
        while True:
            data=self.request.recv(4)
            data_len=struct.unpack('i',data)[0]
            head_json=self.request.recv(data_len).decode(self.coding)
            head_dic=json.loads(head_json)
            # print(head_dic)
            cmd=head_dic['cmd']
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(head_dic)
    def put(self,args):
        file_path = os.path.normpath(os.path.join(
            self.BASE_DIR,
            self.server_dir,
            args['filename']
        ))

        filesize = args['filesize']
        recv_size = 0
        print('----->', file_path)
        with open(file_path, 'wb') as f:
            while recv_size < filesize:
                recv_data = self.request.recv(self.max_packet_size)
                f.write(recv_data)
                recv_size += len(recv_data)
                print('recvsize:%s filesize:%s' % (recv_size, filesize))


ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)  # 这是tcp协议的并发使用方法
# socketserver.ThreadingUDPServer()  # udp协议的使用方法
ftpserver.serve_forever()

 

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值