简单 C/S
一、概述
- socket:(软件抽象层)
- 对 TCP/IP 的一种封装,介于用户和底层协议之间
- 用户不用了解各个协议的使用,只需了解socket
- 用户将数据发送给socket,socket负责传递给相应的协议通道(IP,TCP,UDP,ICMP),由底层协议去实现具体收发
- socket 执行流程
- 注意问题:
- 此处的建立连接和关闭连接是针对socket的;没有TCP的“三次握手/四次挥手”
- accept() 、recv() 都是阻塞的方法,在没有收到请求或消息前,会一直处于该状态,而不执行后面的代码
- 收发消息过程中,需要两端不断进行确认和接收
二、重点
- 注意:
- python3.5 以后,只能发字节byte,
- listen(N) N代表能挂起的连接数,即表示,除了当前处理的会话外,可挂起的连接数。
- 通讯方式:
- server:
- 通过socket 创建sk 对象,使用sk对象 accept() 用户请求;
- 每个client进来,accept 会返回一个客户端 conn 和addr,此后,使用conn 去和该客户端通讯
- client:
- 通过socket创建 sk 对象,使用该sk对象去和服务器端的conn通讯
- send 空消息:
- recv() 状态,只有接收到非“空”数据才会认为自己受到数据
- 若client send() 空消息(即命令行下直接回车),recv不会认为自己受到,即还会一直阻塞
- 而client send() 完成后,会进入recv() ,等待 server的回应,server却还处于阻塞状态,所以此时会发生“卡住”现象
- 链路断开方式:
- 1、异常断开:
- 两端使用 conn <----> sk 进行通讯;任意一段 stop(ide中 红色按钮),相当于去掉了sk 或者 conn,另一端都会发生异常 (此时,链路已然不存在)
- 2、正常断开:
- 捕获指定格式数据:
- 两端分别捕获 "接受到的/发送出的" 的数据,若数据匹配指定格式数据(exit,logout 等),则主动跳到 close()
- 监控对端状态改变:
- 正常情况下,conn <----> sk 两端,一端处于recv 一端处于send;若处于send()状态“由send() 转变为close()” (IDE 中 ctrl +d) ,则recv() 端会收到“空数据”(此时,链路存在,只不过状态发生了改变)
- 可在server端raise 异常进行捕获处理
二、简单的 C/S:
- 异常断开(链路破裂)+ 正常断开(捕获指定格式数据)+ 防止send 空消息(导致两端同时处于阻塞状态,client端进行处理)
- server.py
import socket
# 创建socket实例,并进入accept()server_ip = ('127.0.0.1', 9999)sk = socket.socket()sk.bind(server_ip)sk.listen(2)conn, addr = sk.accept()
# 给client的提示消息,只打印一遍,所以放在循环外welcome_data = bytes('welcome to my server', encoding='utf-8')conn.send(welcome_data)
while True:# 链路断开异常try:recv_data = conn.recv(1024).decode()print('from client:{}'.format(recv_data))# 捕获指定格式数据(exit)if recv_data == 'exit':print('client required exit')breakexcept Exception as ex:print(ex, 'sb client go away.....')breakelse:send_data = bytes(recv_data.upper(), encoding='utf-8')conn.send(send_data)conn.close()
- client.py
import socket
# 创建socketserver_ip = ('127.0.0.1', 9999)sk = socket.socket()sk.connect(server_ip)# 接受提示消息welcome_data = sk.recv(1024).decode()print(welcome_data)
while True:user_inp = input('>>').strip()send_data = bytes(user_inp, encoding='utf-8')
# 防止send 空消息if len(user_inp) == 0:continuesk.send(send_data)# 捕获 exitif user_inp == 'exit':breakrecv_data = sk.recv(1024).decode()print(recv_data)sk.close()
- 注意问题:
- send 在 空消息判断之后,因为若是空消息,就不必发给server
- send 在 exit判断之前,自己发送的exit自己后面可以捕获;但是不发给server,则server没法捕获
- 持续服务(accept放入while)+异常断开(链路破裂,try)+正常断开(链路状态转变,raise异常)+ 防止send空消息(导致两端同时处于阻塞状态,client端进行处理) //以后均使用这种形式
- 说明:
- 链路异常断开:由try处理
- 链路正常断开:由server处理,server会收到 len(recv) == 0,raise异常后break
- client执行exit跳到close,
- client主动执行ctrl+d中断
- server.py
- 此时,server 只需要处理链路状态异常(len =0情况)、和链路异常两种情况
import socket
# socket 对象server_ip = ('127.0.0.1', 9999)sk = socket.socket()sk.bind(server_ip)sk.listen(2)# 持续 服务while True:
conn, addr = sk.accept()welcome_data = bytes('welcome to my server', encoding='utf-8')conn.send(welcome_data)
while True:# 链路破裂try:recv_data = conn.recv(1024).decode()print('from client:{}'.format(recv_data))# 链路状态转变,raise异常if len(recv_data) == 0:
send_data = bytes(recv_data.upper(), encoding='utf-8')raise Exception('链路状态发生改变,client主动进入close()')
conn.send(send_data)
except Exception as ex:print(ex, 'sb client go away.....')break
conn.close()
- client.py
- 此时,client 不论是 ctrl+d 还是手动 exit,都会进入close(),即转化链路状态
import socket# 创建socket 对象server_ip = ('127.0.0.1', 9999)sk = socket.socket()sk.connect(server_ip)# 提示welcome_data = sk.recv(1024).decode()print(welcome_data)
while True:user_inp = input('>>').strip()# 若输入exit,则跳到close() 进而转换链路状态if user_inp == 'exit':break# 防止send空消息,导致两边都处于 recv 阻塞状态if len(user_inp) == 0:continuesend_data = bytes(user_inp, encoding='utf-8')sk.send(send_data)recv_data = sk.recv(1024).decode()print(recv_data)sk.close()