万物互联之~网络编程基础篇
入门篇¶
官方文档:https://docs.python.org/3/library/ipc.html(进程间通信和网络)
实例代码:https://github.com/lotapp/BaseCode/tree/master/python/6.net
1.概念¶
1.1.Python方向¶
已经讲了很多Python的知识了,那Python能干啥呢?这个是我眼中的Python:
Python方向:
- 早期方向
- Web全栈
- 擅长专栏
- 爬虫系列
- 数据分析
- 人工智能
物联网系
(lot
万物互联)- 自动化运维(
安全
与测试
)
- 其他系列
- 游戏开发(最近很火)
如果想专攻Web
、爬虫
、物联网
、游戏
等等方向,网络
这块是硬条件,So ==> 不要不急,咱们继续学习~
多句嘴,一般情况下需要什么库就去官方看下,没有再考虑第三方:https://docs.python.org/3/library
1.2.拙见一点点¶
技术前景:(注意加粗方向)
- Python:
- 最常用:
Data
- 最看好:
LoT
- 最火是:
AI
- 经典是:
Web
- 垄断是:
System
- 最常用:
- Web:
- 最看好:
小程序
- 最常见:
移动端
- 最火是:
Web端
- 最看好:
- Go(
高并发
、区块链
)、C(基础
) NetCore
(WebAPI
、EFCore
)
总的来说:Python最吃香,Go最有潜力,Web必不可少,NetCore性价比高
现在基本没有单一方向的程序员了,如果有可以默默思考几分钟,一般都是JS
and Python
and (Go
or NetCore
)【二选一】
其他行业:(仅代表逆天个人看法)
- 设计师:
影视制作
(剪辑师、合成师、特效师)【目前最火,性价比很高】修图师
(商业修片、影楼后期)【大咖特别多,创业很吃香】UI|UE
(最容易找工作)平面设计
(最常见)室内设计
(高手很吃香)
- 教育:
幼儿编程
和中医课
最火琴棋书画武
+国学
需求颇高英语
一直是出国必学
- 营销:
新媒体+短视频
- 旅游:
出国游
1.2.分层模型¶
1.OSI
7层模型¶
- 物理层:物理设备标准,主要作用就是传输比特流(数据为bit)eg:网线接口、光纤接口、各种传输介质的传输速率
- 双绞线,光纤(硬件)
- 数据链路层:对物理层的校验(是否有丢失、错误)
- 数据的传输和数据检测(网卡层)
- 网络层:指定传输过程中的路径。eg:IP
- 为数据包选择路由(保证数据传达)
- 传输层:定义了传输数据的协议和端口号(主要就是携带了端口号,这样可以找到对应的进程)
- 提供端对端的接口,eg:TCP、UDP
- 会话层:通过传输层,在端与端之间(端口)建立数据传输通道(设备之间可以通过IP、Mac、主机名相互认识)
- 解除或者建立和别的节点之间的联系
- 表示层:保证一个系统应用发的消息可以被另一个系统应用读取到。eg:两个应用发送的消息格式不同(eg:UTF和ASCII各自表示同一字符),有必要时会以一种通用格式来实现不同数据格式之间的转换
- 数据格式化、代码转化、数据加密
- 应用层:为用户的应用程序提供网络服务
- 文件传输、电子邮箱、文件服务、虚拟终端
我用PPT画了个图:(物
数
网
传
会
表
应
)
2.TCP/IP
4层模型¶
- 网络接口层:(
物、数
)- eg:以太网帧协议
- 网络层:
- eg:IP、ARP协议
- 传输层:
- eg:TCP、UDP协议
- 应用层:(
会、表、应
)我们基本上都是关注这个- eg:FTP、SSH、HTTP协议...
1.3.协议相关¶
计算机和计算机网络通信前达成的一种约定,举个例子:以汉语为交流语言
再举个发送文件的例子,PPT做个动画:(自定义协议-文件传输演示)
B/S
基本上都是HTTP
协议,C/S
开发的时候有时会使用自己的协议,比如某大型游戏,比如很多框架都有自己的协议:
- Redis的
redis://
- Dubbo的
dubbo://
协议
总的来说,基本上都是HTTP
协议,对性能要求高的就使用TCP
协议,更高性能要求就自己封装协议了,比如腾讯在UDP
基础上封装了自己的协议来保证通信的可靠性
数据包的封装¶
先看一个老外
的动画(忽略水印广告):https://v.qq.com/x/page/w01984zbrmy.html
以TCP/IP四层协议为例:数据包的逐层封装
和解包
都是操作系统
来做的,我们只管应用层
发送过程:
- 发送消息
- 应用层添加了协议头
- 传输层添加
TCP
段首 - 网络层添加
IP
报头 - 网络接口层(链路层)添加帧头和帧尾
PPT动画示意:
接收过程:
- 去除链路层的帧头和帧尾
- 去除网络层
IP
的报头 - 去除传输层
TCP
的段首 - 去除应用层的协议头
- 获取到数据
PPT动画示意:
我们下面按照解包顺序简单说说各种格式:
1.以太网帧格式¶
先看一下这个是啥?用上面动画内容表示:
以太网帧协议:根据MAC
地址完成数据包传递
如果只知道IP,并不知道MAC
地址,可以使用ARP
请求来获取:
ARP
数据报:根据IP
获取MAC
地址(网卡编号)ARP
只适合IPv4
,IPv6
用ICMPV6
来代替ARP
- 在
TCP/IP
模型中,ARP
协议属于IP
层;在OSI
模型中,ARP
协议属于链路层
PPT画一张图:1bit = 8byte
(1字节=8位)
课后思考:根据ARP原理想想ARP欺骗
到底扎回事?(IP进行ARP请求后会缓存,缓存失效前不会再去ARP请求)
扩展:
RARP 是反向地址转换协议,通过 MAC 地址确定 IP 地址
- 真实IP在网络层的IP协议之中,以太网帧中的IP是下一跳的IP地址(路由)
- 每到一个路由都要解网络层的包(知道到底需要获取哪个IP)
MAC
地址就是硬件地址,厂商向全球组织申请唯一编号(类似于身份证)- 最后附上手画的ARP数据报图示:(一般都不是一步得到MAC的,多数都是经过一个个路由节点最终获取到MAC)
2.IP段格式¶
先贴一IP段格式图片(网络):
我们在这不去详细讲解,扩展部分有课后拓展,我就说一个大多数人困惑的点:
查看IP
信息的时候经常会看到192.168.36.235/24
,这个/24
一直争议很大
我们来简单解释一下:IP为192.168.36.235
192.168.36
:网络标识235
:主机标识/24
:标识从头数到多少位为止属于网络标识(剩下的就是可分配的主机数了)- 二进制表示为:
11111111 11111111 11111111 00000000
(24个1) - 翻译成子网掩码就是:
255.255.255.0
(/多少
就数多少个1,然后转化) - 表示可以有255个ip用来自行分配(记得去除路由之类的占用)
- 二进制表示为:
扩展:IP属于面向无连接行(IP
协议不保证传输的可靠性,数据包在传输过程中可能丢失,可靠性可以在上层协议或应用程序中提供支持)
面向连接
和面向无连接
区别如图:(图片来自网络)
预告¶
关于TCP和UDP的内容下次继续~
课外拓展:
图解TCP/IP第五版
链接: https://pan.baidu.com/s/1C4kpNd2MvljxfwTKO082lw 提取码: 7qce
Python网络编程第三版
Code:https://github.com/brandon-rhodes/fopnp
PDF:链接: https://pan.baidu.com/s/1jhW-Te-GCEFKrZVf46S_Tw 提取码: d7fw
网络基础-含书签(网络文档)
链接: https://pan.baidu.com/s/1WZ1D4BthA4qBk2QXBAjm4w 提取码: jmdg
老外讲解网络数据包解析:
下载:https://pan.baidu.com/s/1uUjahs_b05y9Re9ROtzzIw
中文:http://video.tudou.com/v/XMjE3MTg0NzkzNg==.html
英文:http://video.tudou.com/v/XMTkyNjU5NDYwOA==.html
2.UDP¶
实例代码:https://github.com/lotapp/BaseCode/tree/master/python/6.net/1.UDP
UDP
是无连接的传输协议,不保证可靠性。使用UDP
协议的应用程序需要自己完成丢包重发、消息排序等工作(有点像寄信)
2.1.UDP发送消息¶
引入案例¶
看个UDP的简单案例:
import socket
def main():
# AF_INET ==> IPV4;SOCK_STREAM ==> 类型是TCP,stream 流
# SOCK_DGRAM ==> 类型是UDP,dgram 数据报、数据报套接字
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_sock:
udp_sock.sendto("大兄弟,你好啊".encode("utf-8"), ("192.168.36.235", 8080))
print("over")
if __name__ == '__main__':
main()
接收到的消息:这时候端口是随机的
看起来代码还挺麻烦,我稍微分析下你就知道对比其他语言真的太简单了:
标识:
AF_INET
==>IPV4
SOCK_DGRAM
==> 类型是UDP
SOCK_STREAM
==> 类型是TCP
代码三步走:
- 创建
udp_sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- 发送
udp_sock.sendto(Bytes内容,(IP,Port))
接收:udp_sock.recvfrom(count)
- 关闭
udp_sock.close()
端口绑定¶
借助调试工具
(点我下载)可以知道:上面程序每次运行,端口都不固定
那怎么使用固定端口呢?==> udp_socket.bind(('', 5400))
import socket
def main():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket:
# 绑定固定端口
udp_socket.bind(('', 5400))
# 发送消息
udp_socket.sendto("小明,你知道小张的生日吗?\n".encode("utf-8"),
("192.168.36.235", 8080))
print("over")
if __name__ == '__main__':
main()
消息图示:nc -ul 8080
(nc -l
是监听TCP)
调试工具:
2.2.UDP接收消息¶
先看一个简单版本的:udp_socket.recvfrom(1024)
from socket import socket, AF_INET, SOCK_DGRAM
def main():
with socket(AF_INET, SOCK_DGRAM) as udp_socket:
# 绑定端口
udp_socket.bind(('', 5400))
while True:
# 发送消息
udp_socket.sendto("你可以给我离线留言了\n".encode("utf-8"),
("192.168.36.235", 8080))
# 接收消息(data,(ip,port))
data, info = udp_socket.recvfrom(1024)
print(f"[来自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}")
if __name__ == '__main__':
main()
图示:接收消息(data,(ip,port))
题外话(Nmap)¶
其实如果你使用Nmap
来扫描的话并不能发现nc
打开的UDP
端口:
稍微解释一下:扫描其实就是发了几个空消息过去
-sU
代表扫描UDP,-sT
代表扫描TCP-Pn
这个主要是针对有些服务器禁用ping的处理(ping不通也尝试)-p
指定端口号,如果是所有端口可以使用-p-
sudo
是因为在Ubuntu
下没权限,kali
下可以直接使用nmap
可能有人对nc
输出的你可以给离线留意了
有疑惑,其实就是在给5400端口发空消息的时候~True循环了两次
来张对比图:
扫描TCP和UDP端口:sudo nmap -sTU 192.168.36.235 -Pn
课后扩展:
NC命令扩展:https://www.cnblogs.com/nmap/p/6148306.html
Nmap基础:https://www.cnblogs.com/dunitian/p/5074784.html
收放自如¶
如果还是用True循环来实现:
from socket import socket, AF_INET, SOCK_DGRAM
def main():
with socket(AF_INET, SOCK_DGRAM) as udp_socket:
# 绑定端口
udp_socket.bind(('', 5400))
while True:
msg = input("请输入发送的内容:")
if msg == "dotnetcrazy":
break
else:
udp_socket.sendto(
msg.encode("utf-8"), ("192.168.36.235", 8080))
data, info = udp_socket.recvfrom(1024)
print(f"[来自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}")
if __name__ == '__main__':
main()
你会发现,消息不能轮流发送,只能等对方方式后再发,虽然有处理方式,但太麻烦,这时候就可以使用我们之前说的多线程来改写一下了:
from socket import socket, AF_INET, SOCK_DGRAM
from multiprocessing.dummy import Pool as ThreadPool
def send_msg(udp_socket):
while True:
msg = input("输入需要发送的消息:\n")
udp_socket.sendto(msg.encode("utf-8"), ("192.168.36.235", 8080))
def recv_msg(udp_socket):
while True:
data, info = udp_socket.recvfrom(1024)
print(f"[来自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}")
def main():
# 创建一个Socket
with socket(AF_INET, SOCK_DGRAM) as udp_socket:
# 绑定端口
udp_socket.bind(('', 5400))
# 创建一个线程池
pool = ThreadPool()
# 接收消息
pool.apply_async(recv_msg, args=(udp_socket, ))
# 发送消息
pool.apply_async(send_msg, args=(udp_socket, ))
pool.close() # 不再添加任务
pool.join() # 等待线程池执行完毕
print("over")
if __name__ == '__main__':
main()
输出:(就一个注意点~socket在pool之后关闭
)
2.3.手写UDP网络调试工具¶
调试工具功能比较简单,我们手写一个UDP
版的:
from socket import socket, AF_INET, SOCK_DGRAM
from multiprocessing.dummy import Pool as ThreadPool
def get_port(msg):
"""获取用户输入的端口号"""
while True:
port = input(msg)
try:
port = int(port)
except Exception as ex:
print(ex)
else:
return port # 没有错误就退出死循环
def recv_msg(udp_socket):
"""接收消息"""
while True:
data, info = udp_socket.recvfrom(1024)
print(f"[来自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}")
def send_msg(udp_socket):
"""发送消息"""
ip = input("请输入对方IP:")
port = get_port("请输入对方端口号:")
while True:
msg = input("请输入发送的消息:\n")
udp_socket.sendto(msg.encode("utf-8"), (ip, port))
def main():
with socket(AF_INET, SOCK_DGRAM) as udp_socket:
# 绑定端口
udp_socket.bind(('', get_port("请输网络助手的端口号:")))
# 创建一个线程池
pool = ThreadPool()
# 接收消息
pool.apply_async(recv_msg, args=(udp_socket, ))
# 发送消息
pool.apply_async(send_msg, args=(udp_socket, ))
pool.close()
pool.join()
if __name__ == '__main__':
main()
CentOSIP
和Port
(192.168.36.123:5400
)
演示:(多PC演示)
简单说下本机IP的绑定:
Net里面习惯使用localhost
,很多人不知道到底是啥,其实你打开host
文件就可以看到 ==> 127.0.0.1
被重定向为localhost
,在Linux里面也是这样的,每个PC对应的都是lo
回环地址:
本机通信时,对方ip就可以使用127.0.0.1
了,当然了绑定本机ip的时候也可以使用127.0.0.1
(bind(('',))
中的空其实填的就是这个)(很多地方也会使用0.0.0.0
)
_LOCALHOST = '127.0.0.1' # 看这
_LOCALHOST_V6 = '::1'
def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
if family == AF_INET:
host = _LOCALHOST # 看这
elif family == AF_INET6:
host = _LOCALHOST_V6
....
lsock = socket(family, type, proto)
try:
lsock.bind((host, 0)) # 看这
lsock.listen()
...
2.4.NetCore版¶
快速实现一下:
using System.Net;
using System.Text;
using System.Net.Sockets;
namespace netcore
{
class Program
{
static void Main(string[] args)
{
// UDP通信
using (var udp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
var ip_addr = IPAddress.Parse("192.168.36.235");
// 绑定本地端口
udp_socket.Bind(new IPEndPoint(ip_addr, 5400));
// UDP发送消息
int i = udp_socket.SendTo(Encoding.UTF8.GetBytes("小明你好啊~"), new IPEndPoint(ip_addr, 8080));
Console.WriteLine($"发送计数:{i}");
}
Console.WriteLine("over");
}
}
}
3.TCP¶
示例代码:https://github.com/lotapp/BaseCode/tree/master/python/6.net/2.TCP
TCP
是一种面向连接的、可靠的协议,TCP
传输的双方需要首先建立连接,之后由TCP
协议保证数据收发的可靠性,丢失的数据包自动重发,上层应用程序收到的总是可靠的数据流,通讯之后关闭连接(有点像打电话)
用过下载软件的可能遇到过一种‘Bug’
==> 很多人为了防止自己本地文件纳入共享大军,一般都是直接把网络上传给禁了,然后发现文件经常出问题?
其实这个就是TCP
的一个应用,文件一般都很大,所以进行分割后批量下载,那少量的网络上传其实是为了校验一下文件 ==> 正确做法是限制上传速度而不是禁止(学生时代那会还经常蛋疼这个问题,现在想想还挺好玩的O(∩_∩)O
)
大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器
上面那个例子里,我们的下载工具就是客户端,每一小段文件接收完毕后都会向服务器发送一个完成的指令来保证文件的完整性
3.1.TCP客户端¶
来看一个简单的入门案例:
from socket import socket
def main():
# 默认就是创建TCP Socket
with socket() as tcp_socket:
# 连接服务器(没有返回值)
tcp_socket.connect(("192.168.36.235", 8080))
# 发送消息(返回发送的字节数)
tcp_socket.send("小张生日快乐~".encode("utf-8"))
# 接收消息
msg = tcp_socket.recv(1024)
print(f"服务器:{msg.decode('utf-8')}")
if __name__ == '__main__':
main()
输出:(socket()
默认就是创建TCP Socket
)
概括来说:
- TCP,有点像打电话,先拨号连通了(
connect
)才能通信(send
,recv
),之后的通信不用再拨号连通了 - UDP,有点像寄信封,每次寄过去都不确定能不能收到,每次通信都得写地址(
ip
+port
)
代码四步走:(TCP客户端其实创建Socket
之后connect
一下服务器就OK了)
- 创建:
tcp_sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- 连接:
tcp_sock.connect((IP, Port))
- 发送:
tcp_sock.send(Bytes内容)
接收:tcp_sock.recv(count)
- 关闭:
tcp_sock.close()
模拟HTTP¶
from socket import socket
def get_buffer(tcp_socket):
buffers = b''
while True:
b = tcp_socket.recv(1024)
if b:
buffers += b
else:
break
# 返回bytes
return buffers
def main():
with socket() as tcp_socket:
# 连接服务器
tcp_socket.connect(("dotnetcrazy.cnblogs.com", 80))
# 发送消息(模拟HTTP)
tcp_socket.send(
b'GET / HTTP/1.1\r\nHost: dotnetcrazy.cnblogs.com\r\nConnection: close\r\n\r\n'
)
# 以"\r\n\r\n"分割一次
header, data = get_buffer(tcp_socket).split(b"\r\n\r\n", 1)
print(header.decode("utf-8"))
with open("test.html", "wb") as f:
f.write(data)
print("over")
if __name__ == '__main__':
main()
输出:(test.html
就是页面源码)
HTTP/1.1 200 OK
Date: Thu, 01 Nov 2018 03:10:48 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 20059
Connection: close
Vary: Accept-Encoding
Cache-Control: private, max-age=10
Expires: Thu, 01 Nov 2018 03:10:58 GMT
Last-Modified: Thu, 01 Nov 2018 03:10:48 GMT
X-UA-Compatible: IE=10
X-Frame-Options: SAMEORIGIN
over
注意\r\n
和Connection:close
;split("",分割次数)
3.2.TCP服务端¶
服务端代码相比于UDP,多了一个监听和等待客户端,其他基本上一样:
客户端Code:(如果你想固定端口也可以绑定一下Port
)
from socket import socket
def main():
# 默认就是创建TCP Socket
with socket() as tcp_socket:
# 连接服务器(没有返回值)
tcp_socket.connect(("192.168.36.235", 8080))
print("Connected TCP Server...") # 连接提示
# 发送消息(返回发送的字节数)
tcp_socket.send("小张生日快乐~\n".encode("utf-8"))
# 接收消息
msg = tcp_socket.recv(1024)
print(f"服务器:{msg.decode('utf-8')}")
if __name__ == '__main__':
main()
服务端Code:
from socket import socket
def main():
with socket() as tcp_socket:
# 绑定端口(便于客户端找到)
tcp_socket.bind(('', 8080))
# 变成被动接收消息(监听)
tcp_socket.listen() # 不指定连接最大数则会设置默认值
print("TCP Server is Running...") # 运行后提示
# 等待客户端发信息
client_socket, client_addr = tcp_socket.accept()
with client_socket:
# 客户端连接提示
print(f"[来自{client_addr[0]}:{client_addr[1]}的消息]\n")
# 接收客户端消息
data = client_socket.recv(1024)
print(data.decode("utf-8"))
# 回复客户端
client_socket.send("知道了".encode("utf-8"))
if __name__ == '__main__':
main()
输出:(先运行服务端,再运行客户端。客户端发了一个生日快乐的祝福,服务端回复了一句)
3.2.TCP服务端调试助手¶
如果像上面那般,并不能多客户端通信
这时候可以稍微改造一下:
客户端:¶
from time import sleep
from socket import socket
from multiprocessing.dummy import Pool
def send_msg(tcp_socket):
with tcp_socket:
while True:
try:
tcp_socket.send("小明同志\n".encode("utf-8"))
sleep(2) # send是非阻塞的
print("向服务器问候了一下")
except Exception as ex:
print("服务端连接已断开:", ex)
break
def recv_msg(tcp_socket):
with tcp_socket:
while True:
# 这边可以不捕获异常:
# 服务端关闭时,send_msg会关闭,然后这边也就关闭了
try:
data = tcp_socket.recv(1024)
if data:
print("服务端回复:", data.decode("utf-8"))
except Exception as ex:
print("tcp_socket已断开:", ex)
break
def main():
with socket() as tcp_socket:
# 连接TCP Server
tcp_socket.connect(("192.168.36.235", 8080))
print("Connected TCP Server...") # 连接提示
pool = Pool()
pool.apply_async(send_msg, args=(tcp_socket,))
pool.apply_async(recv_msg, args=(tcp_socket,))
pool.close()
pool.join()
if __name__ == '__main__':
main()
服务端¶
服务器需要同时响应多个客户端的请求,那么每个连接都需要一个新的进程或者线程来处理
from socket import socket
from multiprocessing.dummy import Pool
def wait_client(client_socket, ip_port):
with client_socket:
while True:
data = client_socket.recv(1024)
print(f"[来自{ip_port}的消息]:\n{data.decode('utf-8')}")
client_socket.send(b"I Know") # bytes类型
def main():
with socket() as tcp_socket:
# 绑定端口
tcp_socket.bind(('', 8080))
# 服务器监听
tcp_socket.listen()
print("TCP Server is Running...") # 运行后提示
p = Pool()
while True:
# 等待客户端连接
client_socket, client_addr = tcp_socket.accept()
ip_port = f"{client_addr[0]}:{client_addr[1]}"
print(f"客户端{ip_port}已连接")
# 响应多个客户端则需要多个线程来处理
p.apply_async(wait_client, args=(client_socket, ip_port))
if __name__ == '__main__':
main()
演示:(死循环,Pool
都不用管了)
服务器挂了客户端也会自动退出:
用TCP协议进行Socket
编程在Python中十分简单:
- 客户端:主动连接服务器的IP和指定端口
- 服务器:先监听指定端口,然后对每一个新的连接创建一个线程或进程来处理
3.3.NetCore版¶
Server版¶
大体流程和Python一样:
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace _2_TCP
{
class Program
{
static void Main(string[] args)
{
using (var tcp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
var ip_addr = IPAddress.Parse("192.168.36.235");
// 服务器端绑定Port
tcp_socket.Bind(new IPEndPoint(ip_addr, 8080));
// 服务器监听
tcp_socket.Listen(5);
while (true)
{
// 等待客户端连接
var client_socket = tcp_socket.Accept();
// 远程端口
var client_point = client_socket.RemoteEndPoint;
Task.Run(() =>
{
while (true)
{
byte[] buffer = new byte[1024];
int count = client_socket.Receive(buffer);
Console.WriteLine($"来自{client_socket.RemoteEndPoint.ToString()}的消息:\n{Encoding.UTF8.GetString(buffer, 0, count)}");
client_socket.Send(Encoding.UTF8.GetBytes("知道了~"));
}
});
}
}
}
}
}
Client版¶
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace client
{
class Program
{
static void Main(string[] args)
{
using (var tcp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
// 连接服务器
tcp_socket.Connect(new IPEndPoint(IPAddress.Parse("192.168.36.235"), 8080));
while (true)
{
// 发送消息
tcp_socket.Send(Encoding.UTF8.GetBytes("服务器你好"));
// 接收服务器消息
byte[] buffer = new byte[1024];
int count = tcp_socket.Receive(buffer);
Console.WriteLine($"来自服务器的消息:{Encoding.UTF8.GetString(buffer, 0, count)}");
}
}
}
}
}
图示:
扩展¶
示例代码:https://github.com/lotapp/BaseCode/tree/master/python/6.net/3.Ext
上面忘记说了,Socket
是可以设置超时时间的,eg:tcp_socket.settimeout(3)
探一探localhost
¶
代码不变,如果把TCP客户端的连接服务器IP
空着或者改成127.0.0.1
,咱们再看看效果:tcp_socket.connect(('', 8080))
图示:(怎么样,这回知道本机问啥可以不写IP了吧)
手写一个端口扫描工具¶
端口扫描大家不陌生,自己实现一个简单的TCP端口扫描工具:
from socket import socket
from multiprocessing.dummy import Pool
ip = "127.0.0.1"
def tcp_port(port):
"""IP:服务端IP,Port:服务端Port"""
with socket() as tcp_socket:
try:
tcp_socket.connect((ip, port))
print(f"[TCP Port:{port} is open]")
except Exception:
pass
def main():
# 查看系统本地可用端口极限值 cat /proc/sys/net/ipv4/ip_local_port_range
max_port = 60999
global ip
ip = input("请输入要扫描的IP地址:")
print(f"正在对IP:{ip}进行端口扫描...")
pool = Pool()
pool.map_async(tcp_port, range(max_port))
pool.close()
pool.join()
if __name__ == '__main__':
main()
输出:(你把端口换成常用端口列表就知道服务器开了哪些服务了)
dnt@MZY-PC:~/桌面/work/BaseCode/python/6.net/3.Ext python3 1.port_scan.py
请输入要扫描的IP地址:192.168.36.235
正在对IP:192.168.36.235进行端口扫描...
[TCP Port:22 is open]
[TCP Port:41004 is open]
dnt@MZY-PC:~/桌面/work/BaseCode/python/6.net/3.Ext sudo nmap -sT 192.168.36.235 -Pn -p-
Starting Nmap 7.60 ( https://nmap.org ) at 2018-11-02 18:15 CST
Nmap scan report for MZY-PC (192.168.36.235)
Host is up (0.000086s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 2.07 seconds
课后思考¶
可以自行研究拓展:
- 为啥发送(
send
、sendto
)和接收(recv
、recvfrom
)都是两个方法?(提示:方法名
、阻塞
) send
和sendall
有啥区别?- 有没有更方便的方式来实现服务端?
- 结合
内网映射
或者ShellCode
实现一个远控
课外拓展:
官方Socket编程文档【推荐】
https://docs.python.org/3/library/socket.html
Python核心编程之~网络编程【推荐】
https://wizardforcel.gitbooks.io/core-python-2e/content/19.html
TCP编程知识
https://dwz.cn/dDkXzqcV
网络编程-基础
https://www.jianshu.com/p/55c171ebe5f1
网络编程-UDP
https://www.jianshu.com/p/594870b1634b
网络编程-TCP
https://www.jianshu.com/p/be36d4db5618
Python总结之 recv与recv_from
https://www.jianshu.com/p/5643e810123f
https://blog.csdn.net/xvd217/article/details/38902081
https://blog.csdn.net/pengluer/article/details/8812333
端口扫描扩展:(Python2)
https://thief.one/2018/05/17/1
Python socket借助ngrok建立外网TCP连接
https://www.jianshu.com/p/913b2013a38f
TCP协议知识:
https://www.cnblogs.com/wcd144140/category/1313090.html