首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

超详细的TCP、Sokcket和SuperSocket与TCP入门指导

kiba518脚本之家 作者 |kiba518 出品 | 脚本之家(ID:jb51net) 前言 本文主要介绍TCP、Sokcket和SuperSocket的基础使用。 创建实例模式的SuperSocket服务 首先创建控制台项目,然后Nuget添加引用SuperSocket.Engine。 然后编写服务代码,SuperSocket的服务代码主要是配置AppServer对象,因为AppServer已经很好的封装端口监听了。 代码如下所示: class Program { static AppServer appServer { get; set; } static void Main(string[] args) { var serverConfig = new SuperSocket.SocketBase.Config.ServerConfig(); serverConfig.Port = 5180; serverConfig.TextEncoding = "gb2312"; serverConfig.MaxConnectionNumber = 1000; appServer = new AppServer(); //配置 if (!appServer.Setup(serverConfig)) { Console.WriteLine("配置失败!"); return; } //启动 if (!appServer.Start()) { Console.WriteLine("启动失败!"); return; } Console.WriteLine("启动成功,按Q退出!"); appServer.NewSessionConnected += new SessionHandler<AppSession>(appServer_NewSessionConnected); appServer.SessionClosed += appServer_NewSessionClosed; appServer.NewRequestReceived += new RequestHandler<AppSession, StringRequestInfo>(appServer_NewRequestReceived); while (Console.ReadKey().KeyChar != 'q') { continue; } //停止 appServer.Stop(); Console.WriteLine("服务已停止"); Console.ReadKey(); } static void appServer_NewSessionConnected(AppSession session) { var count = appServer.SessionCount; Console.WriteLine($"服务端得到来自客户端的连接成功 ,当前会话数量:" + count); //这里也可以向会话的stream里写入数据,如果在这里向流写入数据,则客户端需要在Send之前先接收一次,不然的话,Send后接收的就是这条数据了 session.Send("连接成功"); } static void appServer_NewSessionClosed(AppSession session, CloseReason aaa) { var count = appServer.SessionCount; Console.WriteLine($"服务端 失去 来自客户端的连接" + session.SessionID + aaa.ToString()+ " 当前会话数量:" + count); } static void appServer_NewRequestReceived(AppSession session, StringRequestInfo requestInfo) { Console.WriteLine($"Key:" + requestInfo.Key + $" Body:" + requestInfo.Body); session.Send("我是返回值:" + requestInfo.Body); } } AppServer:AppServer是SuperSocket中定义的Socket服务类,他替我们实现了复杂的端口监听,不用再写While循环,不用再关心线程阻塞的问题,在监听端口在这里,我们只要调用AppServer的对象的Start方法,就可以了;AppServer还提供了一个配置文件类—ServerConfig,通过它,我们可以配置具体监听的端口、并发数量、编码、最大传输字节数、传输模式(TCP/UDP)等等属性;此外还提供三个重要事件:会话连接启动事件(NewSessionConnected)、会话关闭事件(SessionClosed)、请求接受事件(NewRequestReceived)。 注:文中在连接成功的事件中,我们向客户端发送消息了,即,客户端在连接后,发送消息前,需要接收该信息。 创建TCP发送消息客户端 服务建立后,我们建立客户端。 代码如下所示: staticvoidMain(string[]args){TCPConnect("127.0.0.1",5180);Console.ReadKey();}staticvoidTCPConnect(Stringserver,Int32port){stringmessage=$"ADDkiba518518"+"\r\n";try{TcpClientclient=newTcpClient();client.Connect(server,port);Byte[]data=System.Text.Encoding.Default.GetBytes(message);StringresponseData=String.Empty;NetworkStreamstream=client.GetStream();byte[]buffer=newbyte[1024*1024*2];Int32bytes=stream.Read(buffer,0,buffer.Length);responseData=System.Text.Encoding.Default.GetString(buffer,0,bytes);Console.WriteLine("接收服务器在连接事件中写入的数据:{0}",responseData);stream.Write(data,0,data.Length);Console.WriteLine("发送数据:{0}",message);data=newByte[256];bytes=stream.Read(buffer,0,buffer.Length);responseData=System.Text.Encoding.Default.GetString(buffer,0,bytes);Console.WriteLine("接收返回值:{0}",responseData);stream.Close();client.Close();}catch(ArgumentNullExceptione){Console.WriteLine("ArgumentNullException:{0}",e.Message);}catch(SocketExceptione){Console.WriteLine("SocketException:{0}",e.Message);}Console.Read();} 代码很简单,就是使用TcpClient连接服务器的IP和端口,然后发送消息。 因为我们使用的SuperSocket,有格式要求,所以我们需要准守。 格式要求如下: 命令名称+空格+参数+参数+...参数+"\r\n" 对应的字符串如下: $"ADD kiba518 518" + "\r\n" 因为上文中,服务在连接成功后就向客户端发送的流中写入了数据,所以,我们在Send消息前,先接收一下流中的数据。 客户端与服务联调 先运行服务,在运行客户端,结果服务端与客户端成功的完成了一次通信,如下图所示: 为了更清晰的了解通信内容,我们在服务接收消息事件中断点,如下图: 可以看到参数requestInfo完整的解析了我们发送的字符串【"ADD kiba518 518" + "\r\n"】。 创建配置模式的SuperSocket服务 现在我们创建一个配置模式的SuperSocket服务,这种模式客户通过配置创建多个SuperSocket,即可以在一个项目里通过配置监听多个端口,这里,我们只做一个端口监听的配置例子。 与实例模式的开始一样,先创建一个控制台程序,然后Nuget添加引用SuperSocket.Engine。 然后进行三步操作。 一,编写Main函数,启动SuperSocket,通过启动引导工厂BootstrapFactory实例化一个启动引导对象,然后初始化化,该初始化会遍历当前项目中所有继承了AppServer的类,然后调用他们的Start方法,代码如下所示: static void Main(string[] args){ #region 初始化Socket IBootstrap bootstrap = BootstrapFactory.CreateBootstrap(); if (!bootstrap.Initialize()) { Console.WriteLine(DateTime.Now + ":Socket初始化失败\r\n"); return; } var result = bootstrap.Start(); foreach (var server in bootstrap.AppServers) { if (server.State == ServerState.Running) { Console.WriteLine(DateTime.Now + ":serverName为:" + server.Name + "Socket运行中\r\n"); } else { Console.WriteLine(DateTime.Now + ":serverName为:" + server.Name + "Socket启动失败\r\n"); } } Console.ReadKey(); #endregion} 二,修改App.config配置文件,在configuration节点下,增加superSocket的section,并配置superSocket,代码如下: <configSections><sectionname="superSocket"type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig,SuperSocket.SocketEngine"/></configSections><!--配置SocketServer路径--><superSocket><servers><!--serverType属性有两个参数,第一个是服务类的完全限定名,第二个是服务类的命名空间--><servername="MySocket"textEncoding="gb2312"serverType="SuperSocketServerSessionMode.SocketServer,SuperSocketServerSessionMode"ip="Any"port="5180"maxConnectionNumber="100"></server></servers></superSocket> 三,创建SocketServer类、SocketSession类、SocketCommand类。 SocketServer类:继承泛型AppServer(其泛型类指定一个会话类)该类用于创建SuperSocket的服务并监听端口;其Setup方法,默认读取App.config配置文件中的superSocket节点—servers节点—server节点;读取时根据server的serverType属性匹配读取。 publicclassSocketServer:AppServer<SocketSession>{protectedoverrideboolSetup(IRootConfigrootConfig,IServerConfigconfig){Console.WriteLine("正在准备配置文件");returnbase.Setup(rootConfig,config);}protectedoverridevoidOnStarted(){Console.WriteLine("服务已开始");base.OnStarted();}protectedoverridevoidOnStopped(){Console.WriteLine("服务已停止");base.OnStopped();}protectedoverridevoidOnNewSessionConnected(SocketSessionsession){Console.WriteLine("新的连接地址为"+session.LocalEndPoint.Address.ToString()+",时间为"+DateTime.Now);base.OnNewSessionConnected(session);}} SocketSession类:继承AppSession,是SuperSocket的会话类。 如果客户端所发送的消息不合法,则会被会话的HandleUnknownRequest函数截获,如果合法,则发送到指定的命令类中。 代码如下: publicclassSocketSession:AppSession<SocketSession>{publicoverridevoidSend(stringmessage){Console.WriteLine("发送消息:"+message);base.Send(message);}protectedoverridevoidOnSessionStarted(){Console.WriteLine("Session已启动");base.OnSessionStarted();}protectedoverridevoidOnInit(){this.Charset=Encoding.GetEncoding("gb2312");base.OnInit();}protectedoverridevoidHandleUnknownRequest(StringRequestInforequestInfo){Console.WriteLine($"遇到未知的请求Key:"+requestInfo.Key+$"Body:"+requestInfo.Body);base.HandleUnknownRequest(requestInfo);}} SocketCommand类:是SuperSocket的命令类,定义明确的会话命令;类名即客户端发送消息的第一个空格前的字符串。 代码如下: publicclassSocketCommand:CommandBase<SocketSession,StringRequestInfo>{publicoverridevoidExecuteCommand(SocketSessionsession,StringRequestInforequestInfo){//根据参数个数或者其他条件判断,来进行一些自己的操作Console.WriteLine($"调用成功Key:"+requestInfo.Key+$"Body:"+requestInfo.Body);session.Send("已经成功接收到你的请求\r\n");}} 创建配置模式的SuperSocket客户端 创建一个配置模式的SuperSocket客户端,这一次我们使用Socket类创建。 代码如下: staticSocketsocketClient{get;set;}staticvoidMain(string[]args){socketClient=newSocket(SocketType.Stream,ProtocolType.Tcp);IPAddressip=IPAddress.Parse("127.0.0.1");IPEndPointpoint=newIPEndPoint(ip,5180);socketClient.Connect(point);Threadthread=newThread(Recive);//不停的接收服务器端发送的消息thread.Start();Threadthread2=newThread(Send);//不停的给服务器发送数据thread2.Start();}staticvoidRecive(){while(true){//获取发送过来的消息byte[]buffer=newbyte[1024*1024*2];vareffective=socketClient.Receive(buffer);if(effective==0){break;}varstr=Encoding.Default.GetString(buffer,0,effective);Console.WriteLine("服务器---"+str);Thread.Sleep(2000);}}staticvoidSend(){inti=0;intparam1=0;intparam2=0;while(true){i++;param1=i+1;param2=i+2;Console.WriteLine($"Sendi:{i}param1:{param1}param2:{param2}");stringmsg=$"SocketCommand{param1}{param2}"+"\r\n";Console.WriteLine($"msg:{msg}");varbuffter=Encoding.Default.GetBytes(msg);vartemp=socketClient.Send(buffter);Console.WriteLine($"Send发送的字节数:{temp}");Thread.Sleep(1000);}} 可以看到Socket的使用方式与Tcp的使用方式几乎相同,都是指定IP和端口号,只是Socket多了一步,需要指定协议类型ProtocolType,这里我们指定了是TCP。 客户端与服务联调 先运行服务,在运行客户端,结果通信成功,如下图所示: 到此TCP、Sokcket和SuperSocket的基本使用已经介绍完了,代码已经传到Github上了,欢迎大家下载。 Github地址:https://github.com/kiba518/SuperSocketConsole 本文作者:kiba518,.NET系统架构师 声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。

优秀的个人博客,低调大师

Web前端入门必知——浏览器基础知识

浏览器的主要功能: 是将用户选择的web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI(Uniform Resource Identifier统一资源标识符)来指定所请求资源的位置。 浏览器的主要组件包括: 1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。 2. 浏览器引擎 - 用来查询及操作渲染引擎的接口。 3. 渲染引擎 - 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来。 4. 网络 - 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。 5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。 6. JS解释器 - 用来解释执行JS代码。 7. 数据存储 - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术。 1. 浏览器输入URL到显示页面发生了什么? 老问题,大家面试的时候应该都被问过这种问题,网上的答案千篇一律,我们来更深入的了解一下。 1.1 在浏览器中输入url 用户输入url,例如http://www.feng.com。其中http为协议,www.feng.com为网络地址,及指出需要的资源在哪台计算机上。一般网络地址可以为域名或IP地址,此处为域名。使用域名是为了方便记忆,一串数字哦我们很容易会记错,但是为了让计算机理解这个地址还需要把它解析为IP地址。 1.2 查看浏览器缓存 如果访问过该url,会先进入浏览器缓存中查询是否有要请求的文件(浏览器缓存是在本地保存资源副本)。 当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。如果缓存查找失败,就会进入网络请求过程了。 在network中会标注该请求是在服务器中请求的还是浏览器缓存中的。 一条域名的DNS记录会在本地有两种缓存:浏览器缓存和操作系统(OS)缓存。 1.2.1 浏览器缓存 – 浏览器会缓存DNS记录一段时间。一般是2分钟到30分钟不等。查找浏览器缓存时会按顺序查找: Service Worker-->Memory Cache-->Disk Cache-->Push Cache。 Service Worker: 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。 Memory Cache: 内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 Disk Cache: 存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。 在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。 Push Cache: Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。 1.2.2系统缓存 – 如果在浏览器缓存里没有找到需要的记录,浏览器会做一个系统调用获得系统缓存中的记录(windows里是gethostbyname)。 1.2.3 路由器缓存** – 接着,前面的查询请求发向路由器,它一般会有自己的DNS缓存。 1.2.4 ISP DNS 缓存** – 接下来要check的就是ISP缓存DNS的服务器。在这一般都能找到相应的缓存记录。 1.2.5 递归搜索** – 你的ISP的DNS服务器从跟域名服务器开始进行递归搜索,从.com顶级域名服务器到Facebook的域名服务器。一般DNS服务器的缓存中会有.com域名服务器中的域名,所以到顶级服务器的匹配过程不是那么必要了。 1.3 DNS域名解析 如果没有访问过该url,就会进行DNS域名解析了。 IP地址和域名一样都是用来做网络标识的,域名和 IP 地址是一一对应的映射关系。 DNS:Domain Name System域名系统(基于RFC规范解释),是万维网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。 DNS解析过程: 1.3.1 用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端。 1.3.2 浏览器将接收到的url中抽取出域名字段,就是访问的主机名,比如www.feng.com, 并将这个主机名传送给DNS应用的客户端. 1.3.3 DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工作)。 1.3.4 该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址。 1.3.5 一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接。 1.4 获取端口号 可能域名下有多个端口号,对应着不同的网络功能,所以在DNS解析之后,浏览器还会获取端口号。 1.5 建立TCP连接 TCP连接,就是耳熟能详的三次握手好朋友,四次挥手是路人。 TCP连接过程: 1.5.1 服务端通过socket,bind和listen准备好接受外来的连接,此时服务端状态为Listen。 1.5.2 客户端通过调用connect来发起主动连接,导致客户端TCP发送一个SYN(同步)字节,告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号,客户端状态为SYN_SENT。 1.5.3 服务器确认(ACK)客户的SYN,并自己也发送一个SYN,它包含服务器将在同一连接中发送数据的初始序列号。 1.5.4 客户端确认服务的ACK和SYN,向服务器发送ACK,客户端状态ESTABLISHED。 1.5.5 服务器接收ACK,服务器状态ESABLISHED。 1.6 HTTP请求 既然我们握手成功了,连接到了Web服务器,浏览器会根据解析到的IP地址和端口号发起HTTP请求。 1.6.1 http协议向服务器发送请求,发送请求的过程中,浏览器会向Web服务器以Stream(流)的形式传输数据,告诉Web服务器要访问服务器里面的哪个Web应用下的Web资源。 1.6.2 服务器接收到浏览器传输的数据后,开始解析接收到的数据,服务器解析请求里面的内容时知道客户端浏览器要访问的是应用里面的哪这个Web资源,然后服务器就去读取这个Web资源里面的内容,将读到的内容再以Stream(流)的形式传输给浏览器。 1.7 关闭TCP TCP连接中止过程: 1.7.1 某端首先调用close,成为主动关闭端,向另一端发送FIN分节,表示数据发送完毕,此时主动关闭端状态FIN_WAIT_1; 1.7.2 接收到FIN的是被动关闭端,FIN由TCP确认,先向主动关闭端发送ACK,作为一个文件结束符传递给接收端应用进程(放在已排队等候该应用进程接收到的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接无额外数据可接收,接收端状态CLOSE_WAIT;主动关闭端接收到ACK状态变为FIN_WAIT_2; 1.7.3 一段时间后,接收端接收到这个文件结束符的应用进程调用close关闭套接字,向主动关闭端发送FIN,接收端状态为LAST_ACK; 1.7.4 主动关闭端确认FIN,状态变为TIME_WAIT,并向接收端发送ACK,接收端接收到ACK关闭TCP,而主动关闭端一段时间后也关闭TCP; 1.8 浏览器渲染 当浏览器获得一个html文件时,会自上而下的加载,并在加载过程中进行解析渲染。 解析: 1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。 2. 将CSS解析成 CSS Rule Tree 。 3. 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。 4. 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点 渲染: 1. 接收服务器返回html文件。 2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件,浏览器又发出CSS文件的请求,服务器返回这个CSS文件。 3. 浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了。 4. 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码。 5. 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码。 6. 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它。 7. Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。突然少了这么一个元素,浏览器不得不重新渲染这部分代码。 8. 终于等到了</html>的到来,浏览器泪流满面。 9. 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径。 10. 浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。 2. 浏览器是如何解析代码的? 上面已经描述了大概,我们深入的了解一下,了解之后可以考虑考虑我们怎么写代码可以给浏览器减少点工作量。 2.1 解析HTML HTML的解析是逐行解析。 浏览器的渲染引擎会解析HTML文档并把标签转换成内容树中的DOM节点。 它会解析style元素和外部文件中的样式数据。样式数据和HTML中的显示控制将共同用来创建另一棵树——渲染树。 渲染引擎会尝试尽快的把内容显示出来。它不会等到所有HTML都被解析完才创建并布局渲染树。它会 在处理后续内容的同时把处理过的局部内容先展示出来。 浏览器的解析器通常把工作分给两个组件——分词程序负责把输入切分成合法符号序列,解析程序负责按照句法规则分析文档结构和构建句法树。词法分析器知道如何过滤像空格,换行之类的无关字符。 解析器输出的树是由DOM元素和属性节点组成的。 DOM与标签几乎有着一一对应的关系,如下面的标签 <html> <body> <p> Hello 枫 </p> <div> <img src="feng.png"/></div> </body> </html> 会被转换成如的DOM树: 2.2 解析CSS CSS选择器的读取顺序是从右向左。 #molly div.haha span{color:#f00} 如上面的代码,浏览器会按照从右向左的顺序去读取选择器。 先找到span然后顺着往上找到class为“haha”的div再找到id为“molly”的元素。 成功匹配到则加入结果集,如果直到根元素html都没有匹配,则不再遍历这条路径,从下一个span开始重复这个过程。 整个过程会形成一条符合规则的索引树,树由上至下的节点是规则中从右向左的一个个选择符匹配的节点。 如果从左向右的顺序读取,在执行到左边的分支后发现没有相对应标签匹配,则会回溯到上一个节点再继续遍历,直到找到或者没有相匹配的标签才结束。 如果有100个甚至1000个分支的时候会消耗很多性能。反之从右向左查找极大的缩小的查找范围从而提高了性能。 这就解释了为什么id选择器大于类选择器,类选择器大于元素选择器。 2.3 解析JS 在浏览器中有一个js解析器的工具,专门用来解析我们的js代码。 当浏览器遇到js代码时,立马召唤“js解析器”出来工作。 解析器会找到js当中的所有变量、函数、参数等等,并且把变量赋值为未定义(undefined)。 把函数取出来成为一个函数块,然后存放到仓库当中。这件事情做完了之后才开始逐行解析代码(由上向下,由左向右),然后再去和仓库进行匹配。 <script> alert(a); //undefined var a = 1; alert(a); //1 </script> <script> a = 1; alert(a); //这个时候会运行报错! //这时候a并不是一个变量,解析器找不到,仓库里面并没有a </script> 再看一下这段代码 <script> alert(a); //function a(){alert(4)} var a = 1; alert(a); //1 function a(){alert(2)} alert(a); //1 var a = 3; alert(a); //3 function a(){alert(4)} alert(a); //3 </script> 在js预解析的时候,在遇到变量和函数重名的时候,只会保留函数块。在逐行解析代码的时候表达式(+、-、*、/、%、++、–、 参数 ……)会改变仓库里对应的值。 我们来了解一个词“作用域”,现在把这个词拆分一下。 作用:读、写操作 域:空间、范围、区域… 连起来就是能够进行读写操作的一个区域。 “域”:函数、json、……都是作为一块作用域。 全局变量、局部变量、全局函数 一段 也是一块域。在域解析的时候,也是由上向下开始解析。这就解释了为什么引用的外部公共js文件(比如:jquery)应该放到自定义js上边的原因。 再来看一下这段代码 <script> var a = 1; function fn(){ alert(a); //undefined var a = 2; } fn(); alert(a); //1 </script> 继续跟踪一下解析器的解析过程:首先函数fn()外部的a是一个全局变量,fn()里面的a是一个局部变量。fn()函数同时是一个作用域,只要是作用域,就得做预解析和逐行解析的步骤。所以第一个alert打印的是fn()作用域的仓库指向的变量a,即为undefined。第二个alert打印的是全局的变量a,即为1。 接下来继续看代码,基本雷同的代码,我改变其中一小个地方。 <script> var a = 1; function fn(){ alert(a); //1 a = 2; } fn(); alert(a); //2 </script> 看到这里当解析到fn()的时候,发现里面并没有任何变量,所以也就不往仓库里面存什么,此时的仓库里面是空的,啥也没有。但是这个时候解析并没有结束,而是从函数里面向外开始找,找到全局的变量a。此时打印的正式全局变量a的值。 这里就涉及到一个作用域链的问题。整个解析过程像是一条链子一样。由上向下,由里到外。局部能够读写全局,全局无法读写局部。 来,继续看代码,基本雷同的代码,我再次改变其中一小个地方。 <script> var a = 1; function fn(a){ alert(a); //undefined a = 2; } fn(); alert(a); //1 </script> 千万不能忘了,在预解析的时候浏览器除了要找变量和函数之外还需要找一些参数,并且赋值为未定义。所以这里的fn(a)相当于fn(var a),这个时候的逻辑就和第一段实例代码一样了。 继续搞事情,继续看代码,基本雷同的代码,我再次改变其中一小个地方。 <script> var a = 1; function fn(a){ alert(a); //1 a = 2; } fn(a); alert(a); //1 </script> 当代码执行到fn(a);的时候调用的fn()函数并且把全局变量a作为参数传递进去。 此时打印的自然是1,要记住function fn(a)相当于function fn(var a),所以这时候a=2;改变的是局部变量a,并没有影响到全局变量a,所以第二次打印的依然是1。 3. 浏览器的垃圾回收机制 由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。 JavaScript的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了。 var a = "before"; var b = "override a"; var a = b; //重写a 这段代码运行之后,“before”这个字符串失去了引用(之前是被a引用),系统检测到这个事实之后,就会释放该字符串的存储空间以便这些空间可以被再利用。 浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。 3.1 标记清除 这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。 当对象,无法从根对象沿着引用遍历到,即不可达(unreachable),进行清除。对于上面的例子,fn() 里面的 a 和 b 在函数执行完毕后,就不能通过外面的上下文进行访问了,所以就可以清除了。 这是当前主流的GC算法,V8里面就是用这种。 不管是高级语言,还是低级语言。内存的管理都是:分配内存使用内存(读或写)释放内存前两步,大家都没有太大异议。关键是释放内存这一步,各种语言都有自己的垃圾回收(garbage collection, 简称GC)机制。 在大部分的应用场景:一个新创建的对象,生命周期通常很短。所以,V8里面,GC处理分为两大类:新生代和老生代。 新生代的堆空间为1M~8M,而且被平分成两份(to-space和from-space),通常一个新创建的对象,内存被分配在新生代。当to-space满的时候,to-space和form-space交换位置(此时,to空,from满),并执行GC。如果一个对象被断定为,未被引用,就清除;有被引用,逃逸次数+1(如果此时逃逸次数为2,就移入老生代,否则移入to-space)。 老生代的堆空间大,GC不适合像新生代那样,用平分成两个space这种空间换时间的方式。老生代的垃圾回收,分两个阶段:标记、清理(有Sweeping和Compacting这两种方式)。 标记,采用3色标记:黑、白、灰。步骤如下: GC开始,所以对象标记为白色。 根对象标记为黑色,并开始遍历其子节点(引用的对象)。 当前被遍历的节点,标记为灰色,被放入一个叫 marking bitmap 的栈。在栈中,把当前被遍历的节点,标记为黑色,并出栈,同时,把它的子节点(如果有的话)标记为灰色,并压入栈。(大对象比较特殊,这里不展开) 当所有对象被遍历完后,就只剩下黑和白。通过Sweeping或Compacting的方式,清理掉白色,完成GC。 3.2 引用计次 引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。 但是用这种方法存在着一个问题,下面来看看代码: function problem() { var objA = new Object(); var objB = new Object(); objA.someOtherObject = objB; objB.anotherObject = objA; } 在这个例子中,objA和objB通过各自的属性相互引用;也就是说这两个对象的引用次数都是2。在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成之后,objA和objB还将会继续存在,因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露。 大多数浏览器已经放弃了这种回收方式。 4. 浏览器的本地存储 如果我问你,浏览器中的缓存有哪些,我相信绝大部分人会说有三种:cookie,sessionStorage,localStorage。 但是诶,我不知为什么大家都叫这三个为缓存,他们叫缓存,我们上面提到的Memory Cache等cache也叫缓存,不是很乱吗,而且浏览器把他们归到了storage里面,storage翻译过来为存储。 还有一点,这里有五种:Cookies、Local Storage、Session Storage、WebSQL 和 IndexedDB。 4.1 cookie Cookies 是最早的本地存储,是浏览器提供的功能,并且对服务器和 JS 开放,这意味着我们可以通过服务器端和客户端保存 Cookies。不过可以存储的数据总量大小只有 4KB,如果超过了这个限制就会忽略,没法进行保存。 HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。Cookie实际上是一小段的文本信息(key-value格式)。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。 4.2 Local Storage Session Storage Local Storage 与 Session Storage 都属于 Web Storage。Web Storage 和 Cookies 类似,区别在于它有更大容量的存储。其中 Local Storage 是持久化的本地存储,除非我们主动删除数据,否则会一直存储在本地。Session Storage 只存在于 Session 会话中,也就是说只有在同一个 Session 的页面才能使用,当 Session 会话结束后,数据也会自动释放掉。 4.3 cookie Local Storage Session Storage比较 一般在我们面试的时候,面试官都会问cookie Local Storage Session Storage之间有什么区别。 特性 Cookie Local Storage Session Storage 数据的生命期 可设置失效时间,默认是关闭浏览器后失效 除非被显式清除,否则永久保存 会话级存储,仅在当前会话下有效,会话结束,关闭页面或浏览器后被清除 存放数据大小 4KB左右 5MB~10MB(浏览器不同,情况不同) 5MB~10MB(浏览器不同,情况不同) 与服务器端通信 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能和安全问题 仅在客户端(即浏览器)中保存,不参与和服务器的通信 仅在客户端(即浏览器)中保存,不参与和服务器的通信 易用性 源生的Cookie接口不友好,开发者需要根据需求封装 源生接口良好,亦可再次封装来对Object和Array有更好的支持 源生接口良好,亦可再次封装来对Object和Array有更好的支持 应用场景 用户登录时,保存服务器返回的一段加密过的唯一辨识单一用户的code,用以判断当前用户登录状态,或者之前电商网站用来保存用户的购物车信息。 Local Storage可以替代Cookie完成用户购物车信息的前端保存功能,同时可以当作HTML5游戏的本地数据的存储空间。 当页面存在多表单的情况下,可以利用Session Storage实现表单页拆分,优化用户体验。 注意 不要将系统敏感的数据保存到Cookie,Local Storage,Session Storage中,防止XSS注入的风险。因为XSS注入可以通过控制台对你的属性值进行修改,具体可以参考我写的另一篇博客,前端黑客技术。 4.4 WebSQL WebSQL 与 IndexedDB 都是最新的 HTML5 本地缓存技术,相比于 Local Storage 和 Session Storage 来说,存储功能更强大,支持的数据类型也更多,比如图片、视频等。 WebSQL 更准确的说是 WebSQL DB API,它是一种操作本地数据库的网页 API 接口,通过 API 可以完成客户端数据库的操作。当我们使用 WebSQL 的时候,可以方便地用 SQL 来对数据进行增删改查。而这些浏览器客户端,比如 Chrome 和 Safari 会用 SQLite 实现本地存储,微信就采用了 SQLite 作为本地聊天记录的存储。 4.5 IndexedDB IndexedDB就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。它可以存储大量数据,提供了查找接口,能够建立索引。但是不属于关系型数据库(不支持SQL查询语句,更类似于NoSQL数据库)。 IndexedDB的特点: 键值对存储:IndexedDB内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括JavaScript对象。对象仓库中,数据以“键值对”的形式保存。每一个数据记录都有对应的主键,主键是唯一的,如果重复会抛出一个错误。 异步:IndexedDB操作不会锁死浏览器,用户依然可以进行其他操作,与Local Storage的同步操作不同,异步的设计是为了大量数据的读写,拖慢页面的表现,降低用户体验。 支持事务:IndexedDB支持事务(transaction),这意味着一些列操作步骤中,只要有某个步骤出现异常,则整个事务就会消失,数据库回滚到事务发生之前的状态,不存在只写一部分数据的情况。 同源限制:IndexedDB受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能够访问跨域的IndexedDB数据库。 存储空间大:IndexedDB的存储空间一般不少于250MB,或者更大。 支持二进制存储:IndexedDB不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer对象和Blob对象)。 事务是数据库的概念,事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。 5. 浏览器的线程 我们平常使用JavaScript的时候都知道js是单线程的,而浏览器,则是多线程的。 5.1 CPU CPU是计算机的核心,其负责承担计算机的计算任务。这里我们比喻为一个工厂。 5.2 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 我们这里将进程比喻为工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。 5.3 线程 线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元。 这里把线程比喻一个车间的工人,即一个车间可以允许由多个工人协同完成一个任务。 5.4 浏览器的多线程 浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成: GUI 渲染线程 JavaScript引擎线程 事件触发线程 定时触发器线程 异步http请求线程 5.4.1 GUI渲染线程 GUI渲染线程负责渲染浏览器界面HTML元素,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。 当界面需要重绘(Repaint)或由于某种操作引发回流(重排)(reflow)时,该线程就会执行。 在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。 5.4.2 JavaScript引擎线程 JavaScript引擎,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。 JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序(单线程)。 注意:GUI渲染线程和JavaScript引擎线程互斥。 由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。 因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript引擎为互斥的关系,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。 如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。 5.4.3 事件触发线程 当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。 这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。 5.4.4 定时触发器线程 setInterval与setTimeout所在线程 浏览器定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确。 通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行) 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。 5.4.5 异步http请求线程 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求。 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JavaScript引擎执行。 6. 浏览器的兼容 浏览器的兼容问题一直是一个让人很头痛的问题,前段时间我还在为qiankun兼容IE弄得焦头烂额。 6.1 为什么我们的代码在浏览器中会出现兼容问题? 因为不同的浏览器对同一段代码有不同的解析,造成页面显示效果不统一的情况。在大多数情况下,我们的需求是,无论用户用什么浏览器来查看我们的网站或者登陆我们的系统,都应该是统一的显示效果。 版本越高的浏览器,支持的特性越多,我们用的某个插件使用的特性可能高版本的浏览器支持,低版本的不支持。 6.2 我们要怎么解决呢? 6.2.1 CSS Hack 在CSS方面,我们可以使用CSS Hack。CSS hack是通过在CSS样式中加入一些特殊的符号也就是浏览器前缀,让不同的浏览器识别不同的符号(什么样的浏览器识别什么样的符号是有标准的,CSS hack就是让你记住这个标准),以达到应用不同的CSS样式的目的。 6.2.2 polyfill 在JS方面,我们可以使用polyfill。polyfill 是一段代码(或者插件),提供了那些开发者们希望浏览器原生提供支持的功能。程序库先检查浏览器是否支持某个API,如果不支持则加载对应的 polyfill。比如,html5的storage。不同浏览器,不同版本,有些支持,有些不支持。其实polyfill就是shim的一种。 Shim 指的是在一个旧的环境中模拟出一个新 API ,而且仅靠旧环境中已有的手段实现,以便所有的浏览 器具有相同的行为。 6.2.3 PostCSS PostCSS是一个利用JS插件来对CSS进行转换的工具,这些插件非常强大,强大到无所不能。其中,Autoprefixer就是众多PostCSS插件中最流行的一个。 Autoprefixer可以自动帮我们加上浏览器前缀。 6.2.4 Modernizr.js Modernizr.js十分的强大,既能给老版本浏览器打补丁,又能保证新浏览器渐进增强的用户体验。 Modernizr默认做的事情很少,除了(在你选择的情况下)给不支持html5的标签的浏览器,如IE6,7,8追加一点由Remy Sharp开发的html5垫片脚本,使其识别 、等html5元素之外,它主要做的就是浏览器功能检测。因此,它知道浏览器是否支持各种html5和css3特性。 7. 最后 最近断断续续整理了一些阿里、腾讯、字节等等大厂的面试题,目的是想了解一下大厂招聘的技术热点,不断提升学习。 其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等,免费分享给大家,还在持续整理收集整理中,有需要的朋友点击这里免费领取题目+解析PDF。 篇幅有限,仅展示部分内容

优秀的个人博客,低调大师

如何在公司项目中使用 WebSocket— 入门实战指南

本文从 WebSocket 基础概念出发,介绍在实际开发中从本地联调到部署上线的流程以及注意事项,让 WebSocket 小白以最小成本应用到项目中。 一、WebSocket 基础 1、什么是 WebSocket WebSocket 是一种网络传输协议,可在单个TCP连接上进行全双工通信 2、对比 http 两者都位于应用层,都依赖TCP协议 WebSocket 协议一般以ws://或wss://开头 HTTP 不支持全双工通信,一般使用轮询方式 3、WebSocket 基础用法 兼容性: https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket 一个简单的 Demo (视频详见原文) 客户端可以在控制台 -network-ws下看到 WebSocket 消息 注意请求头里的几个关键字段 请求地址为 ws:// 或 wss:// 开头 Connection 必须设置 Upgrade,表示客户端希望连接升级 Upgrade字段必须设置 WebSocket,表示希望升级到 WebSocket 协议。 如果服务端支持 websocket,会在响应头中返回相同的信息,并且连接状态置为101(协议切换成功 二、如何在项目中使用 WebSocke 下面以一个实际项目为例,展示如何实现一个WebSocket接口,包含开发->联调->部署→上线整个流程。 1、开发环境 将上面的 Demo简单封装一下,在项目中调用如下: 配置 webpack 代理 说明: WebSocket接口要和http接口分开 域名使用location.host并且通过反向代理转发,目的是保留cookie和头信息。 2、心跳检测&断线重连 为了保证连接稳定,需要考虑一些异常情况,如网络波动导致连接中断,服务器超时等。 心跳检测即客户端定时向服务端发送心跳消息,保持连接稳定; 断线重连即发送消息前,检测连接状态,若连接中断,尝试n次连接; 封装如下: 也可选择第三方库处理。 3、Nginx配置 The WebSocket protocol is different from the HTTP protocol, but the WebSocket handshake is compatible with HTTP, using the HTTP Upgrade facility to upgrade the connection from HTTP to WebSocket. This allows WebSocket applications to more easily fit into existing infrastructures. For example, WebSocket applications can use the standard HTTP ports80 and443, thus allowing the use of existing firewall rules. location /websocket { proxy_pass http://xx.xxx.xx.xx; # websocket服务器。不用管 ws:// proxy_http_version 1.1; # http协议切换 proxy_set_header Host $host; # 保留源信息 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; # 请求协议升级,如果生产环境有报400错误,可以尝试将值设置为websocket proxy_set_header Connection $connection_upgrade; } 三、其他 sockiet.io sockiet.io是基于 Node 的实时应用程序框架,对比原生 WebSocket,封装了更多通用能力,且在不支持WebSocket的浏览器上,可以降级为轮询方式通信。 优点:成熟,开箱即用,兼容性好。 缺点:体积较大,前后端必须统一,即后端使用socket.io则前端必须使用socket.io-client 对应。 作者:vivo 商业化大前端团队

优秀的个人博客,低调大师

Python函数入门

“函数是 Python 程序中不可或缺的一部分,它是的程序模块化、清晰化。Python的很多功能都是通过内置函数提供的,比如 sorted() 表示对一个集合序列排序,len() 表示返回一个集合序列的长度大小等等。这节课,我们主要来学习 Python 的自定义函数。” 10.1 定义函数 使用def关键字 函数可以有返回值也可以没有返回值 # content of test_sample.pydef inc(x): # 有返回值 return x + 1def test_answer(): # 没有返回值 assert inc(3) == 5 10.2 调用函数 add_one=inc(3) 函数被调用时,这个函数此前必须定义过 10.3 函数的参数 10.3.1 位置参数 def divmod(x, y): # known case of builtins.divmod """ Return the tuple (x//y, x%y). Invariant: div*y + mod == x. """ return (0, 0) 这是一个Python内置的获得商和余数的函数。这个函数有两个参数:x和y,这两个参数都是位置参数,调用这个函数时,要按照顺序传入的两个实参值,实参值会依次赋给参数x和y。 10.3.2 默认值参数 def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True): pass 这是Python内置的打开文件的函数,调用open打开文件时,可以只传递file文件路径,其他的参数可以不传递实参值,因为其他的参数都具有默认值。mode是打开文件的模式,默认值是r,表示以读的方式打开文件,默认值参数必须指向不可变对象。 不要这样: def add_end(li=[]): pass 要这样: def add_end(li=None): pass 10.3.3 变长参数 可以给函数一次性传递多个参数,或者传递一个列表或元组。定义函数时在参数名前加一个星号*。 def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum 函数calc的功能是求任意多个参数的平方和。函数定义时,在参数名加一个星号*。这样的函数,在调用时,可以传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。实际例子如下: calc(1, 2,3,4)calc(1, 2)calc()nums = [1, 2, 3]calc(*nums) # 还可以传递列表或者元组等进去 max是python内置的求最大值的函数。它的定义如下: def max(*args, key=None): # known special case of max """ max(iterable, *[, default=obj, key=func]) -> value max(arg1, arg2, *args, *[, key=func]) -> value With a single iterable argument, return its biggest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the largest argument. """ pass 第一个参数是一个变长参数,上面举了两个例子,第一个参数可以是可迭代对象,也可以是多个参数。返回值是多个参数中的最大值,或者可迭代对象中的最大值。比如: print(max(1, 2, 3, 4, -9, -5)) # 多个参数print(max([1,2,3,4,-9,-5]))#可迭代对象,这里是一个列表 max函数还有一个默认值参数key,接受一个函数名,这时max函数返回的值是,将args参数分别用到key函数后的结果中的最大值。比如,找出一个数字列表中绝对值最大的元素: max([1, 2, 3, 4, -9, -5], key=abs) 10.3.4 关键字参数 下面这个sorted函数是Python内置的排序函数,它的第一参数是一个变长参数,第二个参数是个关键字参数,关键字参数是以两个星号开头。 def sorted(*args, **kwargs): # real signature unknown """ Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order. """ pass sorted函数默认是升序排序的,可以传入一个关键字参数reverse进行降序排序。方式是: print(sorted([1, 2, 3, 4, -9, -5], reverse=True)) 关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个字典。 再来自定义一个: def person(name, age, **kwargs): print(name,age,kwargs) 关键字参数可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。传递关键字参数有两种方式: 传入字典 extra = {'city': 'Beijing', 'job': 'Engineer'}person('Jack', 24, **extra) # 字典参数名签名放两个** 直接传入key=value person('Jack', 24, salary=10000, department='finace') 10.3.5 参数组合 定义函数时,参数顺序必须是:位置参数、默认参数、变长参数、关键字参数。定义顺序不能乱。 def f1(a, b, c=0, *args, **kw): pass 10.4 看懂Python源码中函数的定义 有些朋友平时反映,看不懂官方文档中介绍函数的说明,比如查看min的源码: def min(*args, key=None): # known special case of min """ min(iterable, *[, default=obj, key=func]) -> value min(arg1, arg2, *args, *[, key=func]) -> value With a single iterable argument, return its smallest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the smallest argument. """ pass min 函数的源码文档中,举了两个例子,其中的形参有 * 符号,又有 []?他们都表示啥意思呢? 函数形参列表中符号 * 表示,后面的形参只能为关键字参数,不能为位置参数,[]里面的参数是可选的,不是必填的,例如传递一个key参数和default参数时,必须写明参数名: >>> a = [1,2,3,4,2,2,3,4] >>> min(a,key=lambda x: a.count(x), default=1) # 求出个数最少的元素1 因为default是可选的,因此还可以这样: >>> list=[1,2,5,9,4,6,3]>>> min(list,key=lambda x:-x) # 取反后最小的值9 再来看一个min函数的用法,求一段文本中出现次数最少的字母。 import stringdef maxcharactor(content): content = content.lower() return min(string.ascii_lowercase, key=content.count)if __name__ == '__main__': print(maxcharactor("/Users/chunming.liu/learn/learn_python_by_coding/learn_string/")) 平时大家更多看到的是这么使用 min 函数,没有key参数: min([1,2,3,4,2,2,3]) 这是因为在min函数说明中,key参数是放在[] 中的,说明它是可选的,可以不传递。 自定义一个函数 func,参数 b 位于 * 后面,只能为关键字参数: def func(a,*,b): passfunc(a,b=1)f(a,1) # 这种调用是错误的TypeError: f() takes 1 positional argument but 2 were given 再看一个内置函数 sum: sum(iterable, start=0, /) Return the sum of a 'start' value (default: 0) plus an iterable of numbers When the iterable is empty, return the start value. This function is intended specifically for use with numeric values and may reject non-numeric types. 看到形参列表中有一个/,它表示/前的参数只能是位置参数,不能是关键字参数。 因此,以下调用是合法的: a = [1,3,2,1,4,2]sum(a,2) # start=2 表示求和的初始值为 215 以下调用是非法的,iterable 参数不能被赋值为关键字实参: sum(iterable=a,start=2)TypeError: sum() takes no keyword arguments 以后再查看Python中函数用法时,应该可以看懂了吧。 10.5 偏函数functools.partial Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。 先来看下Python官方的描述: # content of _functools.pyclass partial(object): """ partial(func, *args, **keywords) - new function with partial application of the given arguments and keywords. """ pass 翻译过来就是,functools.partial可以给一个函数设置位置参数和关键字参数的默认值,并且返回一个新的可调用对象。之后调用这个新的可调用对象时,就自动带上了functools.partial设置的默认参数值,从而可以传递更少的参数。举个例子: from functools import partialdef add(a, b, *arg, **kwargs): return a + b, arg, kwargsadd10 = partial(add, 10, kw1=1) # 设置add函数的位置参数a的默认值为10,关键词参数kwargs中的参数kw1的默认值是1,并返回新的可调用对象add10print(add10(11, 12)) # 调用新的函数对象add10,只需要在传递add函数的变量b的值就可以了 用Pycharm调试上面的代码,在add函数的return语句处添加断点,可以看到add10这个对象中args属性里面有一个10,kwwargs里面有一个"kw"=1,这就是通过partial偏函数设置进去的。 当调用add10时,只需要提供变量b=11和arg的值12,add10就能够将其与偏函数设置的值一起传递给原函数add了。 本文分享自微信公众号 - 明说软件测试(liuchunmingnet)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

首次使用 linux 阿里云服务器【入门及使用】

上午编辑的文章 下午更新下 安装环境 因为服务器默认 linux 系统,所以这里讲怎么配置 linux 云服务环境。我第一次使用的时候,还以为是要去安装一个界面化桌面,以便我这个命令小白可以操作。但是,对于只有2G 的内存来说安装了之后将会很卡很卡。后来我问学长,他说不要安装界面化,使用命令就好了。 首先关于远程服务器的登录入口, 默认系统的用户名是 root, 然后登录密码可以在控制台进行修改。如下图点击重置密码。 重置之后,然后点击远程连接,就可以连接登录到你的服务器上了。这里要将一点,如果你是 mac 电脑的话,可以在你的电脑上通过 ssh 登录到你的服务器。 命令如下:ssh root@你的公网IP,比如 ssh root@120.78.32.12然后输入密码即可连接成功。 接下来在终端中输入命令。配置环境。如果你是配置 java 环境,那么可以去谷歌一下如何在 linux 中配置 java 环境,这里我是配置的 node 环境,就讲一下如何配置 node 环境以及 npm。 这里我用的是源码安装。首先安装 node 编译依赖的第三方模块yum -y install gcc make gcc-c++ openssl-devel 然后下载 node 的源码包 node 官网的源码 包。wgethttps://nodejs.org/dist/v8.9.4/node-v8.9.4.tar.gz 你下载的时候可以手动改成当前最新版本。下载后,进行解压 tar -zxvf node-v8.9.4.tar.gz, 然后进入解压后的文件夹 cd node-v8.9.4, 依次./configuremakesudo make install这里编译时间会有点久,请耐心等待- -查看安装成功安装 npm同样下载 npm 包wgethttp://nodejs.org/dist/npm/npm-1.4.9.zip解压 tar -zxvf npm-1.4.9.tgz查看是否安装成功 npm -v 这就是 node 环境配置了, 然后就是 mysql 数据库。http://blog.csdn.net/win7system/article/details/53579500 关于项目的上传,如果你本地编写了代码,想上传至服务器,第一个方式,可以是使用 ftp 上传文件。 第二个方式,就是在你的服务器上搭建一个 git 服务器,通过从服务器推送和克隆项目来获取文件。这里我使用的是第二种方法,参见教程: >https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/00137583770360579bc4b458f044ce7afed3df579123eca000 这里注意一下, 关于 设置 ssh 登录,一定要把创建的 .ssh 文件放到你创建的用户下,如我这里是 git 用户,路径就是放置在正确位置之后,还要注意项目的归属者也要是 git 用户,这样才会在 git 用户里匹配到对应的 ssh key。也就是下面这一个步骤还有一点要注意的就是,你再本地推送了代码上 git 服务器之后,想要在云服务器上获取到代码, 需要在服务器上再次克隆 git clone git@server:/srv/sample.git 项目,git pull 获取到代码。 这里也可以通过自己配置 git 服务器的钩子函数,使其自动更新代码,我还没配置,就先不说了 - -。 好了,现在现在基本环境配置好了就可以开始开发你的项目啦。 另外附:项目在服务器上启动了,发现在本地电脑无法通过 ip 访问,那么可能是你的端口号没有开放,需要去服务器上设置安全组。具体添加方法,以及其他一些关于服务器的配置,都可以随时点击右侧的 点我提问,像云博士提问。 = =入口 博客到这里就结束啦,希望可以帮到大家,最后还要跟大家说的一件事就是在使用阿里云服务器时,出现任何问题都可以在控制台提交工单,让阿里的程序员们来帮你解决问题,但是工单的回复速度可能会比较慢 如果在阿里云官方买产品包括:云服务器,云数据库等, 参考下面这些建站干货:阿里云产品2000元代金券领取地址:阿里云云小站阿里云官方最新活动公布地址:阿里云官方最新活动 (实时更新)阿里云服务器实例规格组地址:官方云服务器实例阿里云学习路径地址:阿里云学习路径

优秀的个人博客,低调大师

Nginx系列教程(1) Nginx基本介绍和安装入门

基本介绍 Nginx 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务。 Nginx 是由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。 Nginx 特点是占有内存少,并发能力强。 事实上 nginx 的并发能力确实在同类型的网页服务器中表现较好,一般来说,如果我们在项目中引入了 Nginx ,我们的项目架构可能是这样: 在这样的架构中 , Nginx 所代表的角色叫做负载均衡服务器或者反向代理服务器,所有请求首先到达 Nginx 上,再由 Nginx 根据提前配置好的转发规则,将客户端发来的请求转发到某一个 Tomcat 上去。 那么这里涉及到两个概念: 负载均衡服务器 就是进行请求转发,降低某一个服务器的压力。负载均衡策略很多,也有很多层,对于一些大型网站基本上从 DNS 就开始负载均衡,负载均衡有硬件和软件之分,各自代表分别是 F5 和 Nginx (目前 Nginx 已经被 F5 收购),早些年,也可以使用 Apache 来做负载均衡,但是效率不如 Nginx ,所以现在主流方案是 Nginx 。 反向代理服务器: 另一个概念是反向代理服务器,得先说正向代理,看下面一张图: 在这个过程中,Google 并不知道真正访问它的客户端是谁,它只知道这个中间服务器在访问它。因此,这里的代理,实际上是中间服务器代理了客户端,这种代理叫做正向代理。 那么什么是反向代理呢?看下面一张图: 在这个过程中,10086 这个号码相当于是一个代理,真正提供服务的,是话务员,但是对于客户来说,他不关心到底是哪一个话务员提供的服务,他只需要记得 10086 这个号码就行了。 所有的请求打到 10086 上,再由 10086 将请求转发给某一个话务员去处理。因此,在这里,10086 就相当于是一个代理,只不过它代理的是话务员而不是客户端,这种代理称之为反向代理。 Nginx 的优势 在 Java 开发中,Nginx 有着非常广泛的使用,随便举几点: 使用 Nginx 做静态资源服务器:Java 中的资源可以分为动态和静态,动态需要经过 Tomcat 解析之后,才能返回给浏览器,例如 JSP 页面、Freemarker 页面、控制器返回的 JSON 数据等,都算作动态资源,动态资源经过了 Tomcat 处理,速度必然降低。对于静态资源,例如图片、HTML、JS、CSS 等资源,这种资源可以不必经过 Tomcat 解析,当客户端请求这些资源时,之间将资源返回给客户端就行了。此时,可以使用 Nginx 搭建静态资源服务器,将静态资源直接返回给客户端。 使用 Nginx 做负载均衡服务器,无论是使用 Dubbo 还是 Spirng Cloud ,除了使用各自自带的负载均衡策略之外,也都可以使用 Nginx 做负载均衡服务器。 支持高并发、内存消耗少、成本低廉、配置简单、运行稳定等。 Nginx 安装: 由于基本上都是在 Linux 上使用 Nginx,因此松哥这里主要向大家展示 CentOS 7 安装 Nginx: 首先下载 Nginx 然后解压下载的目录,进入解压目录中,在编译安装之前,需要安装两个依赖: 然后开始编译安装: 装好之后,默认安装位置在 : 进入到该目录的 sbin 目录下,执行 nginx 即可启动 Nginx : Nginx 启动成功之后,在浏览器中直接访问 Nginx 地址: 看到如上页面,表示 Nginx 已经安装成功了。 如果修改了 Nginx 配置,则可以通过如下命令重新加载 Nginx 配置文件: 总结 本文算是一个简单的 Nginx 扫盲文,希望大家看完后对 Nginx 有一个基本的认知

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册