封面图片来源:沙沙野
内容概览
- 三次握手与四次挥手
- TCP 协议与 UDP 协议的流程图
- socket 层
- socket 基本运用
- 传输中文时的方法
- 带退出的聊天程序
- 小练习
- 服务端同时接收多个客户端消息
三次握手与四次挥手
- TCP 协议之所以传输稳定、可靠,就是因为它有三次握手和四次挥手的过程
- 先建立连接,进行三次握手:
- 就好比 QQ 客户端 给服务器发送一个值 1200,问它,"能收到我的消息吗"
- 这时服务器回答,"可以",并且返回给 QQ 客户端一个值 1201 来表示接收到了。然后服务器要问 QQ 客户端,"能收到我的消息吗"
- QQ 客户端回复,"可以"
- 这就是建立连接的三次握手。注意,服务器发送 1201 和问 QQ 客户端归于一次握手
3. 数据传输的过程
- QQ 客户端给服务器发送一条消息
- 服务器收到后给 QQ 客户端返回一条消息
- 这两个过程合在一起才算一个完整的数据传输
4. 断线连接四次挥手
- QQ 客户端给服务器发送一条消息,"我要结束通话了"
- 服务器接收到后,回复一条消息,"收到了"
- 服务器又回复,"我也结束通话了"
- QQ 客户端回复,"好的"
TCP 协议与 UDP 协议的流程图
socket 层
- 所处位置:在应用层和下面所有层之间的位置
- 有了 socket 可以以相对简单的方式进行网络通信
- 本质上是帮助我们解决两个程序之间通信
- socket 是应用层与 TCP/IP 协议通信的中间软件抽象层,是一组接口
socket 基本运用
- 下面开始举例加深对 socket 的理解。首先,新建一个文件,命名为 "server.py",在同一目录下再建一个文件,命名为 "client.py"
- TCP 是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
- server.py 中的内容
# 导入套接字的包
import socket
sk = socket.socket()
# 把地址绑定到套接字
sk.bind(("127.0.0.1", 8080))
# 注意:元组的第一个元素是字符串形式的 IP 地址,必须是本电脑的
# IP 地址有两种
# a. 192.168.13.4, 在cmd里面输入 ipconfig 即可查询
# b. 127.0.0.1, 本地回环地址
# 这两个ip地址的作用是不一样的
# a. 192.168.... 表示所有的和我在同一个局域网的小伙伴都能访问
# b. 127.0.0.1 则只有在自己电脑的 client 才能用
# 元组内的第二个元素是端口号,一般在8000-10000之间
# 三次握手
# 监听链接
sk.listen()
# 阻塞,直到有一个客户端来连接
conn, addr = sk.accept()
print(addr)
# 注意发送的内容必须是 bytes 类型
conn.send(b"Hi, can you receive my message?")
# 最多接收1024个字节,没1024时有多少接收多少
msg = conn.recv(1024)
print(msg)
# 四次挥手
# 关闭客户端套接字
conn.close()
# 关闭服务器套接字
sk.close()
4. client.py 中的内容
import socket
# 创建客户端套接字
sk = socket.socket()
# 把三次握手建立起来了,尝试连接服务器
# 这里 connect 一次,上面(server.py)就 accept 一次
sk.connect(("127.0.0.1", 8080))
# 对话(发送/接收)
msg = sk.recv(1024)
print(msg)
sk.send(b"Yep, How are you?")
# 关闭客户端套接字
sk.close()
# 然后先运行 server.py, 再运行 client.py,两个都会有结果
# 注意收发次数要相等,还要一一对应
5. 先运行 server.py,再运行 client.py,两个都会有结果
# server.py 运行后,会先把消息发给客户端,并处于等待状态
# 待客户端接收到并反馈后,才有以下结果:
('127.0.0.1', 42060)
b'Yep, How are you?'
# client.py 运行后,马上接收到了服务端发来的消息,然后反馈回去
# 运行结果:
b'Hi, can you receive my message?'
传输中文时的方法
- 无论是服务端还是客户端,发送中文信息前先对消息进行编码;接收中文信息后,对消息进行解码
- server.py 中的内容示例
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))
sk.listen()
conn, addr = sk.accept()
# 发送消息前,对其编码
conn.send("你好,能接收到我的消息吗?".encode())
recv_msg = conn.recv(1024)
# 接收消息后,对其解码
decode_recv_msg = recv_msg.decode("utf-8")
print(decode_recv_msg)
conn.close()
sk.close()
3. client.py 中的内容示例
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 8080))
msg = sk.recv(1024)
# 接收消息后,对其解码
decode_msg = msg.decode()
print(decode_msg)
# 发送消息前,对其编码
sk.send("你好,可以接收到你发的消息".encode())
sk.close()
4. 先运行 server.py,再运行 client.py,运行结果如下
# server.py
你好,可以接收到你发的消息
# client.py
你好,能接收到我的消息吗?
带退出的聊天程序
- 上面的示例中服务端与客户端只是建立一次链接,实际运用中肯定不是连一次对话一次,因此,可以写一个程序,能够让双方都保持通信状态,并且可以随时结束聊天
- server.py 中的内容
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8800))
sk.listen()
conn, addr = sk.accept()
# 注意在 accept 之后加上循环
# 是为了能够让我们和一个客户端多说几句话
while 1:
send_msg = input("msg: ")
conn.send(send_msg.encode())
# 设置结束聊天的条件
if send_msg == "q": break
msg = conn.recv(1024).decode()
if msg == "q": break
print(msg)
conn.close()
sk.close()
3. client.py 中的内容
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 8800))
while 1:
msg = sk.recv(1024).decode()
if msg == "q": break
print(msg)
send_msg = input("msg: ")
sk.send(send_msg.encode())
if msg == "q": break
sk.close()
4. 先运行 server.py,再运行 client.py,双方可以一直保持通信状态,当有一方输入 q 之后,相互之间的通信结束
# 注意:如果运行 server.py 时报错:
Traceback (most recent call last):
File "server.py", line 4, in <module>
sk.bind(("127.0.0.1", 8800))
OSError: [Errno 98] Address already in use
# 解决办法:把两个文件中的端口号改一下即可,比如把 8800 改为 8000 再依次重新运行
# 带有 msg: 的表示发送的消息,不带的就是接收到的消息
# server.py
msg: 你好
下午好
msg: 很高兴和你聊天
我也是
msg: 再见
再见
msg: q
# client.py
你好
msg: 下午好
很高兴和你聊天
msg: 我也是
再见
msg: 再见
小练习
- 问题描述:所有的 client 端都要以 server 端的时间为基准,client 端发送一个时间格式——"%Y-%m-%d %H:%M:%S",server 端根据接收到的时间格式向客户端返回时间
- 大概思路解析:按照上面的题意,应该是先客户端向服务端发送消息,服务端接收到后再进行处理,而不是像上面的示例一样,先服务端发送消息
- server.py 中的内容
import socket
import time
sk = socket.socket()
sk.bind(("127.0.0.1", 9000))
sk.listen()
conn, addr = sk.accept()
msg = conn.recv(1024)
print("接收到客户端传来的消息:%s" % msg)
format_msg = time.strftime(msg.decode())
conn.send(format_msg.encode())
conn.close()
sk.close()
4. client.py 中的内容
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 9000))
sk.send(b"%Y-%m-%d %H:%M:%S")
msg = sk.recv(1024).decode()
print("接收到服务端传来的消息:%s" % msg)
sk.close()
5. 同样先运行 server.py,再运行 client.py
# server.py
接收到客户端传来的消息:b'%Y-%m-%d %H:%M:%S'
# client.py
接收到服务端传来的消息:2019-11-25 17:37:52
服务端同时接收多个客户端消息
- server.py 中的内容
import socket
import time
sk = socket.socket()
sk.bind(("127.0.0.1", 8000))
sk.listen()
# 在 accept 之前加上循环,能够让我们和多个客户端进行沟通
# 注意与上面『带退出的聊天程序』中的循环的区别
while 1:
# 接收一个客户端的请求
conn, addr = sk.accept()
print(addr)
msg = conn.recv(1024)
print("接收到客户端传来的消息:%s" % msg)
format_msg = time.strftime(msg.decode())
conn.send(format_msg.encode())
# 断开一个客户端的请求
conn.close()
sk.close()
2. client.py 中的内容
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 8000))
sk.send(b"%Y-%m-%d %H:%M:%S")
msg = sk.recv(1024).decode()
print("接收到服务端传来的消息:%s" % msg)
sk.close()
3. 先运行 server.py,再重复运行 client.py,发现 server.py 的运行结果会不断刷新,不会报错
# server.py:
(base) yanfa@yanfa-H110SD3-C:~/Desktop$ python server.py
('127.0.0.1', 46500)
接收到客户端传来的消息:b'%Y-%m-%d %H:%M:%S'
('127.0.0.1', 46502)
接收到客户端传来的消息:b'%Y-%m-%d %H:%M:%S'
('127.0.0.1', 46504)
接收到客户端传来的消息:b'%Y-%m-%d %H:%M:%S'
('127.0.0.1', 46506)
接收到客户端传来的消息:b'%Y-%m-%d %H:%M:%S'
('127.0.0.1', 46508)
接收到客户端传来的消息:b'%Y-%m-%d %H:%M:%S'
# client.py:
(base) yanfa@yanfa-H110SD3-C:~/Desktop$ python client.py
接收到服务端传来的消息:2019-11-25 18:05:58
(base) yanfa@yanfa-H110SD3-C:~/Desktop$ python client.py
接收到服务端传来的消息:2019-11-25 18:06:00
(base) yanfa@yanfa-H110SD3-C:~/Desktop$ python client.py
接收到服务端传来的消息:2019-11-25 18:06:01
(base) yanfa@yanfa-H110SD3-C:~/Desktop$ python client.py
接收到服务端传来的消息:2019-11-25 18:06:02
(base) yanfa@yanfa-H110SD3-C:~/Desktop$ python client.py
接收到服务端传来的消息:2019-11-25 18:06:03