一、客户端/服务器架构
- 硬件C/S架构(打印机)
- 软件C/S架构(web服务)
- server端要求:
- 力求一直提供服务
- 要绑定一个唯一的地址,客户端可以明确的找到
二、基于tcp协议的简单套接字
- Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它时一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Scoket接口后面
- 基于文件类型的套接字家族
- AF_UNIX
- AF_INET(基于网络通信)
- 套接字的工作流程
- socket()模块函数用法import socketsocket.socket(socket_family,socket_type,protocal=0)socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。获取tcp/ip套接字tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)获取udp/ip套接字udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)由于 socket 模块中有太多的属性。我们在这里破例使用了from module import *语句。使用 from socket import *,我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)
- 服务端套接字函数
- 客户端套接字函数
- 公用用途的套接字函数
- 代码示例
"""
tcp服务端
"""
import socket
IP_ADDR = (127.0.0.1, 8001)
# 创建服务器套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 加入一条socket配置,重用ip和端口
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 把地址绑定到套接字
sock.bind(IP_ADDR)
# 监听链接
sock.listen(5)
while True: # 服务器无限循环
conn, address = sock.accept() # 接受客户端链接
print(address)
while True: # 通信循环
msg = conn.recv(1024) # 对话(接收)
conn.send(msg.upper()) # 对话(发送)
conn.close()
sock.close()
"""
tcp客户端
"""
import socket
IP_ADDR = (127.0.0.1, 8001)
# 创建服务器套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 尝试连接服务器
sock.connect(IP_ADDR)
while True:
cmd = input(>>>).strip()
sock.send(cmd.encode(utf-8)) # 对话(发送/接收)
msg = sock.recv(1024)
print(msg)
sock.close()
- 6、关于套接字conn, address = phone.accept()中conn的内容
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind((127.0.0.1, 8080))
phone.listen(5)
while True:
conn, address = phone.accept()
print(conn)
print(address)
while True:
data = conn.recv(1024)
print(data)
conn.send(data.upper())
conn.close()
conn.close()
客户端输出数字后运行结果:
(127.0.0.1, 56964)
bda
bdasdasd
二、粘包的现象
- 只有TCP有粘包的现象,UDP永远不会粘包,粘包的问题是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的
- TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有--成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方
- UDP是无连接的,面向消息的,提供高效率服务,不会使用块的合并优化算法
- 自制报头解决粘包问题
- 报头(真实数据的描述信息,数字不能编码成bstyp格式
三、多线程编程
- 基于tcp协议的socketserver
- 基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环
- socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)
- server类
- request类
- 继承关系
import socketserver
class FTPserver(socketserver.BaseRequestHandler):
def handle(self):
print(dasdas,self)
print(self.request) # self.request = conn
while True: # 通信循环
data = self.request.recv(1024)
print(data)
self.request.send(data.upper())
if __name__ == __main__:
obj = socketserver.ThreadingTCPServer((127.0.0.1, 8080), FTPserver)
obj.serve_forever() # 链接循环
- 分析socketserver源码
- 与通信有关的,BaseRequestHandler,StearmRequsetHandler,DatagramRequestHandler
- 以下列代码为例:
- ftpserver = socketserver.ThreadingTCPServer((127.0.0.1,8080),FtpServer)ftpserver.server_forever()
- 源码分析总结:
- 基于TCP的socketserver我们自己定义的类中的
- self.server即套接字对象
- self.request即一个链接
- self.client_address即客户端地址
基于UDP的socketserver我们自己定义的类中的
- 面向对象的网络编程
- 服务器端
- """面向对象的网络编程服务器端"""import socketclass MyServer: address_family = socket.AF_INET # socket_type = socket.SOCK_STREAM listen_num = 5 receive_pack_size = 1024 coding = utf-8 decoding = gbk def __init__(self, ip_address, bind_and_activate=True): self.ip_address = ip_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() def server_bind(self): self.socket.bind(self.ip_address) self.ip_address = self.socket.getsockname() def server_activate(self): self.socket.listen(self.listen_num) def server_close(self): self.socket.close() def get_request(self): return self.socket.accept() @staticmethod def close_request(request): request.close() def run(self): while True: self.conn, self.client_address = self.get_request() print(starting...) while True: data = self.conn.recv(self.receive_pack_size) self.conn.send(data)server1 = MyServer((127.0.0.1, 8080))server1.run()