美文网首页Python
Socket 通信之 TCP 通信

Socket 通信之 TCP 通信

作者: 柏丘君 | 来源:发表于2017-06-29 14:30 被阅读48次

上篇文章主要总结了 UDP 套接字的通信方式,这篇文章主要讲解 TCP 套接字的通信方式。

TCP 通信流程

一台主机可以即作为服务端又作为客户端,因此在 UDP 通信流程中将二者串联起来了,没有进行区分。由于 TCP 通信相对于 UDP 通信要复杂一点,这里将 TCP 通信拆分为服务端和客户端,方便理解。
首先是服务端的通信流程:

  • 创建服务端套接字
  • 绑定本机 IP 地址和端口(不必须)
  • 将主动套接字转换为被动套接字(不必须)
  • 监听客户端请求
  • 接收客户端请求
  • 发送/接受消息
  • 关闭客户端套接字
  • 关闭服务端套接字

客户端的通信流程如下:

  • 创建客户端套接字
  • 绑定本机 IP 地址和端口(不必须)
  • 连接服务端
  • 发送/接受消息
  • 关闭客户端

下面依次进行讲解。

服务端通信流程

首先看一下服务端的通信流程。

创建服务端套接字

使用 TCP 通信,需要创建 TCP 的套接字:

sSocket = socket(AF_INET, SOCK_STREAM)

绑定服务器的 IP 和端口

sSocket.bind((IP, PORT))

同使用 UDP 套接字,绑定 IP 和端口是不必须的,如果要绑定本机上所有合法的 IP,可以这样写:

sSocket.bind(("",PORT))

将主动套接字转换为被动套接字

主动套接字是创建 TCP 套接字对象后的默认行为,只能向其他的主机发送消息,如果需要接收其他主机的消息,就需要将其转换为被动套接字,转换后就可以同时进行收发了,如果我们的主机不想接收其他主机的消息,就不需要转换为被动套接字,因此这项也不是必须的。
将主动套接字转换为被动套接字很简单,只需要调用套接字对象的 listen 方法:

sScoket.listen( maxConnect )

listen 函数接受一个参数,表示最大连接数。

接收客户端请求

执行 accept 方法以接收客户端的请求,该方法是一个阻塞方法:

clientSocket, clientInfo = sSocket.accept()

accept 方法返回一个元组,元组的第一项一个新的套接字对象,专门用来处理和相应的客户端的通信,元组的第二项是客户端的 IP 和端口信息。

发送/接受消息

发送消息使用 send 方法,接受消息使用 recv 方法:

# 发送消息
clientSocket.send(byte)

# 接收消息
recvData = clientSocket.recv( maxLen )

send 方法用来发送信息,接受一个 byte 类型的消息。
recv 方法用来接收信息,接受一个最大接收长度作为参数。
注意:以上两个方法都由特定的客户端套接字对象调用。

关闭套接字

通信完成后需要关闭套接字,首先需要关闭客户端套接字,最后当所有的客户端消息处理完成后,需要关闭服务端套接字:

# 关闭客户端套接字
clientSocket.close()

# 关闭服务端套接字
sScoket.close()

客户端通信流程

下面讲解客户端的通信流程,由于前面已有介绍,这里就不再赘述绑定和关闭套接字了。

创建套接字

首先需要在客户端创建一个套接字对象:

cSocket = socket( AF_INET, SOCK_STREAM)

连接服务端

连接服务端需要使用 connect 方法,该方法接受服务端的 IP 地址和对应的端口作为参数:

cSocket.connect( IP, PORT )

该方法也是阻塞的,连接过程会耗费一定时间。连接成功后,会触发客户端 Socket 对象的 accept 方法。

发送/接收消息

使用 send 方法向服务端发送消息,使用 recv 从服务端接收消息:

# 发送消息
cSocket.connect( msg )

# 接收消息
cSocket.recv( maxLen )

由于前面已经使用 connect 将客户端和服务端进行了连接,因此在使用 send 方法的时候不必再传入服务端相关的 IP 和端口信息了。

简单实例

下面做一个客户端和服务端通信的例子。首先是服务端代码 server.py:

from socket import *

def main():
    # 创建服务端 socket 对象
    sSocket = socket(AF_INET, SOCK_STREAM)
    # 绑定本机端口
    sSocket.bind(("",3001))
    # 转换为被动套接字
    sSocket.listen(5)
    # 下面是一个轮询,在每次有客户端请求时进行处理
    while True:
        # 监听客户端请求
        clientSocket,clientAddr = sSocket.accept()
        print("%s 已连入,正在接受消息..."%clientAddr[0])
        while True:
            # 接受客户端消息
            try:
                recvMsg = clientSocket.recv(1024)
            except:
                # 如果触发异常,说明客户端断开了连接
                print("%s 已断开连接~"%clientAddr[0])
                break
            print("%s:%s"%(clientAddr[0],recvMsg.decode("utf-8")))
            # 回复消息
            clientSocket.send("ding~".encode("utf-8"))
        # 关闭客户端套接字
        clientSocket.close()

