【Python】socket同时收发与多线程防止input阻塞

【Python】socket同时收发与多线程防止input阻塞

采薇采薇,薇亦作止。曰归曰归,岁亦莫止。靡室靡家,玁狁之故。不遑启居,玁狁之故。

采薇采薇,薇亦柔止。曰归曰归,心亦忧止。忧心烈烈,载饥载渴。我戍未定,靡使归聘。

采薇采薇,薇亦刚止。曰归曰归,岁亦阳止。王事靡盬,不遑启处。忧心孔疚,我行不来。

彼尔维何?维常之华。彼路斯何?君子之车。戎车既驾,四牡业业。岂敢定居?一月三捷。

驾彼四牡,四牡骙骙。君子所依,小人所腓。四牡翼翼,象弭鱼服。岂不日戒,玁狁孔棘。

昔我往矣,杨柳依依。今我来思,雨雪霏霏。行道迟迟,载渴载饥。我心伤悲,莫知我哀!
——《诗经 · 小雅 · 采薇》

一、What

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
——来自百度百科

二、Why

学Python也有一段时间了,socket也经常用,不过都是单纯发送数据或者接收数据而已,因为业务上没有要求收发同时存在(我接触的设备都是只接收指令,不返回数据),最近正好有这方面的想法,觉得也不难实现,就把接收的逻辑以及同时接收发送的逻辑一起完善一下。
要实现收发,首要的问题是解决发送和接收一起堵塞的问题,我们可以先写一个简单的发送程序和接收程序,然后再想办法合并起来。

三、How

3.1、Client

3.1.1、单纯的发送一段数据

1、代码

    # -*- coding: cp936 -*-
    import socket
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(('10.3.1.67',8001))
    s.send("单纯的发送一段数据".encode('gb18030'))
    s.close()

2、说明
【# -- coding: cp936 --】代表使用中文编码的gb2312,这在发送和接收数据需要指定编码格式时会用到,还有另外一种写法:#coding=cp936,效果是一样的,但是这种的区别在于,前者支持更多的编辑器,比如Emacs等编辑器使用# -- coding: cp936 --这种方式进行编码声明。
import socket表示引入socket这个库。

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

初始化socket,构造函数中包含了两个参数,分别是,AF_INET是设置地址格式为IPV4,SOCK_STREAM参数表示使用TCP的传输方式

    s.connect(('10.3.1.67',8001))

连接到socket的服务端,此时我们的程序作为客户端,通过IP和端口号去连接服务端

    s.send("单纯的发送一段数据".encode('gb18030'))

"单纯的发送一段数据”作为字符串,要经过编码处理,转换为bytes格式,才可以发送

s.close()

关闭TCP 链接,结束通讯

详细内容可参考
https://blog.csdn.net/zzyandzzzy/article/details/72236388
的介绍。
3、实现效果

在这里插入图片描述

3.1.2、自己输入要发送的数据

如果我想要自己输入数据发送呢?使用input方法即可,它会从控制台接收数据并传入程序。

1、代码

    # -*- coding: cp936 -*-
    import socket
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(('10.3.1.67',8001))
    a=input("请输入一段数据并按回车:")
    s.send(a.encode('gb18030'))
    s.close()

2、实现效果

在这里插入图片描述

3、然后我们加一个循环,可以不断的发送数据

 # -*- coding: cp936 -*-
    import socket
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(('192.168.0.106',8001))
    while 1:

        s.send(input('请输入数据并回车:').encode('gb18030'))
    
    #   s.close()

在这里插入图片描述

3.1.3、实现只接收数据的逻辑

1、代码:

    # -*- coding: cp936 -*-
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('10.3.1.67', 8001))
    
    while 1:
        a = s.recv(100)
        print(a.decode('gb18030'))
    #   s.close()

2、说明
使用while 1使程序进去死循环,处于不断的接收数据的状态,然后a = s.recv(100)指定单次接收的最大数据量为100个字节,并将接收到的数据存入a中,最后通过decode函数将bytes格式转化为字符串并打印,最后因为死循环,所以关闭函数不起作用,使用#注释掉

在这里插入图片描述

3.1.4、把两段代码合在一起

最简单的形式就是直接把发送数据的代码复制过来
1、代码如下:

    # -*- coding: cp936 -*-
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('10.3.1.67', 8001))
    
    while 1:
        a = input("请输入一段数据并按回车:")
        s.send(a.encode('gb18030'))
        a = s.recv(100)
        print(a.decode('gb18030'))
    
    #   s.close()

2、实现效果

在这里插入图片描述

这可以算是初步实现效果,我查询了一些网上的socket收发也是这么写的,不过多试几次你会发现,当我们使用工具发送数据的时候,Python程序的控制台是不显示的,此时程序停留在
a = input(“请输入一段数据并按回车:”)
这一行,等待输入,只有你在控制台这里输入内容并回车后才可以显示TCP工具发送的数据,因为input这个方法是线程阻塞的,如果想实现实时收发,需要使用多线程,一个线程接收,一个线程发送。

3.1.5、同时收和发送

