python的socket、urlib、request指定出口网卡

2023-05-16

需求: 一台机器上有多个网卡, 如何访问指定的 URL 时使用指定的网卡发送数据呢?

1

$ curl --interface eth0 www.baidu.com # curl interface 可以指定网卡

阅读 urllib.py 的源码, 追述到 open_http –> httplib.HTTP –> httplib.HTTP._connection_class = HTTPConnection

HTTPConnection 在创建的时候会指定一个 source_address.

HTTPConnection.connect 时调用 HTTPConnection._create_connection = socket.create_connection

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

# 先看一下本地网卡信息

$ ifconfig

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384

  options=3<RXCSUM,TXCSUM>

  inet6 ::1 prefixlen 128

  inet 127.0.0.1 netmask 0xff000000

  inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1

  nd6 options=1<PERFORMNUD>

en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500

  ether c8:e0:eb:17:3a:73

  inet6 fe80::cae0:ebff:fe17:3a73%en0 prefixlen 64 scopeid 0x4

  inet 192.168.20.2 netmask 0xffffff00 broadcast 192.168.20.255

  nd6 options=1<PERFORMNUD>

  media: autoselect

  status: active

en1: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500

  options=4<VLAN_MTU>

  ether 0c:5b:8f:27:9a:64

  inet6 fe80::e5b:8fff:fe27:9a64%en8 prefixlen 64 scopeid 0xa

  inet 192.168.8.100 netmask 0xffffff00 broadcast 192.168.8.255

  nd6 options=1<PERFORMNUD>

  media: autoselect (100baseTX <full-duplex>)

  status: active

可以看到en0和en1, 这两块网卡都可以访问公网. lo0是本地回环.

直接修改 socket.py 做测试.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,

           source_address=None):

  """If *source_address* is set it must be a tuple of (host, port)

  for the socket to bind as a source address before making the connection.

  An host of '' or port 0 tells the OS to use the default.

  source_address 如果设置, 必须是传递元组 (host, port), 默认是 ("", 0)

  """

 

  host, port = address

  err = None

  for res in getaddrinfo(host, port, 0, SOCK_STREAM):

    af, socktype, proto, canonname, sa = res

    sock = None

    try:

      sock = socket(af, socktype, proto)

      # sock.bind(("192.168.20.2", 0)) # en0

      # sock.bind(("192.168.8.100", 0)) # en1

      # sock.bind(("127.0.0.1", 0)) # lo0

      if timeout is not _GLOBAL_DEFAULT_TIMEOUT:

        sock.settimeout(timeout)

      if source_address:

        print "socket bind source_address: %s" % source_address

        sock.bind(source_address)

      sock.connect(sa)

      return sock

 

    except error as _:

      err = _

      if sock is not None:

        sock.close()

  if err is not None:

    raise err

  else:

    raise error("getaddrinfo returns an empty list")

参考说明文档, 直接分三次绑定不通网卡的 IP 地址, 端口设置为0.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

# 测试 en0

$ python -c 'import urllib as u;print u.urlopen("http://ip.haschek.at").read()'

.148.245.16

 

# 测试 en1

$ python -c 'import urllib as u;print u.urlopen("http://ip.haschek.at").read()'

.94.115.227

 

# 测试 lo0

$ python -c 'import urllib as u;print u.urlopen("http://ip.haschek.at").read()'

Traceback (most recent call last):

 File "<stdin>", line 1, in <module>

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 87, in urlopen

  return opener.open(url)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 213, in open

  return getattr(self, name)(url)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 350, in open_http

  h.endheaders(data)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 1049, in endheaders

  self._send_output(message_body)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 893, in _send_output

  self.send(msg)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 855, in send

  self.connect()

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 832, in connect

  self.timeout, self.source_address)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 578, in create_connection

  raise err

IOError: [Errno socket error] [Errno 49] Can't assign requested address

测试通过, 说明在多网卡情况下, 创建 socket 时绑定某块网卡的 IP 就可以, 端口需要设置为0. 如果端口不设置为0, 第二次请求时, 可以看到抛异常, 端口被占用.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Traceback (most recent call last):

 File "<stdin>", line 1, in <module>

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 87, in urlopen

  return opener.open(url)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 213, in open

  return getattr(self, name)(url)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 350, in open_http

  h.endheaders(data)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 1049, in endheaders

  self._send_output(message_body)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 893, in _send_output

  self.send(msg)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 855, in send

  self.connect()

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 832, in connect

  self.timeout, self.source_address)

 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 577, in create_connection

  raise err

IOError: [Errno socket error] [Errno 48] Address already in use

如果是在项目中, 只需要把 socket.create_connection 这个函数的形参 source_address 设置为对应网卡的 (IP, 0) 就可以.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

# test-interface_urllib.py

import socket

import urllib, urllib2

 

_create_socket = socket.create_connection

 

SOURCE_ADDRESS = ("127.0.0.1", 0)

#SOURCE_ADDRESS = ("172.28.153.121", 0)

#SOURCE_ADDRESS = ("172.16.30.41", 0)

 

