一、客户端/服务器架构

  1. 硬件C/S架构(打印机)
  2. 软件C/S架构(web服务)
  3. server端要求:
  1. 力求一直提供服务
  2. 要绑定一个唯一的地址,客户端可以明确的找到

二、基于tcp协议的简单套接字

  1. Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它时一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Scoket接口后面
  2. 基于文件类型的套接字家族
  1. AF_UNIX
  2. AF_INET(基于网络通信)
  1. 套接字的工作流程
  2. socket()模块函数用法import socketsocket.socket(socket_family,socket_type,protocal=0)socket_family 可以是 AF_UNIX AF_INETsocket_type 可以是 SOCK_STREAM SOCK_DGRAMprotocol 一般不填,默认值为 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)
  1. 服务端套接字函数
  2. 客户端套接字函数
  3. 公用用途的套接字函数
  4. 代码示例
"""
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()
  1. 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

二、粘包的现象

  1. 只有TCP有粘包的现象,UDP永远不会粘包,粘包的问题是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的
  2. TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有--成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方
  3. UDP是无连接的,面向消息的,提供高效率服务,不会使用块的合并优化算法
  4. 自制报头解决粘包问题
  1. 报头(真实数据的描述信息,数字不能编码成bstyp格式

三、多线程编程

  1. 基于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() # 链接循环
  1. 分析socketserver源码
  1. 与通信有关的,BaseRequestHandler,StearmRequsetHandler,DatagramRequestHandler
  2. 以下列代码为例:
  1. ftpserver = socketserver.ThreadingTCPServer((127.0.0.1,8080),FtpServer)ftpserver.server_forever()
  1. 源码分析总结:
  1. 基于TCP的socketserver我们自己定义的类中的
  • self.server即套接字对象
  • self.request即一个链接
  • self.client_address即客户端地址

基于UDP的socketserver我们自己定义的类中的

  1. 面向对象的网络编程
  1. 服务器端
  1. """面向对象的网络编程服务器端"""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()