c#服务器全面讲解与制作一

            环境配置与基础架构

  • 环境配置

  • 基础的服务器架构

 

 

这里我会讲解高级的c#服务器的全面制作流程

会对大家有很大的帮助

不过在这个教程中主要是讲解服务器的制作,所以不会讲解客户端的制作,不过会提供相关客户端的代码。

 

1 环境配置

1.1 vs code环境配置

  如果你觉得用visual studio来写代码是一件很酷的事情,那么可以直接略过这个部分,到下一个安装visual studio 2019的部分

  我们在开发之前需要先配置开发环境,由于这里使用的是.net core来进行开发,所以先在下载.net core的sdk,我这里用的是.net core2.2的开发环境

  

   下载完成后双击安装就行啦,我觉得这个就不用教了吧。。。

   那么就进入下一步,到下载vs code,什么!vs code是哪个???看下图即可

  

 

   同理下载后安装即可

  接着就是对c#的支持了,虽然vs code可以支持很多种语言,但不代表下载之后就有这么高超的能力,我们还需要配置一波

  

 

   是不是就配置好了呢,对的呢,下来就是很厉害的一部分了,在vs code中对终端的操作需要熟悉一些才行,下来会讲解以下如何新建一个.net core的项目,会用到很多命令哦

  不过不用太过担心,毕竟只是一些很简单的命令

  首先,我们创建一个文件夹,emm。。。是在win的文件管理器中按下ctrl + shift + n创建的哪种,嗯?为什么不用命令?这个问题问的好,其实你会用终端的话不需要我告诉你你就会了的,就是mkdir <文件夹名>嘛

  既然这么想学那我就顺带提一下,如果想要召唤终端出来需要学习一个简单的召唤术,这个其实很简单,ctrl + ~就能召唤出来啦

   

 

   下面的哪个terminal就是终端啦,如果你用了汉化包就当我什么都没说,因为汉化过来就叫终端

  下来我来解析一下(推了推眼镜)

 

   红色部分就是当前文件的位置了,这里给一些简单的指令,输入进去之后点击回车(enter)就能执行啦 

cd <dirname> //进入名字为<dirname>的文件夹 夹全名其实是change directory
cd ./     //进入当前目录。。。不要问我为什么会有这种指令,它的存在在某些时刻很有意义,这里就不详解了
cd ../     //返回上级目录

mkdir <dirname> 创建一个文件夹

  所以在这里我们先使用mkdir来新建一个文件夹,如果你是ctrl + k + o或是直接在外面新建一个文件夹文件夹拖进来就当我什么都没说

  这里我们新建一个名字叫myserver的文件夹

  

 

  这样就新建成功了,下来使用cd进入我们新建的文件夹

  

   这样我们就可以在这个文件夹里面部署自己的项目啦

  不过怎么新建一个项目呢,这里我们可以使用dotnet new console来新建一个控制台(console)项目

  

 

  看看左边的文件树,可以看到出现了很多文件

  

 

   如果没有怎么办呢?那就去文件夹的地址把文件夹拖进来就行啦

  如果你直接输入dotnet new会列出很多项目,可以按自己的需求选择,这里就不演示了

  不过你以为这样就完了?你可以输入dotnet run来试试运行这个项目,虽然不知道为什么我这里运行成功了,如果运行不成功的话记得输入dotnet restore来修复一下项目

  

 

   

 

   这样我们的项目就创建完成啦,在左边的文件树点击一下program.cs就能在视窗看到代码了

   

 1.2 visual studio 2019环境配置

   点击这里下载visual studio 2019的安装包

   

 

  对于我们的开发其实个人版就已经足够了,打开其实是一个installer,安装好2019版本之后启动installer

  

 

 

 

   点击修改进入配置

  

 

 

   选择.net core跨平台开发

  

 

 

   最后点击右下角的修改就行了,默默等待安装,装完就能启动啦。

  

2 服务器的基础架构

2.1 配置服务器

   首先定义一个startserver方法来写入启动服务器的代码

namespace myserver
{
    class program
    {
        static void main(string[] args)
        {
            startserver();
        }

        static void startserver(){
            
        }
    }
}

 

  这里暂时先这么写,到后期会慢慢向外展开,目前是初始阶段不适合一开始就弄一个类出来

  为了不让startserver运行结束后程序结束,我们添加一个while循环,接收服务端的输入,如果输入了exit就代表服务器该结束了,我们就跳出这个循环

   不过为了服务器的运行不那么莫名其妙,所以在初始化完成后输出一个成功信息并给出一些友好的提示

        static void main(string[] args)
        {
            startserver();
            console.writeline("服务器初始化完毕,输入exit结束服务");
            while (true) {
                string msg = console.readline(); 
                if (msg.equals("exit")) { 
            break; } } }

 