def create_connection(*args, **kwargs):

  in_args = False

  if len(args) >=3:

    args = list(args)

    args[2] = SOURCE_ADDRESS

    args = tuple(args)

    in_args = True

  if not in_args:

    kwargs["source_address"] = SOURCE_ADDRESS

  print "args", args

  print "kwargs", str(kwargs)

  return _create_socket(*args, **kwargs)

 

socket.create_connection = create_connection

 

print urllib.urlopen("http://ip.haschek.at").read()

通过测试, 可以发现已经可以通过制定的网卡发送数据, 并且 IP 地址对应网卡分配的 IP.

问题, 爬虫经常使用 requests, requests 是否支持呢. 通过测试, 可以发现, requests 并没有使用 python 内置的 socket 模块.

看源码, requests 是如果创建的 socket 连接呢. 方法和查看 urllib 创建socket 的方式一样. 具体就不写了.

因为我用的是 python 2.7, 所以可以定位到 requests 使用的 socket 模块是 urllib3.utils.connection 的.

修改方法和 urllib 相差不大.

1

2

3

4

5

6

import urllib3.connection

_create_socket = urllib3.connection.connection.create_connection

# pass

 

urllib3.connection.connection.create_connection = create_connection

# pass

运行后, 可能会抛出异常. requests.exceptions.ConnectionError: Max retries exceeded with .. Invalid argument

这个异常不是每次出现, 跟 IP 段有关系, 跳转递归层数太多导致, 只需要将 kwargs 中的 socket_options去掉即可. 127.0.0.1肯定会出异常.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

import socket

import urllib

import urllib2

import urllib3.connection

 

import requests as req

 

_default_create_socket = socket.create_connection

_urllib3_create_socket = urllib3.connection.connection.create_connection

 

 

SOURCE_ADDRESS = ("127.0.0.1", 0)

#SOURCE_ADDRESS = ("172.28.153.121", 0)

#SOURCE_ADDRESS = ("172.16.30.41", 0)

 

def default_create_connection(*args, **kwargs):

  try:

    del kwargs["socket_options"]

  except:

    pass

  in_args = False

  if len(args) >=3:

    args = list(args)

    args[2] = SOURCE_ADDRESS

    args = tuple(args)

    in_args = True

  if not in_args:

    kwargs["source_address"] = SOURCE_ADDRESS

  print "args", args

  print "kwargs", str(kwargs)

  return _default_create_socket(*args, **kwargs)

 

def urllib3_create_connection(*args, **kwargs):

  in_args = False

  if len(args) >=3:

    args = list(args)

    args[2] = SOURCE_ADDRESS

    in_args = True

    args = tuple(args)

  if not in_args:

    kwargs["source_address"] = SOURCE_ADDRESS

  print "args", args

  print "kwargs", str(kwargs)

  return _urllib3_create_socket(*args, **kwargs)

 

socket.create_connection = default_create_connection

# 因为偶尔会出问题, 所以使用默认的 socket.create_connection

# urllib3.connection.connection.create_connection = urllib3_create_connection

urllib3.connection.connection.create_connection = default_create_connection

 

print " *** test requests: " + req.get("http://ip.haschek.at").content

print " *** test urllib: " + urllib.urlopen("http://ip.haschek.at").read()

print " *** test urllib2: " + urllib2.urlopen("http://ip.haschek.at").read()

注意: 使用 urllib3.utils.connection 好像不起作用

稍微再完善一下, 就是把根据网卡名自动获取 IP.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import subprocess

 

def get_all_net_devices():

  sub = subprocess.Popen("ls /sys/class/net", shell=True, stdout=subprocess.PIPE)

  sub.wait()

  net_devices = sub.stdout.read().strip().splitlines()

  # ['eth0', 'eth1', 'lo']

  # 这里简单过滤一下网卡名字, 根据需求改动

  net_devices = [i for i in net_devices if "ppp" in i]

  return net_devices

ALL_DEVICES = get_all_net_devices()

 

def get_local_ip(device_name):

  sub = subprocess.Popen("/sbin/ifconfig en0 | grep '%s ' | awk '{print $2}'" % device_name, shell=True, stdout=subprocess.PIPE)

  sub.wait()

  ip = sub.stdout.read().strip()

  return ip

 

def random_local_ip():

  return get_local_ip(random.choice(ALL_DEVICES))

 

# code ...

