C#网络编程基本概念和操作Part2Word文件下载.docx
- 文档编号:18412053
- 上传时间:2022-12-16
- 格式:DOCX
- 页数:11
- 大小:21.16KB
C#网络编程基本概念和操作Part2Word文件下载.docx
《C#网络编程基本概念和操作Part2Word文件下载.docx》由会员分享,可在线阅读,更多相关《C#网络编程基本概念和操作Part2Word文件下载.docx(11页珍藏版)》请在冰豆网上搜索。
{
static
void
Main(string[]args){
const
int
BufferSize=8192;
//缓存大小,8192字节
Console.WriteLine("
is
running..."
);
IPAddress
ip=
new
IPAddress(new
byte[]{127,0,0,1});
TcpListener
listener=
TcpListener(ip,8500);
listener.Start();
//开始侦听
StartListening..."
//获取一个连接,中断方法
TcpClient
remoteClient=listener.AcceptTcpClient();
//打印连接到的客户端信息
ClientConnected!
{0}<
--{1}"
remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);
//获得流,并写入buffer中
NetworkStream
streamToClient=remoteClient.GetStream();
byte[]
buffer=
byte[BufferSize];
bytesRead=streamToClient.Read(buffer,0,BufferSize);
Reading
data,{0}bytes..."
bytesRead);
//获得请求的字符串
string
msg=
Encoding.Unicode.GetString(buffer,0,bytesRead);
Received:
{0}"
msg);
//按Q退出
}
}
这段程序的上半部分已经很熟悉了,我就不再解释。
remoteClient.GetStream()方法获取到了连接至客户端的流,然后从流中读出数据并保存在了buffer缓存中,随后使用Encoding.Unicode.GetString()方法,从缓存中获取到了实际的字符串。
最后将字符串打印在了控制台上。
这段代码有个地方需要注意:
在能够读取的字符串的总字节数大于BufferSize的时候会出现字符串截断现象,因为缓存中的数目总是有限的,而对于大对象,比如说图片或者其它文件来说,则必须采用“分次读取然后转存”这种方式,比如这样:
//获取字符串
bytesRead;
//读取的字节数
MemoryStream
msStream=
MemoryStream();
do
bytesRead=streamToClient.Read(buffer,0,BufferSize);
msStream.Write(buffer,0,bytesRead);
}
while
(bytesRead>
0);
buffer=msStream.GetBuffer();
Encoding.Unicode.GetString(buffer);
这里我没有使用这种方法,一个是因为不想关注在太多的细节上面,一个是因为对于字符串来说,8192字节已经很多了,我们通常不会传递这么多的文本。
当使用Unicode编码时,8192字节可以保存4096个汉字和英文字符。
使用不同的编码方式,占用的字节数有很大的差异,在本文最后面,有一段小程序,可以用来测试Unicode、UTF8、ASCII三种常用编码方式对字符串编码时,占用的字节数大小。
现在对客户端不做任何修改,然后运行先运行服务端,再运行客户端。
结果我们会发现这样一件事:
服务端再打印完“ClientConnected!
127.0.0.1:
8500<
--127.0.0.1:
xxxxx”之后,再次被阻塞了,而没有输出“Readingdata,{0}bytes...”。
可见,与AcceptTcpClient()方法类似,这个Read()方法也是同步的,只有当客户端发送数据的时候,服务端才会读取数据、运行此方法,否则它便会一直等待。
1.2客户端程序
接下来我们编写客户端向服务器发送字符串的代码,与服务端类似,它先获取连接服务器端的流,将字符串保存到buffer缓存中,再将缓存写入流,写入流这一过程,相当于将消息发往服务端。
Client
ClientRunning..."
client;
try
client=
TcpClient();
client.Connect("
localhost"
8500);
//与服务器连接
}
catch
(Exception
ex){
Console.WriteLine(ex.Message);
return;
//打印连接到的服务端信息
ServerConnected!
{0}-->
{1}"
client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
"
\"
WelcomeToTraceFact.Net\"
;
streamToServer=client.GetStream();
byte[]buffer=
Encoding.Unicode.GetBytes(msg);
//获得缓存
streamToServer.Write(buffer,0,buffer.Length);
//发往服务器
Sent:
现在再次运行程序,得到的输出为:
//服务端
Serverisrunning...
StartListening...
7847
Readingdata,52bytes...
"
WelcomeToTraceFact.Net"
输入"
Q"
键退出。
//客户端
ClientRunning...
7847-->
127.0.0.1:
8500
再继续进行之前,我们假设客户端可以发送多条消息,而服务端要不断的接收来自客户端发送的消息,但是上面的代码只能接收客户端发来的一条消息,因为它已经输出了“输入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()的位置,以响应其它的客户端。
对于第四种情况,实际上是构建一个服务端更为通常的情况,所以需要专门开辟一个章节讨论,这里暂且放过。
而我们上面所做的,即是列出的第一种情况,接下来我们再分别看一下第二种和第三种情况。
对于第二种情况,我们按照上面的叙述先对服务端进行一下改动:
(true);
然后启动多个客户端,在服务端应该可以看到下面的输出(客户端没有变化):
8196
8199
由第2种情况改为第3种情况,只需要将do向下挪动几行就可以了:
然后我们再改动一下客户端,让它发送多个请求。
当我们按下S的时候,可以输入一行字符串,然后将这行字符串发送到服务端;
当我们输入X的时候则退出循环:
ConsoleKey
key;
Menu:
S-Send,X-Exit"
key=
Console.ReadKey(true).Key;
if
(key==ConsoleKey.S){
//获取输入的字符串
Console.Write("
Inputthemessage:
Console.ReadLine();
(key!
=ConsoleKey.X);
接下来我们先运行服务端,然后再运行客户端,输入一些字符串,来进行测试,应该能够看到下面的输出结果:
11004
Readingdata,44bytes...
欢迎访问我的博客:
TraceFact.Net
data,14bytes...
我们一起进步!
//客户端
11004-->
S-Send,X-Exit
这里还需要注意一点,当客户端在TcpClient实例上调用Close()方法,或者在流上调用Dispose()方法,服务端的streamToClient.Read()方法会持续地返回0,但是不抛出异常,所以会产生一个无限循环;
而如果直接关闭掉客户端,或者客户端执行完毕但没有调用stream.Dispose()或者TcpClient.Close(),如果服务器端此时仍阻塞在Read()方法处,则会在服务器端抛出异常:
“远程主机强制关闭了一个现有连接”。
因此,我们将服务端的streamToClient.Read()方法需要写在一个try/catch中。
同理,如果在服务端已经连接到客户端之后,服务端调用remoteClient.Close(),则客户端会得到异常“无法将数据写入传输连接:
您的主机中的软件放弃了一个已建立的连接。
”;
而如果服务端直接关闭程序的话,则客户端会得到异常“无法将数据写入传输连接:
远程主机强迫关闭了一个现有的连接。
”。
因此,它们的读写操作必须都放入到try/catch块中。
2.服务端回发,客户端接收并输出
2.2服务端程序
我们接着再进行进一步处理,服务端将收到的字符串改为大写,然后回发,客户端接收后打印。
此时它们的角色和上面完全进行了一下对调:
对于服务端来说,就好像刚才的客户端一样,将字符串写入到流中;
而客户端则同服务端一样,接收并打印。
除此以外,我们最好对流的读写操作加上lock,现在我们直接看代码,首先看服务端:
//缓存大小,8192Bytes
//获取一个连接,同步方法,在此处中断
//获得流
//写入buffer中
lock(streamToClient){
(bytesRead==0)
throw
Exception("
读取到0字节"
//转换成大写并发送
msg=msg.ToUpper();
buffer
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C#网络编程基本概念和操作 Part2 C# 网络 编程 基本概念 操作