Python进阶教程m9–网络通信–socket通信

原文链接:http://www.juzicode.com/archives/819

在前面的系列文章中涉及的内容都是在本地进行的,接下来介绍的内容会更有趣,会进入到一个更广阔的世界。Python中内置的socket模块可以实现多台电脑间通过网口进行通信,互相收发消息,这篇文章将从最底层的socket模块开始介绍,而更上层的ftplib,telnetlib等都是基于socket封装的更高层应用的模块。

提到socket通信,必然绕不开C/S结构,在C/S结构中,其中一台电脑作为server端,等待其他的client端连接、通信。比如一个Ftp服务器启动后,会等待其他的客户端连接、传输文件。一个Telnet服务端也是这样,开启telnet服务后,等待其他的客户端连接、登录、发送命令。

1、TCP连接

利用socket编程的模型和C/S结构的特点有关,在服务端:打开某个端口,监听来自客户端的请求连接,完成连接后就可以等待客户端发消息或者像客户端发送消息。在客户端:在某个端口去连接服务端,如果连接成功,就可以向服务端发送消息或者等待服务端发送消息。

服务端

在服务端首先创建一个ipv4的tcp连接实例,参数socket.AF_INET, socket.SOCK_STREAM分别代表IPV4和TCP连接:

    #创建socket服务端实例
    skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

接下来绑定ip和端口,这里我们使用本机地址127.0.0.1进行实验,端口可以选择非周知端口,这里选择19200,

    #绑定端口
    skt.bind(('127.0.0.1',19200))

然后在该端口上开始监听,接收来自客户端的消息,listen方法的入参100表示最多可以同时接收100个客户端进行连接:

    #监听端口
    skt.listen(100)

完成前面3个步骤,就可以用accept方法等待客户端进行连接,如果有客户端连接上,就会创建一个针对该客户端的connet实例,后面与客户端的通信就使用该 connet 实例完成,需要注意的是这里调用send,recv方法不再是前面创建服务端socket实例,而是accept方法创建的 connet 实例。在这个例子中约定了服务端等待客户端发送消息,并将客户端发来的消息发送回去,如果客户端关闭了连接,这个 connet 实例也将关闭:

    while True:
        #等待连接
        print('\n等待连接......')
        connet, address = skt.accept()
        print('客户端地址:%s,建立连接' % str(address))
        print('connet实例: ',connet)
        
        while True:
            #等待消息
            data = connet.recv(1024)
            msg = data.decode('utf8')
            print('客户端地址:%s,消息内容:%s' %(str(address),msg) )
            if not data:
                break
            #回显消息给客户端
            connet.send(data)
            time.sleep(0.5)
            
        #关闭连接
        connet.close()
        print('客户端地址:%s,连接关闭' % str(address))

客户端

在客户端也是一样首先创建一个socket实例 :

    #创建socket实例
    skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

第二步是根据ip和端口连接服务器:

    # 建立连接:
    skt.connect(('127.0.0.1', 19200))

接下来就是根据和服务端约定的通信流程,客户端每次发送一条消息,并等待服务端返回一条消息,当客户端发送结束后,关闭该socket连接:

    current_time=time.asctime()
    msg_list = [current_time,'juzicode.com', '微信公众号:桔子code', 'Socket通信']
    for msg in msg_list:
        # 发送消息:
        data_tx = bytes(msg,encoding='utf8')
        skt.send(data_tx)
        
        # 接收消息:
        data_rx = skt.recv(1024)
        print('接收到消息:%s'%( data_rx.decode('utf8')))
        
        time.sleep(0.5)
    #退出
    skt.close()

注意在客户端的send和recv方法直接使用的是创建的socket实例,而不是像服务端那样使用的是accept方法创建的连接实例。

在cmd下先运行server端程序,再运行client端程序,效果是这样的:

从上图可以看出,客户端每次通信的端口是不确定的,使用的端口号实际由系统分配空闲的端口号。

2、UDP连接

udp连接是一种面向无连接的通信方式,所以udp方式的通信相对于tcp通信,在服务端不需要使用listen方法在端口上连接,也不需要accept方法等待客户端发起连接,而是直接使用recvfrom方法接收消息或者sendto方法发送消息。

同样在客户端,也不需要使用connnet方法先与服务端建立连接,而是可以直接使用sendto或者recvfrom方法收发消息。

服务端

创建socket实例时,使用socket.SOCK_DGRAM参数创建UDP连接实例,并使用bind绑定端口:

#创建socket服务端实例
skt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#绑定端口
skt.bind(('127.0.0.1',19200))

使用recvfrom方法,可以返回获取到的消息和对端的地址信息,该地址信息就能用于sendto方法传入ip地址和端口号。因为udp不是面向连接的通信方法,所以其socket实例创建好之后并没有包含地址信息,这样不能直接使用send方法发送消息。当然仍然可以使用recv方法接收消息,这一般用于客户端,这时候客户端是已知服务端的ip地址和端口号的,并不需要像服务端那样使用recvfrom方法来获取对端通信的ip地址和端口号。

    while True:
        #等待消息
        data,address = skt.recvfrom(1024)
        msg = data.decode('utf8')
        print('客户端地址:%s,消息内容:%s' %(str(address),msg) )
        if not data:
            break
        #回显消息给客户端
        skt.sendto(data,address)
        time.sleep(0.5)

因为不需要建立连接,sever端发送和接收数据的使用的是socket实例,而不是像tcp连接那样使用的是connet连接实例。

客户端

创建socket实例的方法同服务端一样:

#创建socket实例
skt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

仍然是约定客户端发送一条消息,等待服务端返回一条消息,这里不需要connect方法和服务端“建立连接”。

current_time=time.asctime()
msg_list = [current_time,'juzicode.com', '微信公众号:桔子code', 'Socket通信']
for msg in msg_list:
    # 发送消息:
    data_tx = bytes(msg,encoding='utf8')
    skt.sendto(data_tx, ('127.0.0.1',19200))

    # 接收消息:
    data_rx = skt.recv(1024)
    print('接收到消息:%s'%( data_rx.decode('utf8')))

    time.sleep(0.5)
#退出
skt.close()

启动sever端后,运行client端:

这篇文章介绍了利用socket编写服务端和客户端完成简单通信的例子,利用socket编程,需要用户设计应用层协议,更加灵活但是难度也更大。tcp方式通信是面向连接的,所以创建连接后每次发送数据并不需要指定对方的ip地址和端口号,而udp方式通信每次发送数据必须指定对方的ip地址和端口号。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注