0
点赞
收藏
分享

微信扫一扫

Python 构建TCP通讯/TCP转发

早安地球 2022-01-26 阅读 93

Python 构建TCP通讯

TCP/IP协议

TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议 不仅仅 指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议

关于Python 的TCP协议处理

老实说python在这种偏底层的网络协议处理上并不占太大优势,他的处理速度相对于C/C++或者别的什么语言来说相对较慢,但胜就胜在简单 wwwwww

使用 Python 实现tcp通讯,一般要通过 Python 的 scoket 模块来实现。

socket 是 Python 自带的源生库,大家并不需要通过 pip 进行下载,直接在代码里 import 就行了。

Python 提供的 socket 模块, 它提供了标准的 BSD Sockets API

socket 类型

先来看一看 socket 的构造器

socket.socket([family[, type[, proto]]])
class socket(_socket.socket):

    """A subclass of _socket.socket adding the makefile() method."""

    __slots__ = ["__weakref__", "_io_refs", "_closed"]

    def __init__(self, family=-1, type=-1, proto=-1, fileno=None):
        pass
    # 以下省略

family:

意义为地址族,决定 socket 的通讯类型,类型以及描述如下

socket 地址族类型地址族描述
socket.AF_UNIX只能够用于单一的Unix系统进程间通讯
socket.AF_INET服务器之间网络通讯
socket.AF_INET6IPv6

type:

意义为 scoket 类型,根据不同的值构建不同的连接,类型以及描述如下

socket 类型类型描述
socket.SOCK_STREAM流式的 scoket, 处理 TCP 连接选择这种
socket.SOCK_DGAM数据报式的 socket,处理 UDP 连接选择这种
socket.SOCK_RAW原始类型的 socket,普通的套接字无法处理 ICMP、IGMP等网络报文,而 SOCK_RAW 可以;同时,SCOK_RAW 也可以处理特殊的 IPv4 报文;利用原始 socket,可以通过 IP_HDRINCL socket选项由用户构造 IP 头。
socket.SOCK_SEQPACKET可靠的连续数据包服务

protocol:

一般不填默认为 0。

socket 函数

1)与 HTTP 或 UDP 协议不一样, TCP 发送数据时,已建立好 TCP 连接,所以不需要指定地址。
2)服务端与客户端不能直接发送***字符串***外的数据类型。
3)可用函数如下

服务端函数

socket 函数函数描述
bind(address)将 socket 绑定到地址,在 AF_INET 地址族方式下,以元组 (host,port) 的形式表示地址
listen(backlog)开始监听 TCP 传入连接。bcaklog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,在Win/MAC 系统下有效。Linux系统下默认最大值。
accept()接受 TCP 连接并返沪 (conn, address), conn 是新的 socket对象,可以用来接受和发送数据。 address 是连接客户端的地址。

客户端函数

socket 函数函数描述
connect(address)连接到address处的套接字。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
connect_ex(address)功能与connect(address)相同,但是成功返回0,失败返回errno的值。

通用函数

socket 函数函数描述
recv(bufsize[,flag])接受TCP套接字的数据。数据以 byte 式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
send(byte[,flag])发送TCP数据。将 byte 中的数据发送到连接的 socket。返回值是要发送的字节数量,该数量可能小于string的字节大小。
sendall(byte[,flag])完整发送TCP数据。将 byte 中的数据发送到连接的 socket,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
recvfrom(bufsize[.flag])接受UDP socket 的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的 socket 地址。
sendto(string[,flag],address)发送UDP数据。将数据发送到 socket,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
close()关闭 socket
getpeername()返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
getsockname()返回套接字自己的地址。通常是一个元组(ipaddr,port)
setsockopt(level,optname,value)设置给定套接字选项的值。
getsockopt(level,optname[.buflen])返回套接字选项的值。
settimeout(timeout)设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
gettimeout()返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
fileno()返回套接字的文件描述符。
setblocking(flag)如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
makefile()创建一个与该套接字相关连的文件

socket 编程

思路

应当存在一个服务端的 socket ,监听一个端口,接受从客户端建立的连接,以及接收从客户端发送而来的消息,向客户端发送消息。
应当存在一个服务端的 socket,使用一个端口,向服务端请求构建 TCP连接,并且发送消息,并且接收从服务端发送来的消息。

