使用socket时,会使用socket.socket(socket.AF_INET,socket.SOCK.DGRAM)来创建一个socket。然后用其来绑定(bind((IP,Port)))或者连接,那么,参数AF_INET和DGRAM是什么意思呢,是否还有其它的参数呢?
这里可以看到,我们使用了4个参数:socket.AF_INET,socket.SOCK.DGRAM,IP,Port;但是在创建socket的时候有三个参数,只不过默认参数被忽略了。
首先,地址族是最重要的参数,机器可能接触不同类型的网络,对地址族的指定就决定了想要进行通信的网络类型。在学习的时候,一直使用的是socket.AF_INET即IP地址族。按照tcp/ip层分类,这个参数指的是IP层的协议,当然,不止有ip协议,还有蓝牙协议,wifi协议等等
第二个就是套接字类型,这里使用的是socket.SOCK.DGRAM,当然,在IP协议之上有tcp何udp之分,那么在其他ip层协议之上,我们该怎么办呢?这里设计者使用的是广泛可以使用的接口参数,例如:这里使用的socket.SOCK.DGRAM代表使用套接字数据报文传输,在IP协议上就是指UDP,而socket.STREAM指的是可靠的传输,在IP上指的是TCP。那么在其他协议之上的传输也可以使用。
第三个参数是协议类型,比如上面例子中socket.socket(socket.AF_INET,socket.SOCK.DGRAM)就是指在ip协议中选择基于数据报的传输模式协议,那么在ip协议中就是指的是UDP,不需要再输入协议类型了,如果真要输入协议类型,UDP是17,TCP是6,当然一般默认是0,就是让下层程序自动挑选,反正经过前面两个参数可以指定了,后面这个一般默认即可。
绑定(bind((IP,Port)))或者连接的时候呢,就是ip地址和端口,没啥多说的。
那么在实际使用中,我们使用getaddrinfo来获取相关的信息。
getaddrinfo函数的参数:socket.getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]])
首先,host是想要获取的ip地址或者可以通过hosts or DNS解析的字符串,port就是端口(int)或者可以通过/etc/services解析的字符串,第三个是网络层的协议类型,第四个就是传输层的协议类型,第五个就是通过第三个参数和第四个参数确定的协议中的哪一个,一般来说只有一个,所以一般默认为0即可,最后一个就是一些设置参数。
返回值[(family, socktype, proto, canonname, sockaddr)]
首先就是网络层协议,然后是传输层协议,然后是协议编号,canonname不清楚了,sockaddr就是一个元组例如:('220.181.38.148', 80);包含了ip和端口。
当使用getaddrinfo获取信息的时候,最后一个参数非常有用,
首先,如果指定ip地址为None,那么其意义就是获取本地信息,最后一个参数使用socket.AI_PASSIVE就可以获取本地所有可以连接的指定端口的信息,
如果指定了一个ip,那么久应该取消socket.AI_PASSIVE这个参数,设置为0或者其他参数即可。
还有一个是AI_ADDRCONFIG,这个参数可以把IP地址上无法连接的地址都过滤掉。例如,如果本机只能使用ipv4来连接,而对方可以接收ipv4和ipv6,那么这个参数会将ipv6的地址都去掉。
AI_V4MAPPED可以将ipv4的地址转换成ipv6的地址,例如,我们想连接的服务只支持ipv4,但是本机只能使用ipv6,那么使用这个参数可以将v4转化为v6。
参数使用举例:
当我想连接百度的HTTP端口:
from pprint import pprint import socket if __name__== '__main__': infolist = socket.getaddrinfo('baidu.com','www',0,socket.SOCK_STREAM,0,socket.AI_ADDRCONFIG | socket.AI_V4MAPPED) pprint((infolist))
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 0, '', ('39.156.69.79', 80)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 0, '', ('220.181.38.148', 80))]
当我们知道ip和端口后,但是我们不知道这个主机的DNS真实名称,比如,我被告知192.168.10.22时百度的地址,端口是80,然后我也这样访问了,可以连接上,但是这个ip地址是内网地址,是总所周知的,但是,服务器方可以另DNS返回任意他们想要返回的结果,这里他们就可以让DNS返回百度的真实名称给客户端,导致客户端认为这就是百度。
使用这个方式的办法是使用AI_CANONNAME参数,如下:
这种使用只有在服务器定义了反向主机名的时候才有用,但是在互联网上,很少有服务器提供了反向主机名,所以需要用其它方式来确认。
其它参数:
使用例子:
import socket,sys,argparse def connect_to(hostname_or_ip): try: infolist = socket.getaddrinfo(hostname_or_ip,'www',0,socket.SOCK_STREAM,0,socket.AI_ADDRCONFIG | socket.AI_V4MAPPED | socket.AI_CANONNAME) except socket.gaierror as why: print('Name Service failure:',why.args[1]) sys.exit(1) info = infolist[0] socket_args = info[0:3] address = info[4] s = socket.socket(*socket_args) try: s.connect(address) except socket.error as why: print('Network failure:',why.args[1]) else: print(F'Success:host {address[0]} is listening on port 80.') if __name__ == '__main__': parse = argparse.ArgumentParser(description='Try connect to port 80') parse.add_argument('hostname',help='hostname you want to connect') connect_to(parse.parse_args().hostname)
python tcp_sixteen.py baidu.com Success:host 220.181.38.148 is listening on port 80. python tcp_sixteen.py 220.181.38.148 Success:host 220.181.38.148 is listening on port 80.