万物互联之~网络编程上篇

 

入门篇

官方文档: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方向

  1. 早期方向
    • Web全栈
  2. 擅长专栏
    • 爬虫系列
    • 数据分析
    • 人工智能
    • 物联网系lot万物互联)
    • 自动化运维安全测试
  3. 其他系列
    • 游戏开发(最近很火)

如果想专攻Web爬虫物联网游戏等等方向,网络这块是硬条件,So ==> 不要不急,咱们继续学习~

多句嘴,一般情况下需要什么库就去官方看下,没有再考虑第三方:https://docs.python.org/3/library

1.2.拙见一点点

技术前景:(注意加粗方向)

  1. Python
    1. 最常用:Data
    2. 最看好:LoT
    3. 最火是:AI
    4. 经典是:Web
    5. 垄断是:System
  2. Web
    1. 最看好:小程序
    2. 最常见:移动端
    3. 最火是:Web端
  3. Go高并发区块链)、C(基础
  4. NetCoreWebAPIEFCore

总的来说:Python最吃香,Go最有潜力,Web必不可少,NetCore性价比高

现在基本没有单一方向的程序员了,如果有可以默默思考几分钟,一般都是JS and Python and (Go or NetCore)【二选一】


其他行业:(仅代表逆天个人看法

  1. 设计师
    1. 影视制作(剪辑师、合成师、特效师)【目前最火,性价比很高】
    2. 修图师(商业修片、影楼后期)【大咖特别多,创业很吃香】
    3. UI|UE(最容易找工作)
    4. 平面设计(最常见)
    5. 室内设计(高手很吃香)
  2. 教育
    1. 幼儿编程中医课最火
    2. 琴棋书画武+国学需求颇高
    3. 英语一直是出国必学
  3. 营销新媒体+短视频
  4. 旅游出国游

1.2.分层模型

1.OSI 7层模型

  1. 物理层:物理设备标准,主要作用就是传输比特流(数据为bit)eg:网线接口、光纤接口、各种传输介质的传输速率
    • 双绞线,光纤(硬件)
  2. 数据链路层:对物理层的校验(是否有丢失、错误)
    • 数据的传输和数据检测(网卡层)
  3. 网络层:指定传输过程中的路径。eg:IP
    • 为数据包选择路由(保证数据传达)
  4. 传输层:定义了传输数据的协议和端口号(主要就是携带了端口号,这样可以找到对应的进程)
    • 提供端对端的接口,eg:TCP、UDP
  5. 会话层:通过传输层,在端与端之间(端口)建立数据传输通道(设备之间可以通过IP、Mac、主机名相互认识)
    • 解除或者建立和别的节点之间的联系
  6. 表示层:保证一个系统应用发的消息可以被另一个系统应用读取到。eg:两个应用发送的消息格式不同(eg:UTF和ASCII各自表示同一字符),有必要时会以一种通用格式来实现不同数据格式之间的转换
    • 数据格式化、代码转化、数据加密
  7. 应用层:为用户的应用程序提供网络服务
    • 文件传输、电子邮箱、文件服务、虚拟终端

我用PPT画了个图:( ) 1.分层模型.png

2.TCP/IP 4层模型

  1. 网络接口层:(物、数
    • eg:以太网帧协议
  2. 网络层
    • eg:IP、ARP协议
  3. 传输层
    • eg:TCP、UDP协议
  4. 应用层:(会、表、应)我们基本上都是关注这个
    • eg:FTP、SSH、HTTP协议…

1.3.协议相关

计算机和计算机网络通信前达成的一种约定,举个例子:以汉语为交流语言 1.协议定义.png

再举个发送文件的例子,PPT做个动画:(自定义协议-文件传输演示) 1.文件传输演示.gif

B/S基本上都是HTTP协议,C/S开发的时候有时会使用自己的协议,比如某大型游戏,比如很多框架都有自己的协议:

  1. Redis的redis://
  2. Dubbo的dubbo://协议

总的来说,基本上都是HTTP协议,对性能要求高的就使用TCP协议,更高性能要求就自己封装协议了,比如腾讯在UDP基础上封装了自己的协议来保证通信的可靠性

数据包的封装

先看一个老外的动画(忽略水印广告):https://v.qq.com/x/page/w01984zbrmy.html


中文版可以点击微信公众号的原文链接下载(课外拓展也有贴)

以TCP/IP四层协议为例:数据包的逐层封装解包都是操作系统来做的,我们只管应用层

发送过程:

  1. 发送消息
  2. 应用层添加了协议头
  3. 传输层添加TCP段首
  4. 网络层添加IP报头
  5. 网络接口层(链路层)添加帧头帧尾

PPT动画示意: 1.传输.gif

接收过程:

  1. 去除链路层的帧头帧尾
  2. 去除网络层IP报头
  3. 去除传输层TCP段首
  4. 去除应用层的协议头
  5. 获取到数据

PPT动画示意: 2.解包.gif

我们下面按照解包顺序简单说说各种格式

1.以太网帧格式

先看一下这个是啥?用上面动画内容表示: 1.以太网帧格式是啥.png

以太网帧协议根据MAC地址完成数据包传递

如果只知道IP,并不知道MAC地址,可以使用ARP请求来获取:

  • ARP数据报:根据IP获取MAC地址(网卡编号)
  • ARP只适合IPv4,IPv6ICMPV6来代替ARP
  • TCP/IP模型中,ARP协议属于IP层;在OSI模型中,ARP协议属于链路层

PPT画一张图:1bit = 8byte(1字节=8位) 1.以太网帧格式.png


上图数据最小46字节,而ARP就28字节,所以需要填充(PAD)18个无用字节

课后思考:根据ARP原理想想ARP欺骗到底扎回事?(IP进行ARP请求后会缓存,缓存失效前不会再去ARP请求)

扩展:

  1. RARP 是反向地址转换协议,通过 MAC 地址确定 IP 地址
  2. 真实IP在网络层的IP协议之中,以太网帧中的IP是下一跳的IP地址(路由)
  3. 每到一个路由都要解网络层的包(知道到底需要获取哪个IP)
  4. MAC地址就是硬件地址,厂商向全球组织申请唯一编号(类似于身份证)
  5. 最后附上手画的ARP数据报图示:(一般都不是一步得到MAC的,多数都是经过一个个路由节点最终获取到MAC)

1.ARP.png

2.IP段格式

先贴一IP段格式图片(网络): 1.IP报.png

我们在这不去详细讲解,扩展部分有课后拓展,我就说一个大多数人困惑的点:

查看IP信息的时候经常会看到192.168.36.235/24,这个/24一直争议很大

我们来简单解释一下:IP为192.168.36.235

  1. 192.168.36:网络标识
  2. 235:主机标识
  3. /24标识从头数到多少位为止属于网络标识(剩下的就是可分配的主机数了)
    • 二进制表示为:11111111 11111111 11111111 00000000(24个1)
    • 翻译成子网掩码就是:255.255.255.0(/多少就数多少个1,然后转化)
    • 表示可以有255个ip用来自行分配(记得去除路由之类的占用)

扩展:IP属于面向无连接行(IP协议不保证传输的可靠性,数据包在传输过程中可能丢失,可靠性可以在上层协议或应用程序中提供支持)

面向连接面向无连接区别如图:(图片来自网络1.面向有无连接.png


预告

关于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()

接收到的消息:这时候端口是随机的 2.UDP接收消息

看起来代码还挺麻烦,我稍微分析下你就知道对比其他语言真的太简单了:

标识:

  1. AF_INET ==> IPV4
  2. SOCK_DGRAM ==> 类型是UDP
  3. SOCK_STREAM ==> 类型是TCP

代码三步走

  1. 创建 udp_sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  2. 发送 udp_sock.sendto(Bytes内容,(IP,Port)) 接收:udp_sock.recvfrom(count)
  3. 关闭 udp_sock.close()

端口绑定

借助调试工具(点我下载)可以知道:上面程序每次运行,端口不固定 2.UDP随机端口.png

那怎么使用固定端口呢?==> 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 8080nc -l是监听TCP) 2.nc监听UDP.png

调试工具: 2.UDP绑定端口.png

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)) 2.udp_recv.gif


题外话(Nmap)

其实如果你使用Nmap来扫描的话并不能发现nc打开的UDP端口: 2.nmap的UDP扫描.gif

稍微解释一下:扫描其实就是发了几个空消息过去

  1. -sU代表扫描UDP,-sT代表扫描TCP
  2. -Pn 这个主要是针对有些服务器禁用ping的处理(ping不通也尝试)
  3. -p 指定端口号,如果是所有端口可以使用-p-
  4. sudo是因为在Ubuntu下没权限,kali下可以直接使用nmap

可能有人对nc输出的你可以给离线留意了有疑惑,其实就是在给5400端口发空消息的时候~True循环了两次

来张对比图: 2.nc找不到.gif

扫描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.收放自如.gif


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()

CentOSIPPort(192.168.36.123:5400) 2.UDP网络助手.png

演示:(多PC演示) 2.UDPTool.gif

简单说下本机IP的绑定:

Net里面习惯使用localhost,很多人不知道到底是啥,其实你打开host文件就可以看到 ==> 127.0.0.1被重定向为localhost,在Linux里面也是这样的,每个PC对应的都是lo回环地址: 2.lo.png

本机通信时,对方ip就可以使用127.0.0.1了,当然了绑定本机ip的时候也可以使用127.0.0.1bind(('',))中的空其实填的就是这个)(很多地方也会使用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 Socket3.tcp_client.gif

概括来说:

  1. TCP,有点像打电话,先拨号连通了(connect)才能通信(sendrecv),之后的通信不用再拨号连通了
  2. UDP,有点像寄信封,每次寄过去都不确定能不能收到,每次通信都得写地址(ip+port)

代码四步走:(TCP客户端其实创建Socket之后connect一下服务器就OK了)

  1. 创建:tcp_sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  2. 连接:tcp_sock.connect((IP, Port))
  3. 发送:tcp_sock.send(Bytes内容) 接收:tcp_sock.recv(count)
  4. 关闭: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\nConnection:closesplit("",分割次数)


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.tcp_server.gif

3.2.TCP服务端调试助手

如果像上面那般,并不能多客户端通信 3.bug.png

这时候可以稍微改造一下:

客户端:

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都不用管了) 3.正常流程.gif

服务器挂了客户端也会自动退出: 3.自动退出.gif

用TCP协议进行Socket编程在Python中十分简单:

  1. 客户端:主动连接服务器的IP和指定端口
  2. 服务器:先监听指定端口,然后对每一个新的连接创建一个线程或进程来处理

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)}");
                }
            }
        }
    }
}

图示: 3.netcore.gif

扩展

示例代码: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了吧) 3.localhost.png

手写一个端口扫描工具

端口扫描大家不陌生,自己实现一个简单的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

课后思考

可以自行研究拓展:

  1. 为啥发送(sendsendto)和接收(recvrecvfrom)都是两个方法?(提示:方法名阻塞
  2. sendsendall有啥区别?
  3. 有没有更方便的方式来实现服务端?
  4. 结合内网映射或者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