C#网络编程(同步传输字符串) - Part.2

服务端客户端通信

在与服务端的连接建立以后,我们就可以通过此连接来发送和接收数据。端口与端口之间以流(Stream)的形式传输数据,因为几乎任何对象都可以保存到流中,所以实际上可以在客户端与服务端之间传输任何类型的数据。对客户端来说,往流中写入数据,即为向服务器传送数据;从流中读取数据,即为从服务端接收数据。对服务端来说,往流中写入数据,即为向客户端发送数据;从流中读取数据,即为从客户端接收数据。

同步传输字符串

我们现在考虑这样一个任务:客户端打印一串字符串,然后发往服务端,服务端先输出它,然后将它改为大写,再回发到客户端,客户端接收到以后,最后再次打印一遍它。我们将它分为两部分:1、客户端发送,服务端接收并输出;2、服务端回发,客户端接收并输出。

1.客户端发送,服务端接收并输出

1.1服务端程序

我们可以在TcpClient上调用GetStream()方法来获得连接到远程计算机的流。注意这里我用了远程这个词,当在客户端调用时,它得到连接服务端的流;当在服务端调用时,它获得连接客户端的流。接下来我们来看一下代码,我们先看服务端(注意这里没有使用do/while循环):

class Server {
    static void Main(string[] args) {
        const int BufferSize = 8192;    // 缓存大小,8192字节
                   
        Console.WriteLine("Server is running ... ");
        IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
        TcpListener listener = new TcpListener(ip, 8500);

        listener.Start();           // 开始侦听
        Console.WriteLine("Start Listening ...");
       
        // 获取一个连接,中断方法
        TcpClient remoteClient = listener.AcceptTcpClient();
        // 打印连接到的客户端信息
        Console.WriteLine("Client Connected!{0} <-- {1}",
            remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);

        // 获得流,并写入buffer中
        NetworkStream streamToClient = remoteClient.GetStream();
        byte[] buffer = new byte[BufferSize];
        int bytesRead = streamToClient.Read(buffer, 0, BufferSize);
        Console.WriteLine("Reading data, {0} bytes ...", bytesRead);

        // 获得请求的字符串
        string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
        Console.WriteLine("Received: {0}", msg);

        // 按Q退出
    }
}

这段程序的上半部分已经很熟悉了,我就不再解释。remoteClient.GetStream()方法获取到了连接至客户端的流,然后从流中读出数据并保存在了buffer缓存中,随后使用Encoding.Unicode.GetString()方法,从缓存中获取到了实际的字符串。最后将字符串打印在了控制台上。这段代码有个地方需要注意:在能够读取的字符串的总字节数大于BufferSize的时候会出现字符串截断现象,因为缓存中的数目总是有限的,而对于大对象,比如说图片或者其它文件来说,则必须采用“分次读取然后转存”这种方式,比如这样:

// 获取字符串
byte[] buffer = new byte[BufferSize];
int bytesRead;          // 读取的字节数
MemoryStream msStream = new MemoryStream();
do {
    bytesRead = streamToClient.Read(buffer, 0, BufferSize);
    msStream.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);

buffer = msStream.GetBuffer();
string msg = Encoding.Unicode.GetString(buffer);

这里我没有使用这种方法,一个是因为不想关注在太多的细节上面,一个是因为对于字符串来说,8192字节已经很多了,我们通常不会传递这么多的文本。当使用Unicode编码时,8192字节可以保存4096个汉字和英文字符。使用不同的编码方式,占用的字节数有很大的差异,在本文最后面,有一段小程序,可以用来测试Unicode、UTF8、ASCII三种常用编码方式对字符串编码时,占用的字节数大小。

现在对客户端不做任何修改,然后运行先运行服务端,再运行客户端。结果我们会发现这样一件事:服务端再打印完“Client Connected!127.0.0.1:8500 <-- 127.0.0.1:xxxxx”之后,再次被阻塞了,而没有输出“Reading data, {0} bytes ...”。可见,与AcceptTcpClient()方法类似,这个Read()方法也是同步的,只有当客户端发送数据的时候,服务端才会读取数据、运行此方法,否则它便会一直等待。

