socket使用入门与内网穿透实现
1. Socket是什么?
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,作为BSD UNIX的进程通信机制,通常也称做“套接字” ,是一个通信链的句柄,实现不同程序之间的发出请求和应答请求。对于文件用【打开】【读写】【关闭】模式操作。- 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,作为BSD UNIX的进程通信机制,通常也称做“套接字” ,是一个通信链的句柄,实现不同程序之间的发出请求和应答请求。对于文件用【打开】【读写】【关闭】模式操作。
>>socket和file的区别:
1、file模块是针对某个指定文件进行【打开】【读写】【关闭】
2、socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
- python官方文档中文翻译版:https://keelii.com/2018/09/24/socket-programming-in-python/
2. Python-Socket常用功能
(1)创建客户端和服务器:
-
客户端
点击查看代码
#!/usr/bin/env python3
import socket
HOST = '127.0.0.1' # 标准的回环地址 (localhost),只有主机上的进程可以连接到服务器
PORT = 65432 # 监听的端口 (非系统级的端口: 大于 1023)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
#创建了一个 socket 对象,并且支持 context manager type,你可以使用with 语句,这样你就不用再手动调用 s.close() 来关闭 socket 了
#socket.AF_INET 表示因特网 IPv4 地址族,SOCK_STREAM 表示使用 TCP 的 socket 类型
s.bind((HOST, PORT))#关联 socket 到指定的网络接口(IP 地址)和端口号,入参取决于 socket 的地址族,在此为IPv4
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
-
服务器
点击查看代码
#!/usr/bin/env python3
import socket
HOST = '127.0.0.1' # 服务器的主机名或者 IP 地址
PORT = 65432 # 服务器使用的端口
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b'Hello, world')
data = s.recv(1024)
print('Received', repr(data))
(2)更多功能:
参考:
https://www.cnblogs.com/zhangkui/p/5655428.html
https://www.cnblogs.com/aylin/p/5572104.html
点击查看代码
def bind(self, address): # real signature unknown; restored from __doc__
"""
bind(address)
Bind the socket to a local address. For IP sockets, the address is a
pair (host, port); the host must refer to the local host. For raw packet
sockets the address is a tuple (ifname, proto [,pkttype [,hatype]])
"""
'''将套接字绑定到本地地址。是一个IP套接字的地址对(主机、端口),主机必须参考本地主机。'''
pass
def close(self): # real signature unknown; restored from __doc__
"""
close()
Close the socket. It cannot be used after this call.
"""
'''关闭socket'''
pass
def connect(self, address): # real signature unknown; restored from __doc__
"""
connect(address)
Connect the socket to a remote address. For IP sockets, the address
is a pair (host, port).
"""
'''将套接字连接到远程地址。IP套接字的地址'''
pass
def connect_ex(self, address): # real signature unknown; restored from __doc__
"""
connect_ex(address) -> errno
This is like connect(address), but returns an error code (the errno value)
instead of raising an exception when an error occurs.
"""
pass
def detach(self): # real signature unknown; restored from __doc__
"""
detach()
Close the socket object without closing the underlying file descriptor.
The object cannot be used after this call, but the file descriptor
can be reused for other purposes. The file descriptor is returned.
"""
'''关闭套接字对象没有关闭底层的文件描述符。'''
pass
def fileno(self): # real signature unknown; restored from __doc__
"""
fileno() -> integer
Return the integer file descriptor of the socket.
"""
'''返回整数的套接字的文件描述符。'''
return 0
def getpeername(self): # real signature unknown; restored from __doc__
"""
getpeername() -> address info
Return the address of the remote endpoint. For IP sockets, the address
info is a pair (hostaddr, port).
"""
'''返回远程端点的地址。IP套接字的地址'''
pass
def getsockname(self): # real signature unknown; restored from __doc__
"""
getsockname() -> address info
Return the address of the local endpoint. For IP sockets, the address
info is a pair (hostaddr, port).
"""
'''返回远程端点的地址。IP套接字的地址'''
pass
def getsockopt(self, level, option, buffersize=None): # real signature unknown; restored from __doc__
"""
getsockopt(level, option[, buffersize]) -> value
Get a socket option. See the Unix manual for level and option.
If a nonzero buffersize argument is given, the return value is a
string of that length; otherwise it is an integer.
"""
'''得到一个套接字选项'''
pass
def gettimeout(self): # real signature unknown; restored from __doc__
"""
gettimeout() -> timeout
Returns the timeout in seconds (float) associated with socket
operations. A timeout of None indicates that timeouts on socket
operations are disabled.
"""
'''返回的超时秒数(浮动)与套接字相关联'''
return timeout
def ioctl(self, cmd, option): # real signature unknown; restored from __doc__
"""
ioctl(cmd, option) -> long
Control the socket with WSAIoctl syscall. Currently supported 'cmd' values are
SIO_RCVALL: 'option' must be one of the socket.RCVALL_* constants.
SIO_KEEPALIVE_VALS: 'option' is a tuple of (onoff, timeout, interval).
"""
return 0
def listen(self, backlog=None): # real signature unknown; restored from __doc__
"""
listen([backlog])
Enable a server to accept connections. If backlog is specified, it must be
at least 0 (if it is lower, it is set to 0); it specifies the number of
unaccepted connections that the system will allow before refusing new
connections. If not specified, a default reasonable value is chosen.
"""
'''使服务器能够接受连接。'''
pass
def recv(self, buffersize, flags=None): # real signature unknown; restored from __doc__
"""
recv(buffersize[, flags]) -> data
Receive up to buffersize bytes from the socket. For the optional flags
argument, see the Unix manual. When no data is available, block until
at least one byte is available or until the remote end is closed. When
the remote end is closed and all data is read, return the empty string.
"""
'''当没有数据可用,阻塞,直到至少一个字节是可用的或远程结束之前关闭。'''
pass
def recvfrom(self, buffersize, flags=None): # real signature unknown; restored from __doc__
"""
recvfrom(buffersize[, flags]) -> (data, address info)
Like recv(buffersize, flags) but also return the sender's address info.
"""
pass
def recvfrom_into(self, buffer, nbytes=None, flags=None): # real signature unknown; restored from __doc__
"""
recvfrom_into(buffer[, nbytes[, flags]]) -> (nbytes, address info)
Like recv_into(buffer[, nbytes[, flags]]) but also return the sender's address info.
"""
pass
def recv_into(self, buffer, nbytes=None, flags=None): # real signature unknown; restored from __doc__
"""
recv_into(buffer, [nbytes[, flags]]) -> nbytes_read
A version of recv() that stores its data into a buffer rather than creating
a new string. Receive up to buffersize bytes from the socket. If buffersize
is not specified (or 0), receive up to the size available in the given buffer.
See recv() for documentation about the flags.
"""
pass
def send(self, data, flags=None): # real signature unknown; restored from __doc__
"""
send(data[, flags]) -> count
Send a data string to the socket. For the optional flags
argument, see the Unix manual. Return the number of bytes
sent; this may be less than len(data) if the network is busy.
"""
'''发送一个数据字符串到套接字。'''
pass
def sendall(self, data, flags=None): # real signature unknown; restored from __doc__
"""
sendall(data[, flags])
Send a data string to the socket. For the optional flags
argument, see the Unix manual. This calls send() repeatedly
until all data is sent. If an error occurs, it's impossible
to tell how much data has been sent.
"""
'''发送一个数据字符串到套接字,直到所有数据发送完成'''
pass
def sendto(self, data, flags=None, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
"""
sendto(data[, flags], address) -> count
Like send(data, flags) but allows specifying the destination address.
For IP sockets, the address is a pair (hostaddr, port).
"""
pass
def setblocking(self, flag): # real signature unknown; restored from __doc__
"""
setblocking(flag)
Set the socket to blocking (flag is true) or non-blocking (false).
setblocking(True) is equivalent to settimeout(None);
setblocking(False) is equivalent to settimeout(0.0).
"""
'''是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。'''
pass
def setsockopt(self, level, option, value): # real signature unknown; restored from __doc__
"""
setsockopt(level, option, value)
Set a socket option. See the Unix manual for level and option.
The value argument can either be an integer or a string.
"""
pass
def settimeout(self, timeout): # real signature unknown; restored from __doc__
"""
settimeout(timeout)
Set a timeout on socket operations. 'timeout' can be a float,
giving in seconds, or None. Setting a timeout of None disables
the timeout feature and is equivalent to setblocking(1).
Setting a timeout of zero is the same as setblocking(0).
"""
pass
def share(self, process_id): # real signature unknown; restored from __doc__
"""
share(process_id) -> bytes
Share the socket with another process. The target process id
must be provided and the resulting bytes object passed to the target
process. There the shared socket can be instantiated by calling
socket.fromshare().
"""
return b""
def shutdown(self, flag): # real signature unknown; restored from __doc__
"""
shutdown(flag)
Shut down the reading side of the socket (flag == SHUT_RD), the writing side
of the socket (flag == SHUT_WR), or both ends (flag == SHUT_RDWR).
"""
pass
def _accept(self): # real signature unknown; restored from __doc__
"""
_accept() -> (integer, address info)
Wait for an incoming connection. Return a new socket file descriptor
representing the connection, and the address of the client.
For IP sockets, the address info is a pair (hostaddr, port).
"""
pass
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
- 参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信 - 参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而
SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以
通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。
SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,
如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务 - 参数三:协议
0 (默认)与特定的地址家族相关的协议,如果是 0 ,
则系统就会根据地址格式和套接类别,自动选择一个合适的协议
sk.bind(address)
- s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。
- 在AF_INET下,以元组(host,port)的形式表示地址。
sk.listen(backlog)
- 开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
- backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5,这个值不能无限大,因为要在内核中维护连接队列
sk.setblocking(bool)
- 是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
sk.accept()
- 接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收
- 和发送数据。address是连接客户端的地址。接收TCP 客户的连接(阻塞式)等待连接的到来
sk.connect(address)
- 连接到address处的套接字。一般,address的格式为元组(hostname,port),
- 如果连接出错,返回socket.error错误。
sk.connect_ex(address)
- 同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
sk.close()
- 关闭套接字
sk.recv(bufsize[,flag])
- 接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。
- flag提供有关消息的其他信息,通常可以忽略。
sk.recvfrom(bufsize[.flag])
- 与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,
- address是发送数据的套接字地址。
sk.send(string[,flag])
- 将string中的数据发送到连接的套接字。返回值是要发送的字节数量,
- 该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
sk.sendall(string[,flag])
- 将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。
- 成功返回None,失败则抛出异常。
- 内部通过递归调用send,将所有内容发送出去。
sk.sendto(string[,flag],address)
- 将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。
- 返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout)
- 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。
- 一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.getpeername()
- 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname()
- 返回套接字自己的地址。通常是一个元组(ipaddr,port)
sk.fileno()
- 套接字的文件描述符
3.内网穿透:
https://zhuanlan.zhihu.com/p/303175108
(1)“内网”与“外网”的概念:
-
内网:即所说的局域网,比如学校的局域网,局域网内每台计算机的IP地址在本局域网内具有互异性,是不可重复的。但两个局域网内的内网IP可以有相同的。
-
外网:即互联网,局域网通过一台服务器或是一个路由器对外连接的网络,这个IP地址是唯一的。也就是说内网里所有的计算机都是连接到这一个外网IP上,通过这一个外网IP对外进行交换数据的。也就是说,一个局域网里所有电脑的内网IP是互不相同的,但共用一个外网IP。(用ipconfig/all查到的IP是你本机的内网IP;在http://www.ip138.com上看到的是你连接互联网所使用的IP,即外网)。
(2)公有 IP 和私有 IP 的区别:
-
公有地址(Public address):由 Inter NIC(Internet Network Information Center 因特网信息中心)负责。这些 IP 地址分配给注册并向Inter NIC提出申请的组织机构,公有 IP 全球唯一,通过它直接访问因特网(直接能上网)。
-
私有地址(Private address):属于非注册地址,专门为组织机构内部使用,说白了,私有 IP 不能直接上网。
-
而我们平时通过运营商(电信、移动、联通宽带等)上网,家里面通过路由器分出来的 IP 都是私有 IP(局域网 IP),大家可能会疑问,我们可以上网啊,怎么会是私有 IP 呢?租用(申请)公有 IP 是需要钱的。 运营商买了一些公有 IP,然后通过这些公有 IP 分出来,再分给一个一个的用户使用。这个过程有点类似于,我们去安装了宽度,通过路由器分出几个 IP,让好几个人都能上网,当然运营商通过公有 IP 分出来的过程肯定比这个复杂多了。所以,我们平时上网用的 IP 是私有 IP,真正拥有公有 IP 的是运营商(当然,我们可以租用一个公有 IP )。所以,A 家庭的局域网 IP 和 B 家庭的局域网 IP 相同很正常,但是,最终 A 和 B 能上网(数据走出去)还是通过运营商的公有 IP,毕竟,公有 IP 的资源有限,这一片区域的用户使用的很有可能(实际上就是这样的)是同一个公有 IP
(3)端口映射:
-
端口映射是 NAT 的一种,它将外网主机的 IP 地址的一个端口映射到内网中一台机器,提供相应的服务。当用户访问该 IP 的这个端口时,服务器自动将请求映射到对应局域网内部的机器上。
-
之前提到的内网,是不能被外网直接的访问的,只能通过一些中转技术,让内网“假装”成外网。
-
这就平常所说的内网穿透。
(4)内网穿透原理
- 对于内网来说,其不是不能主动访问公网端口,而是不能反过来有效的被公网访问。内网穿透的主要思路就是利用这一点,让在内网的节点主动访问一个拥有公网IP地址的服务器,并由中间服务器搭桥,打通经过该服务器从其他主机到NAT之后节点的隧道。
(5)建立隧道的工具
Ngrok\Ssh 配合autossh工具使用\Natapp\Frp\Lanproxy\Spike\花生壳
-
使用NATAPP的免费服务,先去注册
https://natapp.cn/
,在依次进入“我的渠道”>>“购买渠道”>>“免费渠道”,设置一下端口就可以购买了。 -
接下来下载客户端和配置config.ini,只需要修改authtoken,换成你自己的authtoken。
-
运行客户端
-
获得映射网址,如:http://aaa.natappfree.cc -> 127.0.0.1:8888(8888为购买时设置的端口)(注意:每次开启内网穿透服务,地址会变化!!!)
服务器代码
import socket
import sys
import threading
import time
def server():
def bind():
HOST = '192.168.199.122'
s.bind((HOST, 28080))
print("bind ok")
def listen():
s.listen(10)
print('Socket now listening')
def send_sth(conn):
while True:
try:
sth = input('say something:\n')
conn.sendall(sth.encode('utf-8'))
except ConnectionError:
print('connect error')
sys.exit(-1)
except:
print('unexpect error')
sys.exit(-1)
def recv(conn):
while True:
try:
data = conn.recv(1024)
data2 = data.decode('utf-8')
print('get message:' + data2)
except ConnectionError:
print('connect error')
sys.exit(-1)
except:
print('unexpect error')
sys.exit(-1)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
bind()
listen()
conn, addr = s.accept()
print("connect success")
print('connect time: ' + time.ctime())
threading._start_new_thread(recv, (conn,))
send_sth(conn)
if __name__ == '__main__':
server()
客户端代码
import socket
import sys
import threading
import time
class client():
def __init__(self):
self.s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.ip = "server.natappfree.cc"
def connect(self):
try:
self.s.connect((self.ip, 42911))
# self.s.connect('http://pjnudp.natappfree.cc')
print("connect success")
print('connect time: '+time.ctime())
except ConnectionError:
print('connect error')
sys.exit(-1)
except:
print('unexpect error')
sys.exit(-1)
def send_sth(self):
while True:
sth=input('say something:\n')
try:
self.s.sendall(sth.encode('utf-8'))
except ConnectionError:
print('connect error')
sys.exit(-1)
except:
print('unexpect error')
sys.exit(-1)
def receive(self):
while True:
try:
r=self.s.recv(1024)
print ('get message:'+r.decode('utf-8'))
except ConnectionError:
print('connect error')
sys.exit(-1)
except:
print('unexpect error')
sys.exit(-1)
c1 = client()
c1.connect()
threading._start_new_thread(c1.receive,())
c1.send_sth()
-
NATAPP隧道配置,可以配置自己的内网地址,也可配置为127.0.0.1
-
运行NATAPP客户端后的端口映射,在对应IP和端口填写: