欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

你也可以写个服务器 - C# Socket学习2

程序员文章站 2022-06-07 12:55:27
续上篇 "《你也可以写个聊天程序 C Socket学习1》" 前言 这里说的服务器是Web服务器,是类似IIS、Tomcat之类的,用来响应浏览器请求的服务。 Socket模拟浏览器的Url Get请求 首先浏览器的请求是HTTP协议。我们上一篇说过,HTTP是短连接,用完就断开,是无状态的。所以我 ......

续上篇《你也可以写个聊天程序 - c# socket学习1》

前言

这里说的服务器是web服务器,是类似iis、tomcat之类的,用来响应浏览器请求的服务。

socket模拟浏览器的url get请求

首先浏览器的请求是http协议。我们上一篇说过,http是短连接,用完就断开,是无状态的。所以我们在等待响应的时候不需要另外开个线程循环等待。
也就是我们只需要通过socket和服务器建立连接,然后发送请求,然后接收服务器的响应,这样就完成了一次请求。
可是,我们一般访问网页的时候都是通过域名,没有ip也没有端口,怎么和服务器建立连接了。这里就需要用到我们上篇介绍的几个类了:

//根据dns获取域名绑定的ip
foreach (var address in dns.gethostentry("www.baidu.com").addresslist)
{
    console.writeline($"百度ip:{address}");
}

//字符串转ip地址
ipaddress ipaddress = ipaddress.parse("192.168.1.101");

//通过ip和端口构造ipendpoint对象,用于远程连接
//通过ip可以确定一台电脑,通过端口可以确定电脑上的一个程序
ipendpoint ipendpoint = new ipendpoint(ipaddress, 80);

对于http没有显示端口默认都是80 (为了简单这里就先不考虑https了)
知道了ip和端口,连接是可以建立了,为了得到正确的响应,我们应该给服务器发送什么消息呢?这里就需要用到http协议了。
具体协议这里就不说了,我们先f12看看浏览器的请求报文,然后依葫芦画瓢试试,以http://fanyi-pro.baidu.com为例。(现在找个非https的地址也是不容易了)
你也可以写个服务器 - C# Socket学习2
然后我们代码实现如下:

void ...()
{
    //得到主机信息
    iphostentry ipinfo = dns.gethostentry(new uri("http://fanyi-pro.baidu.com").host);
    //取得ipaddress[]
    ipaddress[] ipaddr = ipinfo.addresslist;
    //得到服务器ip
    ipaddress ip = ipaddr[0];
    //组合远程终结点
    ipendpoint ipendpoint = new ipendpoint(ip, 80);
    //创建socket 实例
    socket socketclient = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp);
    
    //尝试连接
    socketclient.connect(ipendpoint);
    //发送请求
    send(socketclient);
    //接收服务器的响应 
    receive(socketclient); 
}

//接收来自服务端的消息
void receive(socket socketclient)
{
    byte[] data = new byte[1024 * 1024];
    while (true)
    {
        //读取客户端发送过来的数据
        int readleng = socketclient.receive(data, 0, data.length, socketflags.none);
        textbox2.appendtext($"{socketclient.remoteendpoint}:{encoding.utf8.getstring(data, 0, readleng)}\r\n");
    }
}

//发送消息到服务端
void send(socket socketclient)
{
    //为了方便演示,仅用请求报文的前两行即可。(切记:需要严格按照报文格式。如,最后需要连续两次换行)
    var msg = $"get / http/1.1\r\nhost: {new uri(textbox1.text).host}\r\n\r\n";
    socketclient.send(encoding.utf8.getbytes(msg));
}

整个流程也就是:

  • 1、dns服务把域名解析成ip
  • 2、通过ip和端口和服务器建立连接(三次握手)
  • 3、获取html文档
  • 4、根据文档里面的链接(js、css、img)再重复以上过程