1.2 客户端程序

接下来我们编写客户端向服务器发送字符串的代码,与服务端类似,它先获取连接服务器端的流,将字符串保存到buffer缓存中,再将缓存写入流,写入流这一过程,相当于将消息发往服务端。

class Client {
    static void Main(string[] args) {
        Console.WriteLine("Client Running ...");
        TcpClient client;

        try {
            client = new TcpClient();
            client.Connect("localhost", 8500);      // 与服务器连接
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return;
        }
        // 打印连接到的服务端信息
        Console.WriteLine("Server Connected!{0} --> {1}",
            client.Client.LocalEndPoint, client.Client.RemoteEndPoint);

        string msg = "\"Welcome To TraceFact.Net\"";
        NetworkStream streamToServer = client.GetStream();

        byte[] buffer = Encoding.Unicode.GetBytes(msg);     // 获得缓存
        streamToServer.Write(buffer, 0, buffer.Length);     // 发往服务器
        Console.WriteLine("Sent: {0}", msg);

        // 按Q退出
    }
}

现在再次运行程序,得到的输出为:

// 服务端
Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:7847
Reading data, 52 bytes ...
Received: "Welcome To TraceFact.Net"
输入"Q"键退出。
// 客户端
Client Running ...
Server Connected!127.0.0.1:7847 --> 127.0.0.1:8500
Sent: "Welcome To TraceFact.Net"
输入"Q"键退出。

再继续进行之前,我们假设客户端可以发送多条消息,而服务端要不断的接收来自客户端发送的消息,但是上面的代码只能接收客户端发来的一条消息,因为它已经输出了“输入Q键退出”,说明程序已经执行完毕,无法再进行任何动作。此时如果我们再开启一个客户端,那么出现的情况是:客户端可以与服务器建立连接,也就是netstat-a显示为ESTABLISHED,这是操作系统所知道的;但是由于服务端的程序已经执行到了最后一步,只能输入Q键退出,无法再采取任何的动作。

回想一个上面我们需要一个服务器对应多个客户端时,对AcceptTcpClient()方法的处理办法,将它放在了do/while循环中;类似地,当我们需要一个服务端对同一个客户端的多次请求服务时,可以将Read()方法放入到do/while循环中

现在,我们大致可以得出这样几个结论:

  • 如果不使用do/while循环,服务端只有一个listener.AcceptTcpClient()方法和一个TcpClient.GetStream().Read()方法,则服务端只能处理到同一客户端的一条请求。
  • 如果使用一个do/while循环,并将listener.AcceptTcpClient()方法和TcpClient.GetStream().Read()方法都放在这个循环以内,那么服务端将可以处理多个客户端的一条请求。
  • 如果使用一个do/while循环,并将listener.AcceptTcpClient()方法放在循环之外,将TcpClient.GetStream().Read()方法放在循环以内,那么服务端可以处理一个客户端的多条请求。
  • 如果使用两个do/while循环,对它们进行分别嵌套,那么结果是什么呢?结果并不是可以处理多个客户端的多条请求。因为里层的do/while循环总是在为一个客户端服务,因为它会中断在TcpClient.GetStream().Read()方法的位置,而无法执行完毕。即使可以通过某种方式让里层循环退出,比如客户端往服务端发去“exit”字符串时,服务端也只能挨个对客户端提供服务。如果服务端想执行多个客户端的多个请求,那么服务端就需要采用多线程。主线程,也就是执行外层do/while循环的线程,在收到一个TcpClient之后,必须将里层的do/while循环交给新线程去执行,然后主线程快速地重新回到listener.AcceptTcpClient()的位置,以响应其它的客户端。

对于第四种情况,实际上是构建一个服务端更为通常的情况,所以需要专门开辟一个章节讨论,这里暂且放过。而我们上面所做的,即是列出的第一种情况,接下来我们再分别看一下第二种和第三种情况。