if __name__ == '__main__':
    main()

接着是客户端的代码,client.py:

from socket import *

def main():
    cSocket = socket(AF_INET, SOCK_STREAM)
    cSocket.connect(("192.168.2.142",3001))
    while True:
        msg = input("Enter Message:")
        if msg == "q!":
            break
        else:
            cSocket.send(msg.encode("utf-8"))

    cSocket.close()

if __name__ == '__main__':
    main()

我们看到 server.py 中有两个死循环,最外层的死循环用于和其他主机进行连接,连接成功后调用内存循环,用来和客户机通信。通信结束后,跳出内层循环,等待下一次连接。
如果客户端关闭了连接,服务端的 recv 方法在运行时会产生异常,我们可以通过异常的捕获来判断客户端是否断开了连接。
下面是运行效果:

tcp通信.gif

实例改进

上面的实例有个问题:服务器一次只能处理一个 Socket 通信,只有在上一个 Socket 通信处理完成后,才能进行下一次通信的处理。这是因为外层的 while 循环需要内层的 while 循环执行完成后再执行,其他时间它都是阻塞的,只有这一次的 Socket 的通信处理完了,外层的 while 循环才能进行下一次的处理。两个客户端同时请求服务端的运行效果如下:

tcp通信02.gif
要解决这个问题也很简单,既然是多连接多任务处理,我们只需将内层的 while 循环放在进程中,此后每新增一个连接,就为其开一个进程进行处理,实现并发操作。
修改 server.py:
from socket import *
from multiprocessing import Process

class Server():
    @classmethod
    def __prepareSocket(cls):
        # 将服务端的 Socket 对象作为类成员
        cls.sSocket = socket(AF_INET, SOCK_STREAM)
        cls.sSocket.bind(("",3001))
        cls.sSocket.listen(5)

    @classmethod
    def startServer(cls):
        cls.__prepareSocket()
        while True:
            # 监听客户端请求
            clientSocket,clientAddr = cls.sSocket.accept()
            print("%s 已连入,正在接受消息..."%clientAddr[1])
            cp = SocketHander(clientSocket,clientAddr)
            cp.start()


class SocketHander(Process):
    def __init__(self,clientSocket,clientAddr):
        Process.__init__(self)
        self.clientSocket = clientSocket
        self.clientAddr = clientAddr

    def run(self):
        # 将内层循环至于 run 方法中
        while True:
            try:
                recvMsg = self.clientSocket.recv(1024)
            except:
                # 如果触发异常,说明客户端断开了连接
                print("%s 已断开连接~"%self.clientAddr[0])
                break
            print("%s:%s"%(self.clientAddr[0],recvMsg.decode("utf-8")))
            # 回复消息
            self.clientSocket.send("ding~".encode("utf-8"))
        # 关闭客户端套接字
        self.clientSocket.close()

if __name__ == '__main__':
    Server.startServer()

看下效果:

tcp通信03.gif

完。

相关文章

  • Kotlin网络编程(3)TCP Socket 低层次网络编程

    TCP Socket 通信概念TCP Socket 通信过程Socket 类ServerSocket 类案例:文件...

  • Socket 通信之 TCP 通信

    上篇文章主要总结了 UDP 套接字的通信方式,这篇文章主要讲解 TCP 套接字的通信方式。 TCP 通信流程 一台...

  • 22.网络socket

    Socket / TCP / HTTP 联系与区别 Socket / TCP socket)是通信的基石,是支持T...

  • 31.Python之网络编程(socket模块)

    Python之网络编程(socket模块) 什么是socket?Socket是应用层与TCP / IP协议族通信的...

  • CocoaAsyncSocket --Socket学习

    Socket理论 套接字(Socket)概念 套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信...

  • Android Socket基于UDP协议通信

    首先我们要知道UDP通信协议是Socket通信的一种实现方式,Socket通信一般有两种通信方式:基于TCP协议、...

  • Socket通信原理

    Socket通信原理 Socket博客地址Socket 是一组调用接口、是{ 应用层与TCP/IP协议族 }通信...

  • Java socket

    Java Socket实现基于TCP和UDP多线程通信Java Socket编程

  • socket知识点补充

    socket套接字 通信基石 包含本地.远端ip地址,本地.远端通信协议,连接协议支持tcp/ip网络通信多个通信...

  • TCP socket通信

    TCP Socket 通信 在物联网开发中,通过串口服务器将串口数据转化成网络信号,服务器与终端之间的链接就相应的...

网友评论

    本文标题:Socket 通信之 TCP 通信

    本文链接:https://www.haomeiwen.com/subject/awqmcxtx.html