# 网络编程

# socket

又称套接字,程序通过其向网络发出或者应答请求,也可以用户主机间或者进程间的通讯

创建

socket.socket([family[, type[, proto]]])
#都是可选值,family 默认 AF_INET

family:

套接字家族,可以是 AF_UNIX 或者 AF_INET (6)

前者表示进程间通信,后者表示使用 IPv4 (6)

type:

类型,面向连接 SOCK_STREAM (默认值); 非连接 SOCK_DGRAM

前者是流式套接字,对应 TCP 协议,或者是数据报套接字,对应 UDP

proto:

协议号。一般不填,默认为 0,表示自动匹配。这个值取决于前两个值

当使用 AF_INET 以及 SOCK_RAW(原始套接字,需要 root 权限)时才需指定。
存在:

IPPROTO_TCP(6)
IPPROTO_UDP(17)
IPPROTO_ICMP(1)
IPPROTO_IP(0)

ICMP 可以用于处理 ping 命令时

内建方法

服务端
s.bind()
#绑定地址到套接字,在 AF_INET 下时元组形式保存(host,port)
s.listen()
#TCP 监听。只有在转为监听后才能 accept
s.accept()
#被动接受
--------------------
客户端
s.connect()
#主动初始化 TCP 服务连接,一般格式是元组(hostname,port)
s.connect_ex()
#上一个的扩展,遇到错误不抛异常,而是返回出错码,没有错误返回 0
--------------------
公用
s.recv()
#接受 TCP 数据
s.send()
s.sendall()
s.recvfrom()
#接受 UDP 数据
s.sendto()
#发送 UDP 数据
s.close()
#关闭套接字
s.getpeername()
#返回连接的远程地址,元组(ipaddr,port)
s.getsockname()
#返回套接字自己的地址,元组(ipaddr,port)

10061/111:connect refused

10060: time out

# 实例

import socket
import sys
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
host = socket.gethostname()
port = 9999
serversocket.bind((host, port))
# 设置最大连接数,超过后排队
serversocket.listen(5)
while True:
    # 建立客户端连接
    clientsocket,addr = serversocket.accept()      
    print("连接地址: %s" % str(addr))
    
    msg='欢迎访问菜鸟教程!'+ "\r\n"
    clientsocket.send(msg.encode('utf-8'))
    clientsocket.close()

可能不是那么重要的:

s.setblocking(True/False)
#默认为 True,阻塞模式,send/recv/accept 都要等待返回才继续执行,非阻塞反之
#非阻塞中,若操作无法完成,会报错 socket.error
s.setsockopt(level,optname,value)
#设置给定套接字选项的值
s.getsockopt(level,optname[.buflen])
#返回套接字选项的值

level
选项所属协议层级,指定对什么类型的套接字生效,SOL_SOCKET 表示所有
此外就是 IPPROTO_TCP 一类的

optname
选项名称,是 socket 模块定义的常量。包括 SO_REUSEADDR(端口重用)等

value
选项值,整数(一般是 1/0,表示开启关闭)或者字节流

选项:

SO_REUSEADDR
通过 fork 子进程来继承 socket,实现多个进程共用一个 socket

SO_REUSEPORT
通过多个 socket 共用一个端口实现,可以多个进程同时监听使用,而不是同一时间能有一个,这些进程必须有相同的 euid(有效用户 id)和 uid

tip: 有配置 SUID 的文件在运行时会强制 euid 为文件所有者,需要注意

这个问题参见: 网络编程中的 SO_REUSEADDR 和 SO_REUSEPORT 参数详解 - 知乎