对于第二种情况,我们按照上面的叙述先对服务端进行一下改动:

do {
    // 获取一个连接,中断方法
    TcpClient remoteClient = listener.AcceptTcpClient();
    // 打印连接到的客户端信息
    Console.WriteLine("Client Connected!{0} <-- {1}",
        remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);

    // 获得流,并写入buffer中
    NetworkStream streamToClient = remoteClient.GetStream();
    byte[] buffer = new byte[BufferSize];
    int bytesRead = streamToClient.Read(buffer, 0, BufferSize);
    Console.WriteLine("Reading data, {0} bytes ...", bytesRead);

    // 获得请求的字符串
    string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
    Console.WriteLine("Received: {0}", msg);
} while (true);

然后启动多个客户端,在服务端应该可以看到下面的输出(客户端没有变化):

Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:8196
Reading data, 52 bytes ...
Received: "Welcome To TraceFact.Net"
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:8199
Reading data, 52 bytes ...
Received: "Welcome To TraceFact.Net"

由第2种情况改为第3种情况,只需要将do向下挪动几行就可以了:

// 获取一个连接,中断方法
TcpClient remoteClient = listener.AcceptTcpClient();
// 打印连接到的客户端信息
Console.WriteLine("Client Connected!{0} <-- {1}",
    remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
// 获得流,并写入buffer中
NetworkStream streamToClient = remoteClient.GetStream();

do {
    byte[] buffer = new byte[BufferSize];
    int bytesRead = streamToClient.Read(buffer, 0, BufferSize);
    Console.WriteLine("Reading data, {0} bytes ...", bytesRead);

    // 获得请求的字符串
    string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
    Console.WriteLine("Received: {0}", msg);
} while (true);

然后我们再改动一下客户端,让它发送多个请求。当我们按下S的时候,可以输入一行字符串,然后将这行字符串发送到服务端;当我们输入X的时候则退出循环:

NetworkStream streamToServer = client.GetStream();
ConsoleKey key;
Console.WriteLine("Menu: S - Send, X - Exit");
do {
    key = Console.ReadKey(true).Key;

    if (key == ConsoleKey.S) {
        // 获取输入的字符串
        Console.Write("Input the message: ");
        string msg = Console.ReadLine();

        byte[] buffer = Encoding.Unicode.GetBytes(msg);     // 获得缓存
        streamToServer.Write(buffer, 0, buffer.Length);     // 发往服务器
        Console.WriteLine("Sent: {0}", msg);
    }
} while (key != ConsoleKey.X);

接下来我们先运行服务端,然后再运行客户端,输入一些字符串,来进行测试,应该能够看到下面的输出结果:

// 服务端
Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:11004
Reading data, 44 bytes ...
Received: 欢迎访问我的博客:TraceFact.Net
Reading data, 14 bytes ...
Received: 我们一起进步!
//客户端
Client Running ...
Server Connected!127.0.0.1:11004 --> 127.0.0.1:8500
Menu: S - Send, X - Exit
Input the message: 欢迎访问我的博客:TraceFact.Net
Sent: 欢迎访问我的博客:TraceFact.Net
Input the message: 我们一起进步!
Sent: 我们一起进步!

这里还需要注意一点,当客户端在TcpClient实例上调用Close()方法,或者在流上调用Dispose()方法,服务端的streamToClient.Read()方法会持续地返回0,但是不抛出异常,所以会产生一个无限循环;而如果直接关闭掉客户端,或者客户端执行完毕但没有调用stream.Dispose()或者TcpClient.Close(),如果服务器端此时仍阻塞在Read()方法处,则会在服务器端抛出异常:“远程主机强制关闭了一个现有连接”。因此,我们将服务端的streamToClient.Read()方法需要写在一个try/catch中。同理,如果在服务端已经连接到客户端之后,服务端调用remoteClient.Close(),则客户端会得到异常“无法将数据写入传输连接: 您的主机中的软件放弃了一个已建立的连接。”;而如果服务端直接关闭程序的话,则客户端会得到异常“无法将数据写入传输连接: 远程主机强迫关闭了一个现有的连接。”。因此,它们的读写操作必须都放入到try/catch块中。

2.服务端回发,客户端接收并输出

2.2服务端程序

我们接着再进行进一步处理,服务端将收到的字符串改为大写,然后回发,客户端接收后打印。此时它们的角色和上面完全进行了一下对调:对于服务端来说,就好像刚才的客户端一样,将字符串写入到流中;而客户端则同服务端一样,接收并打印。除此以外,我们最好对流的读写操作加上lock,现在我们直接看代码,首先看服务端:

class Server {
    static void Main(string[] args) {
        const int BufferSize = 8192;    // 缓存大小,8192Bytes
        ConsoleKey key;

        Console.WriteLine("Server is running ... ");
        IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
        TcpListener listener = new TcpListener(ip, 8500);

        listener.Start();           // 开始侦听
        Console.WriteLine("Start Listening ...");

        // 获取一个连接,同步方法,在此处中断
        TcpClient remoteClient = listener.AcceptTcpClient();

        // 打印连接到的客户端信息
        Console.WriteLine("Client Connected!{0} <-- {1}",
            remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);

        // 获得流
        NetworkStream streamToClient = remoteClient.GetStream();
       
        do {
            // 写入buffer中
            byte[] buffer = new byte[BufferSize];
            int bytesRead;
            try {
                lock(streamToClient){
                    bytesRead = streamToClient.Read(buffer, 0, BufferSize);
                }
                if (bytesRead == 0) throw new Exception("读取到0字节");
                Console.WriteLine("Reading data, {0} bytes ...", bytesRead);

                // 获得请求的字符串
                string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
                Console.WriteLine("Received: {0}", msg);

                // 转换成大写并发送
                msg = msg.ToUpper();                   
                buffer = Encoding.Unicode.GetBytes(msg);
                lock(streamToClient){
                    streamToClient.Write(buffer, 0, buffer.Length);
                }
                Console.WriteLine("Sent: {0}", msg);
            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
                break;
            }                          
        } while (true);

        streamToClient.Dispose();
        remoteClient.Close();
       
        Console.WriteLine("\n\n输入\"Q\"键退出。");
        do {
            key = Console.ReadKey(true).Key;
        } while (key != ConsoleKey.Q);
    }
}

接下来是客户端:

class Client {
    static void Main(string[] args) {
        Console.WriteLine("Client Running ...");
        TcpClient client;
        ConsoleKey key;
        const int BufferSize = 8192;

        try {
            client = new TcpClient();
            client.Connect("localhost", 8500);      // 与服务器连接
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return;
        }

        // 打印连接到的服务端信息
        Console.WriteLine("Server Connected!{0} --> {1}",
            client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
                   
        NetworkStream streamToServer = client.GetStream();         
        Console.WriteLine("Menu: S - Send, X - Exit");

        do {
            key = Console.ReadKey(true).Key;

            if (key == ConsoleKey.S) {
                // 获取输入的字符串
                Console.Write("Input the message: ");
                string msg = Console.ReadLine();

                byte[] buffer = Encoding.Unicode.GetBytes(msg);     // 获得缓存
                try {
                    lock(streamToServer){
                        streamToServer.Write(buffer, 0, buffer.Length);     // 发往服务器
                    }
                    Console.WriteLine("Sent: {0}", msg);

                    int bytesRead;
                    buffer = new byte[BufferSize];                     
                    lock(streamToServer){
                        bytesRead = streamToServer.Read(buffer, 0, BufferSize);
                    }
                    msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
                    Console.WriteLine("Received: {0}", msg);

                } catch (Exception ex) {
                    Console.WriteLine(ex.Message);
                    break;
                }
            }
        } while (key != ConsoleKey.X);

        streamToServer.Dispose();
        client.Close();

        Console.WriteLine("\n\n输入\"Q\"键退出。");
        do {
            key = Console.ReadKey(true).Key;
        } while (key != ConsoleKey.Q);
    }
}

最后我们运行程序,然后输入一串英文字符串,然后看一下输出:

// 客户端
Client is running ...
Server Connected!127.0.0.1:12662 --> 127.0.0.1:8500
Menu: S - Send, X - Exit
Input the message: Hello, I'm jimmy zhang.
Sent: Hello, I'm jimmy zhang.
Received: HELLO, I'M JIMMY ZHANG.

// 服务端
Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:12662
Reading data, 46 bytes ...
Received: Hello, I'm jimmy zhang.
Sent: HELLO, I'M JIMMY ZHANG.

看到这里,我想你应该对使用TcpClient和TcpListener进行C#网络编程有了一个初步的认识,可以说是刚刚入门了,后面的路还很长。本章的所有操作都是同步操作,像上面的代码也只是作为一个入门的范例,实际当中,一个服务端只能为一个客户端提供服务的情况是不存在的,下面就让我们来看看上面所说的第四种情况,如何进行异步的服务端编程。

附录:ASCII、UTF8、Uncicode编码下的中英文字符大小

private static void ShowCode() {
    string[] strArray = { "b", "abcd", "乙", "甲乙丙丁" };
    byte[] buffer;
    string mode, back;

    foreach (string str in strArray) {

        for (int i = 0; i <= 2; i++) {
            if (i == 0) {
                buffer = Encoding.ASCII.GetBytes(str);
                back = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
                mode = "ASCII";
            } else if (i == 1) {
                buffer = Encoding.UTF8.GetBytes(str);
                back = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
                mode = "UTF8";
            } else {
                buffer = Encoding.Unicode.GetBytes(str);
                back = Encoding.Unicode.GetString(buffer, 0, buffer.Length);
                mode = "Unicode";
            }

            Console.WriteLine("Mode: {0}, String: {1}, Buffer.Length: {2}",
                mode, str, buffer.Length);

            Console.WriteLine("Buffer:");
            for (int j = 0; j <= buffer.Length - 1; j++) {
                Console.Write(buffer[j] + " ");
            }

            Console.WriteLine("\nRetrived: {0}\n", back);
        }
    }
}

输出为:

Mode: ASCII, String: b, Buffer.Length: 1
Buffer: 98
Retrived: b

Mode: UTF8, String: b, Buffer.Length: 1
Buffer: 98
Retrived: b

Mode: Unicode, String: b, Buffer.Length: 2
Buffer: 98 0
Retrived: b

Mode: ASCII, String: abcd, Buffer.Length: 4
Buffer: 97 98 99 100
Retrived: abcd

Mode: UTF8, String: abcd, Buffer.Length: 4
Buffer: 97 98 99 100
Retrived: abcd

Mode: Unicode, String: abcd, Buffer.Length: 8
Buffer: 97 0 98 0 99 0 100 0
Retrived: abcd

Mode: ASCII, String: 乙, Buffer.Length: 1
Buffer: 63
Retrived: ?

Mode: UTF8, String: 乙, Buffer.Length: 3
Buffer: 228 185 153
Retrived: 乙

Mode: Unicode, String: 乙, Buffer.Length: 2
Buffer: 89 78
Retrived: 乙

Mode: ASCII, String: 甲乙丙丁, Buffer.Length: 4
Buffer: 63 63 63 63
Retrived: ????

Mode: UTF8, String: 甲乙丙丁, Buffer.Length: 12
Buffer: 231 148 178 228 185 153 228 184 153 228 184 129
Retrived: 甲乙丙丁

Mode: Unicode, String: 甲乙丙丁, Buffer.Length: 8
Buffer: 50 117 89 78 25 78 1 78
Retrived: 甲乙丙丁

大体上可以得出这么几个结论:

  • ASCII不能保存中文(貌似谁都知道=_-`)。
  • UTF8是变长编码。在对ASCII字符编码时,UTF更省空间,只占1个字节,与ASCII编码方式和长度相同;Unicode在对ASCII字符编码时,占用2个字节,且第2个字节补零。
  • UTF8在对中文编码时需要占用3个字节;Unicode对中文编码则只需要2个字节。

转载于:https://www.cnblogs.com/starspace/archive/2009/01/19/1378552.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Factory Method工厂方法

“对象创建“模式 通过”对象创建“模式绕开new&#xff0c;来避免对象创建(new)过程中所导致的紧耦合&#xff08;以来具体类&#xff09;&#xff0c;从而支持对象创建的稳定。它是接口抽象之后的第一部工作。 典型模式&#xff1a;Factory Method&#xff0c;Abstract Facto…

centos 关闭防火墙_CentOS7操作系统下如何关闭防火墙

centos系统如果不关闭防火墙在使用中会遇到不少问题&#xff0c;而且centos7和centos6关闭防火墙的方式不一样。centos6:1.永久性生效&#xff0c;重启后不会复原开启&#xff1a; chkconfig iptables on关闭&#xff1a; chkconfig iptables off2.即时生效&#xff0c;重启后复…

web 网页按比例显示图片 js

原文链接&#xff1a;http://blog.csdn.net/liqinghuiyx/article/details/5442349 在动态站点上经常需要上传自己的图片&#xff0c;而这些图片的大小是未知的&#xff0c;在显示成缩略图的时候必须进行按比例的缩放才能美观地显示。以最近做的golf网站&#xff08;http://www…

黑马C++设计模式1

设计模式的基础是&#xff1a;多态。 设计模式综览表&#xff1a; 单例模式&#xff1a;是保证一个类仅有一个实例&#xff0c;并提供一个访问它的全局访问点。 简单工厂模式&#xff1a;通过专门顶一个一个类来负责创建其它类的实例&#xff0c;被创建的实例通常都具有共同的父…

对于未来的一点思考

最近在思考一个问题&#xff1a;以后的发展路线。   自己算是走上了IT的道路&#xff0c;但现在也只是在程序员阶段&#xff0c;当然还未毕业&#xff0c;以后的路还很长&#xff0c;但是这个问题确是现在或以后不得不面对的一个问题。  上学期未那两个月&#xff0c;去了N…

深入解析react关于事件绑定this的四种方式

这篇文章主要介绍了详解react关于事件绑定this的四种方式&#xff0c;写的十分的全面细致&#xff0c;具有一定的参考价值&#xff0c;对此有需要的朋友可以参考学习下。如有不足之处&#xff0c;欢迎批评指正。 在react组件中&#xff0c;每个方法的上下文都会指向该组件的实例…

Apache的认证、授权、访问控制

原文链接&#xff1a; http://man.chinaunix.net/newsoft/Apache2.2_chinese_manual/howto/auth.html Apache认证、授权、访问控制 认证(Authentication)是指任何识别用户身份的过程。授权(Authorization)是允许特定用户访问特定区域或信息的过程。 相关模块和指令 认证和授权…

黑马C++设计模式2

简单工厂模式 //一般来说&#xff0c;自己创建一个对象的方法是在自己写的业务函数中直接new一个对象出来//但是现实需求&#xff0c;我不想创建对象&#xff0c;我只想拿来用。&#xff08;创建类的步骤比较复杂&#xff09; //好处&#xff0c;1、客户端和具体实现类解耦。2…

[转]Struts 2.1发布

作者 Ian Roughley译者 崔康 发布于 2009年2月4日 上午8时13分 Struts2框架刚刚发布最新2.1版。该版本做了重大升级&#xff0c;包括重构更多代码到插件框架、通过增加convention插件减少XML配置和改进REST支持。 我采访了Musachy Barroso——该版本的一位开发人员&#xff0c…

dim private public static_PHP中const,static,public,private,protected的区别

const: 定义常量&#xff0c;一般定义后不可改变static: 静态&#xff0c;类名可以访问public: 表示全局&#xff0c;类内部外部子类都可以访问&#xff1b;private: 表示私有的&#xff0c;只有本类内部可以使用&#xff1b;protected: 表示受保护的&#xff0c;只有本类或子类…

C#图解教程 第六章 深入理解类

深入理解类 类成员 前两章阐述了9种类成员中的两种&#xff1a;字段和方法。本章将会介绍除事件(第14章)和运算符外的其他类成员&#xff0c;并讨论其特征。 成员修饰符的顺序 字段和方法的声明可以包括许多如public、private这样的修饰符。本章还会讨论许多其他修饰符。多个修…

Apache用户身份验证

原文链接&#xff1a;http://www.yylog.org/?p4830 Apache用户身份验证 在apache应用过程中&#xff0c;管理员经常需要对apache下的目录做一些限制&#xff0c;不希望所有用户都能访问该目录下的文件&#xff0c;只对指定用户访问&#xff0c;此时我们就要用到apache用户身…

携程elong相继牵手支付宝转“危”为“机”

新华网浙江频道1月16日电 自电子机票全面普及以来&#xff0c;航空公司机票直销的力度不断加强正给传统的机票代理甚至在线旅游平台带来了极大的生存压力。 而面对危机&#xff0c;在进一步丰富自身产品服务之外&#xff0c;大的在线旅行平台也终于找到对策。继eLong此前与支付…

c# 获取word表格中的内容_Java 获取、删除Word文本框中的表格

本文介绍如何来获取Word文本框中包含的表格&#xff0c;以及删除表格。程序测试环境包括&#xff1a;IDEAJDK 1.8.0Spire.Doc.jar注&#xff1a;jar导入&#xff0c;可通过创建Maven程序项目&#xff0c;并在pom.xml中配置Maven仓库路径&#xff0c;并指定Free Spire.Doc for J…

Array.prototype.reduce 的理解与实现

Array.prototype.reduce 是 JavaScript 中比较实用的一个函数&#xff0c;但是很多人都没有使用过它&#xff0c;因为 reduce 能做的事情其实 forEach 或者 map 函数也能做&#xff0c;而且比 reduce 好理解。但是 reduce 函数还是值得去了解的。 reduce 函数可以对一个数组进行…

PS抠图方法[photoshop中文教程]

PS抠图方法 一、魔术棒法——最直观的方法   适用范围&#xff1a;图像和背景色色差明显&#xff0c;背景色单一&#xff0c;图像边界清晰。   方法意图&#xff1a;通过删除背景色来获取图像。   方法缺陷&#xff1a;对散乱的毛发没有用。   使用方法&#xff1a…

我的核心技术都是从哪里学到的?如何提高成长的?分享给大家。

1997年&#xff0c;我在读黑龙江大学读大二时&#xff0c;我认识了一个内蒙古大学计算机专业毕业的一个高材生&#xff0c;那时我那朋友引导了我很多&#xff0c;他那时候在我们家那边开一个IT公司&#xff0c;他知道如何靠IT技术赚钱&#xff0c;如何靠程序等赚钱&#xff0c;…

python线性回归算法简介_Python实现的简单线性回归算法实例分析

本文实例讲述了Python实现的简单线性回归算法。分享给大家供大家参考&#xff0c;具体如下&#xff1a; 用python实现R的线性模型(lm)中一元线性回归的简单方法&#xff0c;使用R的women示例数据&#xff0c;R的运行结果&#xff1a; > summary(fit) Call: lm(formula weig…

Object/Relation Mapping 对象关系映射

对象-关系映射&#xff08;Object/Relation Mapping&#xff0c;简称ORM&#xff09;&#xff0c;是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法&#xff0c;关系数据库是企业级应用环境中永久存放数据的主流数据存储…

FastReport使用方法(C/S版)

前言 这两天群里一直有群友问一些关于FastReport的问题&#xff0c;结合他们的问题&#xff0c;在这里做一个整理&#xff0c;有不明白的可以加 FastReport 交流群 群 号&#xff1a;554714044 工具 VS2017 FastReport 开始 1.新建项目&#xff0c;添加三个按钮。预览、设计、…