应用程序架构的分类:
C/S 客户端/服务器
B/S 浏览器/服务器
C/S架构和B/S架构的区别
C/S架构的优点:
- 个性化更容易实现
- 更安全
- 占用网络资源少
B/S结构的优点:
- 更新方便
- 使用方便
- 几乎不占用本地资源
@
前言
- C/S架构与socket的关系:我们学习socket的目的是为了实现C/S架构;
- 还有一种架构是基于socket来实现的,写一个服务器端软件,客户端软件不用写,客户端就是浏览器(浏览器的本质是套接字写的客户端);
一、什么是网络
在一台计算机上由硬件,硬件之上是操作系统,操作系统之上是应用程序(为客户端软件)
在另一台计算机上硬件之上是操作系统,在操作系统之上是应用程序(服务器端软件),这两台计算机是基于网络通信的
什么是网络:
-
物理介质:网线;
-
协议(就像两个人打电话,物理介质是电话线,两个人处于不同的地区,两个人说的话彼此听不懂,怎么解决?定义一个标准,称之为互联网协议)
总结:
在计算机通信中,计算机除了物理链接介质外,想要通信,计算机必须有一个标准,称之为互联网协议
即(网络就是物理链接介质和互联网协议)
接下来写一个客户端软件和服务器软件,两者之间基于网络通信(网络包含:物理链接介质,(是网络工程师干的);我们要干的事情是客户端软件给服务器软件发消息,必须让服务器软件和客户端软件遵循互联网协议)
二、互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
(我们主要学习五层)
数据的封装与解封装详情见:https://www.cnblogs.com/huoxc/p/13611394.html
在网络层:IP协议
传输层:TCP/UDP协议
数据链路层:以太网协议
应用层:http协议,DNS协议,ftp协议
三:什么是socket
- 应用层软件是我们写的,想要发数据传给传输层,应用层规定好的协议其他层控制不了,是客户端和服务器端规定好的,没人管,但是往外发数据,必须按照UDP或者TCP协议;如果用TCP协议必须按照TCP的协议来,必须把TCP协议学会以及IP协议等;那么效率比较低;
- 怎么解决:对于这种情况把TCP,IP协议等疯涨起来,对于我来说TCP以下我都不打交道了,我只跟应用层打交道,socket就是在应用层和传输层之间的,传输层一下的都封装到socket中,对于我来遵循套接字的标准,那么我们基于socket来开发的;针对这种协议,python中有一个模块,socket
四:套接字的工作流程
(TCP协议的套接字)
首先:
1:服务器端先拿到一个套接字对象
2:接下来绑定IP和端口
3:接下来监听
4:等待客户端的链接
其次:
1:客户端拿到一个套接字对象
2:客户端和服务器建立链接(即3次握手,双向链接)
3:客户端向服务器发送数据
4:服务器对请求进行处理,处理完毕后再响应给客户端
5:关闭客户端
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
服务端
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024 #收发消息的大小
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #其中socket.AF_INET:基于网络通信的套接字;
#socket.SOCK_STREAM:流式套接字又称之为TCP协议
s.bind(ip_port) #绑定的IP和端口(IP为服务器的IP)
s.listen(5) #监听,如打电话,在跟别人通话的时候,还可能来多个电话,这个链接处于挂起的状态,如果当前的链接以结束,立马就跟刚刚挂起的电话通信,在TCP协议中被称之为连接池的大小
conn,addr=s.accept() #手机接电话(accept是一种阻塞操作,什么时候变成非阻塞状态,有人链接就会变成非阻塞状态)返回一个链接对象,客户端的IP地址和端口
# print(conn)
# print(addr)
print('接到来自%s的电话' %addr[0])
msg=conn.recv(BUFSIZE) #接受消息
print(msg,type(msg))
conn.send(msg.upper()) #发消息,
conn.close() #关闭链接
s.close() #关闭套接字
客户端:
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(ip_port) #建立链接
s.send('lheiehei '.encode('utf-8')) #发消息(基于网络发送的是二进制格式)
feedback=s.recv(BUFSIZE) #收消息
print(feedback.decode('utf-8'))
s.close() #关闭链接
有可能还会出现以下的情况:
怎么解决:
这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)
#加入一条socket配置,重用ip和端口 或者重新写一个非著名端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
如果在Linux中解决方法如下:
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
加上链接循环与通信循环:
服务端:
import socket
ip_port=('127.0.0.1',9000) #电话卡
BUFSIZE=1024 #收发消息的尺寸
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
print(s) #s是套接字对象:这个套接字对象主要用来建立三次握手
s.bind(ip_port) #手机插卡
s.listen(5) #手机待机
conn,addr=s.accept() #手机接电话
# print(conn)
# print(addr)
while True:
msg=conn.recv(BUFSIZE) #听消息,听话(这种套接字主要用来收发消息,跟一个客户端收发消息,因为跟一个客户端建号的三次握手)
conn.send(msg.upper()) #发消息,说话
conn.close() #挂电话
s.close()
客户端:
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(ip_port) #拨电话
while True:
s.send('linhaifeng nb'.encode('utf-8')) #发消息,说话(只能发送字节类型)
feedback=s.recv(BUFSIZE) #收消息,听话
print(feedback.decode('utf-8'))
s.close() #挂电话
总结:
服务端套接字有几种形式:
- conn这种套接字(这种套接字主要用来收发消息,跟一个客户端收发消息,因为跟一个客户端建号的三次握手)
如果再换一个客户端链接,就是一个新的conn对象; - 刚开始的时候,拿到的s套接字对象,这个套接字对象主要用来建立三次握手;
如果客户端发的是空,则直接卡住了,在客户端没有卡住,在服务器端卡住了,服务端一直在等着,怎么解决?,在客户端解决;
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(ip_port) #拨电话
while True:
msg=input('>>>:').strip()
if not msg:continue #当为空的时候,继续让用户输入
s.close() #挂电话
粘包:
须知:只有TCP有粘包现象,UDP永远不会粘包,首先需要掌握一个socket收发消息的原理