1、代码

    # -*- coding:cp936  -*-
    import socket, threading
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    s.connect(('172.19.6.236', 8080))
    
    
    class A(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            while True:
                c = input('小明:')
                s.send(bytes(c, encoding='gb18030'))
    
    
    a = A()
    a.start()
    
    while True:
        b = s.recv(1024)
        print('\n小红:', str(b, encoding='gb18030'))
        

2、实现效果

在这里插入图片描述

3、说明
我们定义了一个类A,并且继承了多线程的threading.Thread类,然后在构造函数中初始化这个类的实例self,之后在run方法(实际上是Thread类的方法,在这里被重写)中实现发送数据的逻辑。接着我们新建了对象a,并且启动该线程。然后再写一个循环,用来接收数据。

3.2 Server

3.2.1 简单的发送一段数据

考虑一下思路,如果我们要使客户端可以连接我们,那么需要先绑定一个端口并且监听,我们可以用最简单的127.0.0.1,就是自己的端口,然后绑定上去,等客户端来连接。
1、代码

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
#   设置最大连接数,超过后排队
s.listen(2)
#   进入循环,不断的接受客户端的请求

#   accept返回两个值,
# 第一个是新的套接字c,用于通讯,s这个套接字专门用来连接
# 第二个是字符串a,对方的IP地址
c, a = s.accept()
c.send('简单的发送一段数据'.encode('gb2312'))

c.close()
s.close()

2、实现效果

在这里插入图片描述

3、说明
注意这里的执行顺序,我首先运行我的程序,此时程序会阻塞在accept这里,然后我在TCP工具那里选择TCP客户端,输入127的地址和端口号,点击连接,接收区立刻收到一串数据,然后我程序也结束了。
注意accept这个方法返回两个值,第一个是专门用来通讯的套接字,之前那个绑定了IP的套接字s是专门用来监听和连接的,这样的好处是多个连接不会导致数据混乱,保证点对点数据传输。

3.2.2 自定义发送的数据

1、代码

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
#   设置最大连接数,超过后排队
s.listen(2)
#   进入循环,不断的接受客户端的请求

#   accept返回两个值,
# 第一个是新的套接字c,用于通讯,s这个套接字专门用来连接
# 第二个是字符串a,对方的IP地址
c, a = s.accept()
c.send(input('请输入:').encode('gb2312'))

c.close()
s.close()

2、效果

在这里插入图片描述

3.2.3 循环输入

1、代码

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
#   设置最大连接数,超过后排队
s.listen(2)
#   进入循环,不断的接受客户端的请求

#   accept返回两个值,
# 第一个是新的套接字c,用于通讯,s这个套接字专门用来连接
# 第二个是字符串a,对方的IP地址
c, a = s.accept()

while True:
    c.send(input('请输入:').encode('gb2312'))

#   c.close()
#   s.close()

2、效果

在这里插入图片描述

3、说明
这种方式很容易阻塞,一旦停止掉服务端的程序,那么再启用就会提示

在这里插入图片描述

可以通过在终端kill掉这个线程来实现重新连接。

在这里插入图片描述

注意第一句前面可能要加sudo,否则无法查询。

3.2.4 接收

1、代码

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
#   设置最大连接数,超过后排队
s.listen(2)
#   进入循环,不断的接受客户端的请求

#   accept返回两个值,
# 第一个是新的套接字c,用于通讯,s这个套接字专门用来连接
# 第二个是字符串a,对方的IP地址
c, a = s.accept()

while True:
    print(c.recv(1024).decode('gb2312'))

#   c.close()
#   s.close()

2、效果

在这里插入图片描述

3.2.5 结合

1、效果

在这里插入图片描述

2、代码

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
#   设置最大连接数,超过后排队
s.listen(5)
#   进入循环,不断的接受客户端的请求

#   accept返回两个值,
# 第一个是新的套接字c,用于通讯,s这个套接字专门用来连接
# 第二个是字符串a,对方的IP地址
c, a = s.accept()

while True:
    c.send(input('请输入:').encode('gb2312'))
    print(c.recv(1024).decode('gb2312'))

#   c.close()
#   s.close()

3.2.6 多线程

1、效果

在这里插入图片描述

2、代码

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
#   设置最大连接数,超过后排队
s.listen(5)
#   进入循环,不断的接受客户端的请求

#   accept返回两个值,
# 第一个是新的套接字c,用于通讯,s这个套接字专门用来连接
# 第二个是字符串a,对方的IP地址
c, a = s.accept()


class A(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            b = c.recv(1024)
            if len(b) > 0:
                print('\n小红:', str(b, encoding='gb18030'))


e = A()
e.start()

while True:
    d = input('小明:')
    c.send(bytes(d, encoding='gb18030'))


#   c.close()
#   s.close()

3.2.7 用自己的Client程序

1、效果

在这里插入图片描述

在这里插入图片描述

2、客户端代码:

# -*- coding:cp936  -*-
import socket, threading

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
s.connect(('127.0.0.1', 8001))


class A(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            b = s.recv(1024)
            if len(b) > 0:
                print('\n小明:', str(b, encoding='gb18030'))


a = A()
a.start()

while True:
    c = input('小红:')
    s.send(bytes(c, encoding='gb18030'))

完。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值