我是如何解释这些代码的

  如果是修改过的部分会被标记出来 如 新代码

  如果是被被删除的的代码,会被划掉被标记出来 不需要的代码

  如果一个方法里代码过多 会用 … 来代表被省略的代码

  下来就是服务器运行相关的代码了

  这里我们需要创建一个套接字(socket),里面储存的主要是一个地址和一些传输的协议,一般情况下<socket>.xxx(这里的<socket>代表socket类型的变量)代表让封装的地址做xxx或是对封装的地址做xxx,不过这些都是下来说的了,这里就简单的了解一下就行

  首先,我们要使用ipv4的网络协议,也就是常用的ip地址进行链接,例如本机的ipv4地址是127.0.0.1

  然后使用流形式的数据传输,这样主要是可以保证数据的传输顺序,不会出现后发的数据跑到前面去,不然发个”你好“别人接受到是”好你“就不对劲了,而且tcp协议用的就是stream的数据形式,用其他不匹配的信息方式是会报错的。

  最后就是使用tcp协议了,这里就默认大家对tcp协议已有过了解,毕竟网上对tcp的讲解一大堆,这里就不赘述了。

        static void startserver() {
            //                                使用ipv4             使用流形式的数据传输      使用tcp协议
            socket server = new socket(addressfamily.internetwork,sockettype.stream,protocoltype.tcp);
        }

  

  下来就是定义一个绑定的ip:port,这里我们叫做地址,区别于ip地址,下来客户端可以通过这个地址访问到我们的服务器,这里我们绑定到本地的8989端口,毕竟正常情况下不可能去绑定别人的ip地址开服务器,至少在这里不是,就用固定的127.0.0.1就行了。

  首先定义一个ipaddress来指定ip地址,然后创建一个ipendpoint来指定我们服务器即将绑定的端口

 

        static void startserver() {
            //                        使用ipv4                  使用流形式的数据传输      使用tcp协议
            socket server = new socket(addressfamily.internetwork,sockettype.dgram,protocoltype.tcp);

       ipaddress ipaddress = ipaddress.parse("127.0.0.1");
       ipendpoint ipendpoint = new ipendpoint(ipaddress, 8989);
        }

 

  下来就是绑定这个地址了,这里使用<socket>.bind(ip:port)来绑定一个地址,让我们的客户端通过这个地址来访问我们的服务器

  接着使用<socket>.listen(<int num>)来定义我们的服务器最多可以连接多少个客户,这里我们定义的是10,如果连接的客户达到10那么将会被拒绝其它客户的连接请求

 

        static void startserver() {
            ...
            ipendpoint ipendpoint = new ipendpoint(ipaddress, 8989);
        server.bind(ipendpoint);
        server.listen(10);
          }    

 

  不过这样写代码未免有些太多,我们将代码简化一些,

    

        static void startserver() {
       ...
            ipaddress ipaddress = ipaddress.parse("127.0.0.1");
       ipendpoint ipendpoint = new ipendpoint(ipaddress, 8989);
            server.bind(new ipendpoint(ipaddress.parse("127.0.0.1"), 8989));
            server.listen(10);
        }

 

    这样我们的服务器配置工作就完成啦

 

2.2 接收用户

  现在服务器的初始化已经完成了

  接着就该是让我们的服务器接收用户的时候了

  简单的调用<socket>.beginaccept(<asynccallback func>,<object param>)来开启一个异步的用户接收,这里的异步接收可以理解为开了一个线程,不过异步不一定就是多线程,在用户接入的时候会调用asynccallback委托类型的回调函数func,如果有参数传递的需要可以传递一个参数param到回调函数中。

  这里我们先这么写,假设我们有一个不存在的方法acceptcallback,而且我们将创建的server传递过去

 

        static void startserver() {
            ...
            server.listen(10);

            server.beginaccept(acceptcallback,server);
        }

 

  如果你用的是visual studio,那么很幸运的是,你可以点击一下这个不存在的方法的名字,使用ctrl + .来自动生成这个方法,如果是vs code的话可能需要自己写,毕竟我没怎么用过,也不是很清楚。

  这个方法需要一个iasyncresult类型的参数,里面包含了异步请求的数据,包括我们之前传递过来的<socket server>变量

 

        static void startserver() {...}

        private static void acceptcallback(iasyncresult ar) {
     }

  

  这里我们获取一下传递过来的<socket server>变量,实际上传递的变量都封装在了<iasyncresult>.asyncstate里面

  

        private static void acceptcallback(iasyncresult ar) {
            socket server = ar.asyncstate as socket;
        }

 

as是如何运作的?

as在c#中是强制转换的一个变体,如果能转换到对应的class则返回class状态的变量,否则返回null,正常写法中可以用下图表示。

 

 

 

 

 

 

  用户接入后<socket server>应当结束接收来获取接入的客户端套接字,这样我们就接受到用户啦,为了给我们一个提示,所以简单的输出用户接入即可

  结束接收的方法需要将异步信息传入才能读取出接入的用户

  

  

        private static void acceptcallback(iasyncresult ar) {
            socket server = ar.asyncstate as socket;

            socket client = server.endaccept(ar);
       console.writeline($"用户{client.addressfamily}接入");
        }

 

$的工作原理?

上面$”用户{client.addressfamily}“代码可以替换为 “接收到用户信息” + client.addressfamily

$是c#中的一个语法糖,在java中可以用 string.format来达到目的 如string.format("接收到用户信息:%s", msg);
在c#中是使用string.format("接收到用户信息:{0}",msg)

$即是一个合并字符串的简写方法

 

  下一篇会讲解服务器欢迎语的发送

  

 3 最后。。。

  现在我们服务器的基础部分就已经完成啦,下面给一个全局图方便观看

  

 

 

   接着就是客户端的代码了

 

using system;
using system.net.sockets;
using system.net;

namespace tcp客户端
{
    class program
    {
        static void main(string[] args)
        {
            socket clientsocket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp);
            clientsocket.connect(new ipendpoint(ipaddress.parse("127.0.0.1"), 8899));
            console.writeline("press any key to continue");
            console.read();
            clientsocket.close();
        }
    }
}