伪代码

服务端:

1.创建 socket, 绑定到本地的IP与端口

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8892))

2.开始监听端口

s.listen(5)

3.进入循环,不断接受客户端的连接请求

s.accept()

4.接收数据,并且发送返回信息给客户端

s.recv(1024)
s.send()

5.传输完毕或者需求完成后,关闭 socket

s.close()

客户端:

1.创建 socket,连接服务端

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect()

2.发送数据并且接收数据

data = 'data'
s.send(data)
s.recv()

3.传输完毕或者需求完成后,关闭 socket

s.close()

示意图

在这里插入图片描述

代码

服务端:

# server.py
import socket


class Server:
    def __init__(self, family, t, port):
        self.sock = socket.socket(family, t)
        self.sock.bind(('0.0.0.0', port))
        self.sock.listen(10)

    def accept(self):
        self.sock.accept()


if __name__ == '__main__':
    server = Server(socket.AF_INET, socket.SOCK_STREAM, 7792)
    client, client_addr = server.sock.accept()
    print('来自:' + str(client_addr) + '的连接')
    data = client.recv(1024)
    print('收到从:' + str(client_addr) + '的消息:')
    # 接收到的数据是 byte, 需要转换成 string
    print(str(data, 'utf-8'))
    # 发送数据则需要转换成 byte
    client.send(str('你好,我已收到消息!').encode())

客户端:

# client.py
import socket


class Client:
    def __init__(self, family, t, ip, port):
        self.sock = socket.socket(family, t)
        self.ip = ip
        self.port = port

    def connect(self):
        self.sock.connect((self.ip, self.port))

    def recv(self, size):
        return self.sock.recv(size)

    def send(self, byteData):
        self.sock.send(byteData)

    def close(self):
        self.sock.close()


if __name__ == '__main__':
    client = Client(socket.AF_INET, socket.SOCK_STREAM, '127.0.0.1', 7792)
    client.connect()
    client.send(str('你好!').encode())
    data = client.recv(1024)
    client.close()
    print('来自服务端的消息:\n' + str(data, 'utf-8'))

运行结果

先启动服务端,再启动客户端
服务端结果:在这里插入图片描述

客户端结果:
在这里插入图片描述

用线程来操作 socket

# 实现 tcp 请求转发
import sys, socket, time, threading, json

# 日志锁,使得日志输出线程安全
loglock = threading.Lock()

def log(msg, ip):
    loglock.acquire()
    try:
        print('[%s]: \n[IP:%s]\n%s\n' % (time.ctime(), ip, msg.strip()))
        sys.stdout.flush()
    finally:
        loglock.release()

# 传输类,source为来源,target为目标
# 转发原理,服务端 与 客户端建立连接, 服务端与转发目标端建立连接
# 服务端得到两个 sock,使用PipeThread 进行消息互转
class PipeThread(threading.Thread):

    def __init__(self, source, target, addr):
        threading.Thread.__init__(self)
        self.source = source
        self.target = target
        self.addr = addr

    def run(self):
        while True:
            try:
                data = self.source.recv(1024)
                log('datas:', self.addr)
                print(data)
                if not data:
                    break
                self.target.send(data)
            except Exception as e:
                print(e)
                break
        log('PipeThread done', self.addr)


class Forwarding(threading.Thread):
    def __init__(self, port, targethost, targetport):
        threading.Thread.__init__(self)
        self.targethost = targethost
        self.targetport = targetport
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind(('0.0.0.0', port))
        self.sock.listen(10)

    def run(self):
        while True:
            client_fd, client_addr = self.sock.accept()
            target_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            target_fd.connect((self.targethost, self.targetport))
            log('new connect', client_addr)
            PipeThread(target_fd, client_fd, '(' + self.targethost + ':' + str(self.targetport) + ")").start()
            PipeThread(client_fd, target_fd, client_addr).start()


# two direct pipe


if __name__ == '__main__':
    Forwarding(8928, '10.110.87.243', 8879).start()
    sys.exit(1)

举报

相关推荐

0 条评论