【注意】:发送报文的时候需要严格按照报文格式。如,最后需要连续两次换行、行末不能有空格等。

效果图:
你也可以写个服务器 - C# Socket学习2

用socket实现web服务器

web服务器的实现和我们上一篇的socket聊天服务端其实也差不多。
不同之处就在于,解析请求报文,然后按http协议回复标准的响应报文(我这里为了简单,就没有按标准的协议来玩,仅仅只是实现了表面的效果)
代码如下:

void ...()
{
    //1 创建socket对象
    socket socketserver = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp);

    //2 绑定ip和端口
    ipaddress ip = ipaddress.parse("127.0.0.1");
    ipendpoint ipendpoint = new ipendpoint(ip, 80);
    socketserver.bind(ipendpoint);

    //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
    socketserver.listen(10); 
    
    //阻塞等待客户端连接
    task.run(() => { accept(socketserver); });
}

//4 阻塞等待客户端连接
private static void accept(socket socketserver)
{
    while (true)
    {
        //阻塞等待客户端连接
        socket newsocket = socketserver.accept();
        task.run(() => { receive(newsocket); });
    }
}

//5 读取客户端发送过来的报文
private static void receive(socket newsocket)
{
    byte[] data = new byte[1024 * 1024];
    while (newsocket.connected)
    {
        //读取客户端发送过来的数据
        int readleng = newsocket.receive(data, 0, data.length, socketflags.none);
        //读取客户端发来的请求报文
        var requst = encoding.utf8.getstring(data, 0, readleng);
        
        //解析请求报文的请求路径(可以解析请求路径、请求文件、文件类型)
        var requstfile = requst.split("\r\n")[0].split(" ")[1];
        //回复客户端响应报文
        send(newsocket, requstfile);
    }
}

//6 回复客户端响应报文
private static void send(socket newsocket, string requstfile)
{
    //这里如果请求的根目录,默认显示index.html
    if (requstfile == "/" ) requstfile = "/index.html";

    var msg = file.readalltext(directory.getcurrentdirectory() + requstfile);
    //把消息内容转成字节数组后发送
    newsocket.send(encoding.utf8.getbytes(msg));
   
    //回复响应后马上关闭连接
    newsocket.shutdown(socketshutdown.both);
    newsocket.close();
}

效果如下:
你也可以写个服务器 - C# Socket学习2
你也可以写个服务器 - C# Socket学习2
由此我们知道了.net core为什么可以在不需要iis的情况下,一个黑窗体就提供了对网址的访问。其实也就是kestrelserver通过socket绑定并监听端口提供的服务。
【注意】:我们绑定的ip是127.0.0.1socketserver.bind(ipendpoint),所以我们测试的时候只能在浏览器输入127.0.0.1或者localhost。如果想通过内外ip访问,我们可以绑定任意ipipaddress.any。如socketserver.bind(new ipendpoint(ipaddress.any, port))

为什么不见三次握手

对于http/tcp可能大家多少都听过三次握手,可是在我们在用socket编写web服务器的时候并没有看到相关的东西啊,这是怎么回事。
因为我们在客户端执行连接socketclient.connect(ipendpoint)的时候已经进行了三次握手
你也可以写个服务器 - C# Socket学习2
具体可细读小坦克大佬的文章。
也就是说我们在用c#的socket、tcp、httpclient的时候根本就不用关注这些细节。
另外套接字有三种不同的类型:流套接字、数据报套接字和原始套接字。前两者是标准套接字,分别对应tcp和udp。而原始套接字则更加底层更加牛逼,普通开发人员一般接触不到。
我们说的http、tcp、udp之类都是网络协议,那协议到底是什么?通俗的说其实只是你我他之间的一个约定而已,大家都按规定了来那就可以说是协议。
而http又是建立在tcp之上的,也就是说基础协议之后再加约定又可以成为一种新的协议。下章我们将用socket来实现modbustcp协议对寄存器读和写。

结束