Socket 通信之 TCP 通信

上篇文章主要总结了 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

完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容