socket也叫套接字,是对各种协议的封装,实现收发数据。


Python里socket工作过程:(图片来自网络)

Python基础:网络编程socket基本篇_python


socket在Python中实际上是一个模块,实现发送和接收数据的功能。


因为socket是一个类,所以只导入模块需要使用socket.socket()创建一个socket对象。


  • 创建一个socket格式:

socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

参数名选项名称作用
familyAF_UNIX
unix系统进程间传输数据
AF_INETIPv4网络传输数据
AF_INET6IPv6网络传输数据
typeSOCK_STREAM
流式数据,TCP

SOCK_DGRAM数据报式数据,UDP

SOCK_RAW
原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。

SOCK_RDM
是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。

SOCK_SEQPACKET
连续的数据包传输(已废弃)
proto
0默认是0,根据地址簇和套接类别自动选择合适的协议
fileno默认是None
If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd(), fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close().



  • socket对象的方法:

    1、socket分为服务端和客户端。

    2、TCP传输不需要IP,UDP传输需要IP地址。

    3、socket传输字符串需要变成byte型。

    4、列表、字典等数据也需要成变byte型。json处理过的数据是字符型的,decode后可以进行send。

    5、传输大数据,使用长度时,要注意len的对象是原数据,还是encode后的数据,接收方也得计算相应的数据。否则会造成文件长度不匹配


方法名对象作用
bind(地址)
服务端绑定服务端地址,IPv4下,是元组的形式(地址,端口)
listen(backlog)服务端设定客户端连接数量,数字
accept()服务端

完整的接收信息:

(<socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6666), raddr=('127.0.0.1', 58775)>, ('127.0.0.1', 58775))

<>部分是套接字信息

后面元组是客端地址。

connect(地址)客户端绑定服务端地址,IPv4下,是元组的形式(地址,端口)
connect_ex()客户端功能与connect相同,但是成功返回0,失败返回errno的值。
s.recv(bufsize[,flag])服务和客户端

接受TCP套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。

bufsize官方建议8192,不同系统最大数值不同,一般一次可以收10M左右。

s.send(string[,flag])发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall(string[,flag])

完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

s.recvfrom(bufsize[.flag])接受UDP套接字的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto(string[,flag],address)发送UDP数据。将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close()关闭套接字。
s.getpeername()返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname()返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value)设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen])返回套接字选项的值。
s.settimeout(timeout)设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.fileno()套接字的文件描述符


  • 服务端连接实例:

import socket

server = socket.socket()
server.bind("localhost",6666)   # localhost是本地主机,也可以写172.0.0.1
server.listen(6)                # 同时允许5个客户端

while True:                     # 此位置的while是为了客户端结束后,再等待其它客户端进入。
    conn,addr = server.accept() # 接收一套接字信息,和地址。对应的是客户端的connect
    
    while True:
        conn.recv(1024)         # 服务端先接收数据,可以改变每次接收的数值,但是不要小于客户端发送的值。
        conn.send(b'00000')              # 字符前面加r,变成字节数据,才可以传输
        # 这里就是互相通信的主体,可以有多个recv和send,需要注意的是,一收一发,要和客户端对应
        # 服务端和客户端不能同时收或同时发。
        
                break                            # 结束此客户端,继续listen其它客户端


  • 客户端连接实例:

import socket

client = socket.socket()                        # 创建套接字对象
client.connect(('localhost',6666))              # 连接的主机名和端 口,也可以是字符串的ip地址  "127.0.0.1"
while True:
    client.send(b'11111')
    client.recv(1024)
    # 这里就是互相通信的主体,可以有多个recv和send,需要注意的是,一收一发,要和客户端对应
    break

client.close()                                  # 客户端关闭连接。


  • 简单FTP制作的问题点

json.decoder.JSONDecodeError: Extra data: 

因为传输的过程中有二进制数据,所以json无法decode。


传输文件完成时怎么返回?

客户端都一收一发。并且在传输个列表,第一项是标志,第二项是True,当两项不匹配时,提示错误,并返回选项列表。



有时候服务器运行程序时间长,没有到接收语句,而客户端发送数据太快,导致出错

在客户端加个sleep...



logging使用filehandler中文乱码

创建filehandler时,写入encode参数

file_handler = logging.FileHandler(log_path,encoding='utf-8')


调用logging模块,重复输出

1、使用removeHandler()把这个logger里的handler移除掉

2、在log方法里做判断,如果这个logger已有handler,则不再添加handler。


字典、列表无法传输

使用json序列化后传输。json序列化后原来是字节的!


“粘包”:A给B连续发送两个send,B接收到的两个数据都混在一起,分不清第一次还是第二次接收的

原因是,A发送时存在缓存区,大约0.5S后缓存消失

如果想要分开两次的数据,A使用send后立即执行recv,接收B的send,然后再发送第二句。

            '''更好的解决粘包'''
            # recv_file_size 已经接收的大小
            # file_size      文件总大小
            # base_recv      每次接收的大小
            base_recv = 1024
            recv_file_size = 0
            while recv_file_size<file_size:

                data = conn.recv(base_recv).decode()
                if file_size-recv_file_size < base_recv:
                    base_recv = file_size-recv_file_size
                recv_file_size =+ len(data)