c#网络编程学习笔记02_Tcp编程(中)_简单的同步tcp聊天程序

/*

写一个同步tcp程序,功能为,客户端发送一个字符串给服务器,服务器将字符串打印,服务器再将字符串全部转化为大写字母,发送给客户端,然后客户端接受打印

*/

参考大牛地址:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part2.aspx


回忆一下编写服务端的一般步骤:

1.取得服务器的ip和端口号,创建TcpListener对象,调用Start方法开始监听。

2.利用TcpListener的AcceptTcpClient方法得到监听的客户端TcpClient对象,利用GetStream得到NetStream对象,即字节流。然后可选取其他流处理方法进行字符或者字节流处理。

3.进行通信。

4. 关闭流。关闭监听。

上代码:

服务器端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;


namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            const int BufferSize = 8192;
            Console.Write("Server is running!");
            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
            TcpListener listener = new TcpListener(ip, 8500);
            listener.Start();
            Console.WriteLine("服务器开始侦听");
            TcpClient remoteClient = listener.AcceptTcpClient();
            Console.WriteLine("Client conneted! {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退出
            Console.WriteLine("按下Q键退出");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}

客户端程序1:测试连接:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;


namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running:");
            TcpClient client = new TcpClient();

            try
            {
                client.Connect("localhost", 8500);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                
                throw;
            }
            Console.WriteLine("Server Conneted!{0} --- > {1}", client.Client.LocalEndPoint.ToString(), client.Client.RemoteEndPoint.ToString());
            Console.Read();
        }
    }
}

先运行服务器,再运行客户端发现:


连接成功!!,但是,,我们发现服务器端并没有执行流处理,即int bytesRead = streamToClient.Read(buffer, 0, BufferSize);说明Read方法是一个同步方法,这里没有接受字符串,发生了阻塞。我们接下来开始发送字符串,完善客户端程序。

客户端程序二:完善版

 <span style="white-space:pre">		</span>const int BufferSize = 8192;
            Console.Write("Server is running!");
            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
            TcpListener listener = new TcpListener(ip, 8500);
            listener.Start();
            Console.WriteLine("服务器开始侦听");
            TcpClient remoteClient = listener.AcceptTcpClient();
            Console.WriteLine("Client conneted! {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退出
            Console.WriteLine("按下Q键退出");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
                remoteClient.Close();
                streamToClient.Close();
            } while (key != ConsoleKey.Q);

运行结果:


恩。。看上去挺爽了,但是我在这里要强调两个问题!!!

问题1

记得在程序结束的时候关闭流,关闭连接。

重要的重要的重要的问题2:

我在写这段代码的时候脑袋中突然出现一个问题,在服务器和客户单进行连接后,服务器和客户端的代码都在向下执行,为什么客户端写入了数据后,服务器一定就能接收到呢?为什么不是服务器先接受然后为空?过了几分钟,我明白了,NetStream中的write和read都是同步方法,都会阻塞的。。(沃日...),严格来说,与AcceptTcpClient()方法类似,这个Read()方法也是同步的,只有当客户端发送数据的时候,服务端才会读取数据、运行此方法,否则它便会一直等待。。

问题三:这个程序只能实现一个客户端,并且只能发送一条信息。

这明显是不行的,我们如何实现一个客户端,多条消息呢?

 当我们需要一个服务端对同一个客户端的多次请求服务时,可以将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 conneted! {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);

然后启动多个客户端,结果:

下面说第三种,要稍微改动下客户端和服务器的代码:

服务端:

do
            {
                remoteClient = listener.AcceptTcpClient();
                Console.WriteLine("Client conneted! {0}----{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
                //获得流,并且写到buffer中
                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);

客户端:

     
<pre name="code" class="csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;


namespace Client3
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpClient client=null;
            NetworkStream streamToServer = null;
            ConsoleKey key;
client = new TcpClient();
            client.Connect("localhost", 8500);
            streamToServer = client.GetStream();
		do 
	{ 
		key = Console.ReadKey(true).Key;
		 try { if (key == ConsoleKey.S) 
		{ 
			Console.WriteLine("Input the message : ");
			 string msg = Console.ReadLine();
			 byte[] buffer = Encoding.Unicode.GetBytes(msg); 
			streamToServer.Write(buffer, 0, buffer.Length); 
			Console.WriteLine("send : {0}", msg); } } 
		catch (Exception e) 
		 Console.WriteLine(e.Message); 
		throw; 
		} 
		} while (key!=ConsoleKey.K); 
	} }}





运行结果:

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

/*-----------------------------------------------无敌分割线--------------------------------------------------*/

回到开始,完成那个程序。

客户端发送到服务端是搞定了,接下来搞定字符处理,并发送回客户端打印。

具体做法就是和客户端发送给服务器一样。除此之外,我们最好对流的操作加上lock(这是个什么东西)。

服务器:

 const int bufferSize = 8192;
            TcpListener listener;
            TcpClient client;
            NetworkStream stream;
            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
            
            try
            {
                listener = new TcpListener(ip, 8500);
                listener.Start();
                Console.WriteLine("开始监听");
                byte[] buffer = new byte[bufferSize];
                client = listener.AcceptTcpClient();
                stream = client.GetStream();
                lock (stream)
                {
                    int readnum = stream.Read(buffer, 0, bufferSize);
                }
                

                string msg = Encoding.Unicode.GetString(buffer);
                Console.WriteLine("Msg = {0}", msg);

                msg = msg.ToUpper();
                buffer = Encoding.Unicode.GetBytes(msg);
                lock (stream)
                {
                    stream.Write(buffer, 0, bufferSize);
                }
                
            }
            catch (Exception)
            {

                throw;
            }
            stream.Close();
            client.Close();
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key!=ConsoleKey.Q);
客户端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;


namespace Client
{
    class Program
    {
        //接下来我们编写客户端向服务器发送字符串的代码,与服务端类似,
        //他先获取连接服务器端的流,将字符串保存在缓存中,写入流这一个过程,相当于将消息发送服务端
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running:");
            TcpClient client =null;

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

            string msg = "\"What a Big Shit\"";
            NetworkStream streamToServer = client.GetStream();

            byte[] buffer = Encoding.Unicode.GetBytes(msg);
            lock (streamToServer)
            {
                streamToServer.Write(buffer, 0, buffer.Length);
            }
            
            Console.WriteLine("Sent: {0}", msg);

            lock (streamToServer)
            {
                streamToServer.Read(buffer, 0, buffer.Length);
            }
            msg = Encoding.Unicode.GetString(buffer);
            Console.WriteLine("转换后: {0}", msg);
            
            //按下q退出
            ConsoleKey key;
            Console.WriteLine("按下q退出");
            do
            {
                key = Console.ReadKey(true).Key;
                client.Close();
                streamToServer.Close();
            } while (key != ConsoleKey.Q);
        }
    }
}


运行结果:



这种一对一的方式,在实际开发中几乎是不存在,这只是一个概念,一个流程,接下来会有些难度了。恩恩,今天就这样了~~~

地方、

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章