只需要把 args[2] = SOURCE_ADDRESS 和 kwargs["source_address"] = SOURCE_ADDRESS改成 random_local_ip() 或者 get_local_ip("eth0")

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python的socket、urlib、request指定出口网卡 的相关文章

  • 导入错误 - Tornado 需要在 ubuntu 14.04 上更新 SSL 模块

    在我的 ubuntu 14 04 上安装 jupyter 笔记本时 我得到以下信息 ImportError Tornado requires an up to date SSL module This means Python 2 7 9
  • 如何从 gridsearchcv 绘制决策树?

    我试图绘制由 GridSearchCV 形成的决策树 但它给了我一个属性错误 AttributeError GridSearchCV object has no attribute n features 但是 如果我尝试在没有 GridSe
  • Python Pandas:使用 groupby() 和 agg() 时顺序是否保留?

    我经常使用熊猫 agg 函数对 data frame 的每一列运行摘要统计 例如 以下是生成平均值和标准差的方法 df pd DataFrame A group1 group1 group2 group2 group3 group3 B 1
  • 为什么 Sequence 是 mypy 中 + 不支持的操作数类型?

    mypy给出一个错误Sequence str 不是受支持的操作数类型 操作员 test py from typing import Sequence def test x Sequence str y Sequence str gt Seq
  • 如何使用 scipy.spatial.Delaunay 查找 delaunay 三角剖分中给定点的所有邻居?

    我一直在寻找这个问题的答案 但找不到任何有用的东西 我正在使用 python 科学计算堆栈 scipy numpy matplotlib 并且我有一组二维点 我为其计算 Delaunay 训练 wiki https en wikipedia
  • 在 scipy 中按稀疏矩阵分组并返回一个矩阵

    关于使用 SO 处理有几个问题groupby与稀疏矩阵 然而输出似乎是列表 字典 https stackoverflow com questions 35410839 group by on scipy sparse matrix 数据框
  • 有没有办法可以保留子线程的上下文局部变量?

    目前 我创建了一个库来记录后端调用 例如对boto3 and requests库 然后根据一些数据 例如响应的状态代码等 填充全局 数据 对象 我原来有data对象作为全局的 但后来我意识到这是一个坏主意 因为当应用程序并行运行时 data
  • 使用 Matplotlib 和 TeX 实现均匀间距

    我正在为数学课绘制一些图表 但我无法在绘图图例中正确地获得和平定义的间距 我目前正在使用 对于 TeX 中的单个空间 但会遇到一种情况 其中一个空间比另一个空间稍远 这可能是由于左边的方程占用了多少空间 这是我的代码 import matp
  • 使用 Python 映射字母数字字符串

    我有一个姓名数据集 根据名称的字母数字字符串 我需要将它们映射到子名称 如下所示 Name Subname 9 AIF 09 9A09 980 PD Z09A 980P09 15 KIC 12 15K12 PIA 110H P 110 IC
  • 为什么线性读-混洗写并不比混洗读-线性写快?

    我目前正在尝试更好地了解内存 缓存相关的性能问题 我在某处读到 内存局部性对于读取比对于写入更重要 因为在前一种情况下 CPU 必须实际等待数据 而在后一种情况下 它可以将它们发送出去并忘记它们 考虑到这一点 我做了以下快速而肮脏的测试 我
  • Python字典键(类对象)与多个比较器的比较

    我使用自定义对象作为 python 字典中的键 这些对象有一些默认值hash and eq定义的方法用于默认比较 但在某些功能中我需要使用不同的方式来比较这些对象 那么有什么方法可以覆盖或传递一个新的比较器来仅针对该特定函数进行这些关键比较
  • swaplevel() 和 reorder_levels() 有什么区别?

    在使用 pandas 的分层索引级别时 有什么区别swaplevel https pandas pydata org pandas docs stable generated pandas DataFrame swaplevel html
  • 激活虚拟环境不起作用

    我创建了两个 virtualenv 并安装了两个不同版本的 django 现在我在激活两个环境时遇到问题 我喜欢这样 source Django1 6 bin activate 然后我看到环境被激活了 然后我这样做 pip install
  • 使用 scipy 在 python 中读取 MatLab 文件

    我正在使用 python 和 scipy 包来读取 MatLab 文件 然而 它需要太长时间并且崩溃 The Dataset http realitycommons media mit edu RealityMining zip大小约为50
  • 启动robotframework-RIDE(机器人框架IDE)时出错

    我已经安装了Robot Framework并安装了wxPython 然后安装了Ride 当我通过执行启动它时python ride py 它会遇到如下错误 我相信这与wxPython版本有关 不确定 有一系列UnreprError像这样
  • 从 C# 运行多个 python 脚本

    我希望有人能够在这里帮助我 我对 C 比较陌生 正在尝试执行我在 C winform 应用程序中编写的一些 Python 代码 我想做的是从 winform 中的文本框中输入名称 并让它通过 python 脚本进行处理 并在 winform
  • 有没有办法向后遍历 dask 数据帧?

    我想要read parquet但从开始的地方向后阅读 假设索引已排序 我不想将整个镶木地板读入内存 因为这违背了使用它的全部意义 有什么好的方法可以做到这一点吗 假设数据帧已建立索引 索引的反转可以通过两步过程完成 反转分区的顺序并反转每个
  • 查找框和裁剪图像的角点

    Hey Guys I am working with numpy and opencv and want to get a image cropped by the contours of it Here is one example wh
  • 使用脚本取消设置 PDF 字体

    我正在使用 xhtml2pdf 库自动创建 PDF 几个月前我有过这个问题 https stackoverflow com questions 25203219 xhtml2pdf doesnt embed helvetica 库嵌入了我没
  • 使用多处理或线程加速单个任务

    是否可以使用多处理 线程来加速单个任务 我的直觉是答案是否定的 以下是我所说的 单一任务 的示例 for i in range max pick random choice on off both 当参数为 10000000 时 在我的系统

随机推荐