第二十六篇:正则入门、分析请求path、Socket服务端开启协程支持
引言:上篇我们对Socket服务端进行了一个死循环,不管客户端连接了多少次,都不会让我们的服务端停止,并且实现了浏览器的基本输出;
如果浏览器不支持响应,需要添加响应的http格式;那我们的浏览器就会正常的响应;
今天我们要做的内容是在上节课的基础上加上协程支持;并分析请求path;
首先在上篇浏览器连接的时候尼,发送了一段HTTP请求;其它我们都不看,我们先只看第一行;

GET / HTTP/1.1
GET:代表GET请求;
/:代表请求的path到底是什么
HTTP/1.1:协议以及版本
一:go里面正则接触下
先来演示套路,具体细节以后再说;
所属包:regexp
1.1:先触碰几个方法,对我们的字符串进行处理;
1、r,err:=regexp.Compile(expr string) (*Regexp, error) 将正则表达式编译成一个正则对象
2、r:=regexp.MustCompile(str string) *Regexp 同上,如果正则写错了,会把异常panic出来
3、r. MatchString(s string) bool 判断能否匹配到(一般做判断用,判断如果没有,就不做下面的步骤)
4、r. FindStringSubmatch和r.FindAllStringSubmatch。前者查找匹配到的第一个匹配结果及分组内容,后者支持多个匹配结果,n参数(查找多少次,负数为不限)
package main
import (
"fmt"
"regexp"
)
func GetRqPath(rq string) string{
/* 正则匹配的时候,如果有些内容需要转义,就要使用双引号;
GET /abc.php HTTP/1.1 中GET后面是一个空格,在我们正则里面就是\s,
因为\s中\需要转义,因此要加上转义符\,即"^GET\\s"
如果不想转义,想要原始输出,可以使用反单引号``,即:`GET\s`;
因为要匹配abc.php这部分内容,所以我们写个分组,分组内容:
(.*?),再匹配后面的空格和HTTP即(.*?)\sHTTP,
r:=regexp.MustCompile(`GET\s(.*?)\sHTTP`) 这个正则取出来以后
就可以在字符串str中进行匹配,字符串的匹配有以下方法:
(1):r.MatchString(s string) bool 判断能否匹配到
一般做判断用,判断如果没有,就不做下面的步骤
(2):r.FindStringSubmatch和r.FindAllStringSubmatch。
前者查找匹配到的第一个匹配结果及分组内容,
后者支持多个匹配结果,n参数(查找多少次,负数为不限)
*/
//课后思索如何使用分组名的方式进行匹配?
r:=regexp.MustCompile(`GET\s(.*?)\sHTTP`)
if r.MatchString(rq){
/* 如果匹配到匹配到的结果为:GET /abc.php HTTP
匹配内容为/abc.php 这里要取出第二个内容即/abc.php
*/
return r.FindStringSubmatch(rq)[1]
}else {
return "/"
}
}
func main() {
/* 如下为正则需要匹配的内容;
只要取/abc.php这一部分内容就行了;
只看path部分,看path部分是否有我们设定的一些字符;
如果有,
*/
str:=`GET /abc.php HTTP/1.1
Host: localhost:8099
Connection: keep-alive
Upgrade-Insecure-Requests: 1`
fmt.Println(GetRqPath(str))///abc.php
}
二:分析请求path
将上述匹配代码块放到我们的服务端的一个函数中:

服务端代码如下:
package main
import (
"fmt"
"net"
"time"
)
func response()string{
str:=`HTTP/1.1 200 OK
Server:myServer
Content-Type:text/html
`+`this is body`
return str
}
func main() {
lis,err:=net.Listen("tcp","127.0.0.1:8099")
if err !=nil{
fmt.Println(err.Error())
return
}
defer lis.Close()
fmt.Println("创建监听成功,等待客户端连接")
for {
client,err:=lis.Accept()
if err !=nil{
fmt.Println(err.Error())
return
}
func(c net.Conn){
defer c.Close()
buf:=make([]byte,4096)
//服务端读
n,err:=c.Read(buf)
if err !=nil{
fmt.Println(err.Error())
return
}
if GetRequestPath(string(buf[0:n]))=="/delay"{
time.Sleep(time.Second*5)
}
//服务端发送
c.Write([]byte(response()))
}(client)
}
}
执行结果:由于服务端的代码是单进程单线程,所以当http://localhost:8099/delay执行还没结束的时候,去执行http://localhost:8099/也会延迟;这也是我们Socket服务端处理能力
比较单一的问题;

为了解决上述问题,我们接下来使用协程来处理:
改造代码:
package main
import (
"fmt"
"net"
"time"
)
func response()string{
str:=`HTTP/1.1 200 OK
Server:myServer
Content-Type:text/html
`+`this is body`
return str
}
func main() {
lis,err:=net.Listen("tcp","127.0.0.1:8099")
if err !=nil{
fmt.Println(err.Error())
return
}
defer lis.Close()
fmt.Println("创建监听成功,等待客户端连接")
for {
client,err:=lis.Accept()
if err !=nil{
fmt.Println(err.Error())
return
}
/* 每当有客户端连接的时候,就开一个协程
改造代码很简单,直接加上 go 即可以;*/
go func(c net.Conn){
defer c.Close()
buf:=make([]byte,4096)
//服务端读
n,err:=c.Read(buf)
if err !=nil{
fmt.Println(err.Error())
return
}
if GetRequestPath(string(buf[0:n]))=="/delay"{
time.Sleep(time.Second*5)
}
//服务端发送
c.Write([]byte(response()))
}(client)
}
}
执行结果:如此便解决了上述问题,http://localhost:8099/delay执行还没结束的时候,去执行http://localhost:8099/也不会延迟;

补充思考:每有一个客户端连接,就会开辟一个协程,如果有成千上万个客户端同时连接, 就会开辟成千上万个协程,因此会有性能损耗,如何解决这个问题?
可以使用协程池来解决,先思考或者查阅资料,后面的篇幅会讲起;

浙公网安备 33010602011771号