/*
写一个同步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);
}
}
}
运行结果:
这种一对一的方式,在实际开发中几乎是不存在,这只是一个概念,一个流程,接下来会有些难度了。恩恩,今天就这样了~~~
地方、