在客户端的窗口上有两个按钮点击button1就开始连接服务端:
tcpClient = new TcpClient("192.168.1.3", 8080);点击button2开始发数据到服务端:
NetworkStream stream = tcpClient.GetStream();
...
...
...
(通讯完毕后,并没有关闭tcpClient)>>>>>当第一次点击button2的时候正常运行,当第二次点击它的时候就抛出异常:“不允许对非连接的套接字执行该操作 at System.Net.Sockets.TcpClient.GetStream()”各位老大,到底是什么问题引起的呀?应该怎么修改呢?我的目的是想客户端与服务断连接之后,连续通讯,直到一方退出为止...
tcpClient = new TcpClient("192.168.1.3", 8080);点击button2开始发数据到服务端:
NetworkStream stream = tcpClient.GetStream();
...
...
...
(通讯完毕后,并没有关闭tcpClient)>>>>>当第一次点击button2的时候正常运行,当第二次点击它的时候就抛出异常:“不允许对非连接的套接字执行该操作 at System.Net.Sockets.TcpClient.GetStream()”各位老大,到底是什么问题引起的呀?应该怎么修改呢?我的目的是想客户端与服务断连接之后,连续通讯,直到一方退出为止...
解决方案 »
- Panel 竖着显示滚动条
- 这段代码究竟哪里出问题了?
- 如何返回原地址?
- C#图标问题
- 为何在DotNet中用Insert Into插入记录如此之慢?难道还不如VB中的AddNew?
- Task 和Thread在效率上有什么区别
- winform怎么调用微云API对文件进行上传
- HellMaster(李晋) 送给你的100分--------------------1
- 创建和访问xml web services的问题,在线等待。 难道真没人知道吗?我太失望了!
- 以下代码类型和c#如何转换?、
- netmicro(麦)请进,因CSDN限制,之前那个帖已经无法回复。
- SuperSocket 信息: (SpnRegister) : Error 1355。这个错误怎么解决?
第一次调用之后,以后直到关闭连接之前的所有通信,直接对stream 读写即可。
while(true)
{
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
if(stream.CanRead)
{
readBuffer = new Byte[10240]; stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), stream);
}
}private void ReadCallback(IAsyncResult ar)
{
NetworkStream n_stream = (NetworkStream)ar.AsyncState;int read_size = n_stream.EndRead(ar);
...
...
n_stream.BeginWrite(byteData, 0, byteData.Length, new AsyncCallback(WriteCallback), n_stream);
}public static void WriteCallback(IAsyncResult ar)
{
NetworkStream n_stream = (NetworkStream)ar.AsyncState;n_stream.EndWrite(ar);//n_stream.Close();
}请问是不是我的服务端写得不好呢?
{
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
while(true)
{ if(stream.CanRead)
{
readBuffer = new Byte[10240]; stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), stream);
}
}
}
试试看这样行不?
{
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
while(true)
{ if(stream.CanRead)
{
readBuffer = new Byte[10240]; stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), stream);
}
}
}用这种方法不行呀当一接收到客户端连接,就跑到第二个while里面去了,然后服务端和客户端都没反应了
刚才简单写了个,看看你能不能参考一下using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading;
using System.Net;namespace csdn
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} TcpListener tcpc;
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(MyServer));
t.IsBackground = true;
t.Start();
} private void MyServer()
{
tcpc = new TcpListener(IPAddress.Parse("127.0.0.1"), 2000);
tcpc.Start(20);
while (true)
{
TcpClient client = tcpc.AcceptTcpClient();
ClientClass cc = new ClientClass(client);
cc.work();
}
}
}
public class ClientClass
{
private TcpClient tcpc;
public ClientClass(TcpClient t)
{
this.tcpc = t;
}
public void work()
{
Thread t = new Thread(new ThreadStart(run));
t.IsBackground = true;
t.Start();
}
private void run()
{
NetworkStream nstream = tcpc.GetStream();
while (true)
{
// 在这里写你的收发代码,比如下面:
byte[] bb = Encoding.ASCII.GetBytes("hello");
nstream.Write(bb, 0, bb.Length);
}
}
}
}
{
NetworkStream n_stream = (NetworkStream)ar.AsyncState;n_stream.EndWrite(ar);n_stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), n_stream);
//n_stream.Close();
}请各位大大看看解决方法是不是这样呀?
若不用异步,用同步加线程的话,像我上面的那样就可以了。
while (true) { // 循环接收多个client
TcpClient client;
try { client = tcpListener.AcceptTcpClient(); }
catch { // 如果接收出错(例如外面有代码关闭了tcpListener)
break; // 就终止服务,跳出外循环
}
NetworkStream stream = client.GetStream();
try {
while (true) { // 循环相应client的数据
..... // 同步读
.... // 处理
.... // 同步写
}
} catch { } // 当出错(例如client已断开)时便可跳出内循环
finally {
try { stream.Close(); } catch {} // 始终关闭
try { client.Clise(); } catch {} // 始终关闭
}
}异步的话编起来比较麻烦,但是比较符合良好程序设计,因为充分调用了可用的资源首先是侦听的线程:private AutoResetEvent next = new AutoResetEvent(false);
private TcpListener tcpListener;
private TcpClient tcpClient; // 把局部变量都提到类字段
private NetworkStream stream;// 外部可以调用这个方法来终止侦听
public void StopListening() {
try { tcpListener.Close(); } catch { }
next.Set();
}public void Listen() {
tcpListener = new .......
while (true) {
try { client = tcpListener.AcceptTcpClient(); }
catch { // 如果接收出错(例如外面有代码关闭了tcpListener)
break; // 就终止服务,跳出
}
stream = client.GetStream();
BeginRead();
next.WaitOne(); // 等待当前连接的client完成
}
}void Next() {
try { stream.Close(); } catch { }
try { tcpClient.Close(); } catch { }
next.Set();
}void BeginRead() {
readBuffer = new Byte[10240];
try {
stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), stream);
} catch {
Next();
}
}然后 ReadCallback 和 WriteCallback 里都改成类似上面这个BeginRead的结构,也就是:try {
// 原来的事情
} catch {
Next();
}在WriteCallback的catch{}之后,再次调用BeginRead();
try { stream.Close(); } catch { }
try { tcpClient.Close(); } catch { }
next.Set();
}这个Next()把NetworkStream和TcpClient都关闭了,那么也就是服务端和客户端已经断开了连接,但我是想两者不断开连接,连续通讯的而且所有的Next()都是在Catch{}里面执行,那么读写stream不抛出异常的话,next.Set()就没办法执行了,那其他的客户端不就得不到处理了?(2)stream = client.GetStream();
BeginRead();
next.WaitOne(); // 等待当前连接的client完成在这里执行next.WaitOne()的话,如果之前连接的客户端不发送数据过来的话,线程会一直处于等待状态,其他的客户端也是发不了数据的
可以通过SocketExecption来确定,如果发生这类异常那连接基本是断开了.
我自己编写的连接池就是通过这方来释放连接的(不过我比较喜欢直接用Socket).“同步 + 线程”的运作方式到底是怎么样的?
通常的做法是开一个新的线程来维护一个Socket请求和发送.多个连接其实可以同时进行同步接收和发送.只是工作在各自的线程中.如果有N(A,B,C,D)个客户端已经连接了服务端,可不可以令服务端在某一时间内只处理其中一个客户端(A)?其他的客户端处于等待状态,等服务端处理完A之后在处理下一个请求?
不知道为什么你需要这样需求,因为现实中一个任务可能是多次请求发送数据.
如果你想只能有一个连接操作业务逻辑,那你可以对业务逻辑进行同步.
如果你想只能有一个连接操作业务逻辑,那你可以对业务逻辑进行同步.>>>因为现在的需求是客户端只发一次数据,然后等带服务端返回结果,所以就想实现这种方式的通讯,其实我也知这样设计是不合理的,但没办法,时间比较紧迫,所以最简单的方法就是一次只接受一个客户端的数据...另外,想请教楼上的各位大大,如果服务端和客户端要长期保持连接,然后服务端要按照一定的时间间隔主动发送消息到已连接的客户端(如检测客户端是否已经断开连接或者发送其他数据).我看过一些SOCKET通讯的例子,都是当客户端连接的时候把客户端的SOCKET保存在一个ARRAY动态数组里面,然后在需要发信息的时候就在数组里找到对应的SOCKET发送消息,但如果我是用TCPLISTENER和TCPCLIENT这种方式,那么服务端是把AcceptTcpClient()得到的TCPCLIENT还是AcceptTcpSocket()得到的SOCKET还是NetworkStream保存到数组里面呀?
其调用关系
TcpClient<->NetworkStream<->Socket(数据发送和接收).
其实三个都一样,只是封装使用的层次不同,当然Socket是最灵活的.
其调用关系
TcpClient<->NetworkStream<->Socket(数据发送和接收).
其实三个都一样,只是封装使用的层次不同,当然Socket是最灵活的.>>>恩,那么说我只要在服务端用数组将连接上来的TcpClient对象保存起来,然后等到需要的时候再对其进行操作就可以了,是吗?因为之前完全没做过网络方便的编程,所以各位大大不要谦虚我问的问题菜,嘿嘿....我把我的想法说出来,麻烦大家斧正:ArrayList clientList = new ArrayList();//服务端启动侦听:
tcpListener.Start();while(true)
{
TcpClient client = tcpc.AcceptTcpClient();
clientList.Add(client);
NetworkStream stream = client.GetStream(); readBuffer = new Byte[10240]; stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), stream);}//服务端需要主动发消息给指定的客户端
sendMsgToClient(clientList[x]);private sendMsgToClient(TcpClient _client)
{
NetworkStream _stream = _client.GetStream();
_stream.Write("要发送的消息");
}//服务端要关闭指定的客户端的连接
closeClient(clientList[x]);private sendMsgToClient(TcpClient _client)
{
_client.Close();
}上面是我的想法,不知道是否是正确的,请大大们指点指点呀!
但有些不太合理的地方,基于索引来维护连接很多时候有不确定性(到底这个索引的Cleitn是谁?)。
最好是用类包装一下,提供UserName等相关属性用hashtable 来维护连接。这样服务器想主动发信息给谁就很明确了。
不需做其他工作.
不过关闭后你还必须把连接从池中移走。
这样,就有可能出现这种情况:一个14.4K modem连接的用户正在用龟速发几MB的数据
如果你的服务器是“一次只服务一个客户端”,那么它就必须等待n分钟
即使1秒之后,每隔两三秒就有一个快速的连接用光速发了几KB的数据,
这些数据也必须等待几分钟,甚至因为等待超时了而被网络层强迫中断所以,一次只服务一个客户端的想法就不对了其次,TCP连接在.NET里的体现是“网络流”,也就是可以作为一个流Stream来读取/写入数据
这样的话,两次连续的数据之间哪里是分界线?或者同一次数据因为网络堵塞而中间停顿了几秒钟,如何判断这次数据还没有传完?最后还有,假设使用了业务模型,你还要注意客户端故障断掉了,如何取消这个业务(最起码,如何解锁,以便其他客户端可以处理他们的业务)网络世界有太多的不稳定性最后说说我对各个方面的各自的想法:1)关于TcpListener/TcpClient/NetworkStream/Socket:前3者是后面的Socket的超级繁复的包装。在这3者里面,只有TcpListener好用;TcpClient和NetworkStream之间的关系很不明白,特别是两者都有Close,不调用好像又没有释放好资源,两者都调用的话,好像能释放的资源就只有一个——Socket,所以一般情况下,特别是很复杂的服务器端,我只用Socket,有时也用一下TcpListener2)关于将所有已连接的Socket放在一个IList里面:有一个Socket.Select方法可以在三个IList里面过滤剩下符合条件的Socket,这个模型和方法非常适合单线程编程使用,过程类似:while(服务器未停止){测试可读的IList = 程序管理的已连接Socket的IList.副本();
测试可写的IList = 程序管理的已连接Socket的IList.选择写出缓冲区有数据的子集();
测试有错的IList = 程序管理的已连接Socket的IList.副本();Select(测试可读的IList, 测试可写的IList, 测试有错的IList, 1);如果可读IList有剩余(即这些剩余Socket有等待接收的数据)-->
对于每个可读Socket:
try{
检查 Socket.Available 属性确定有多少数据可读
读取这些数据,放入程序管理的单独为此Socket创建的读入缓冲区
如果刚才的读取得到0字节-->
// 根据MSDN,“可读”并且读出0字节意味着对方已中断
从已连接的Socket的IList里面将此Socket删除
} catch (读取时出错) {
从已连接的Socket的IList里面将此Socket删除
释放该Socket的所有资源
}
如果读入缓冲区里面的数据形成最起码一条完整的信息-->
处理这个信息
循环直到读入缓冲区没有完整的信息
循环直到可读IList完成遍历
如果可写IList有剩余-->
对于每个可写Socket:
从写出缓冲区获取并移除一小截数据(最大约几KB)
try { 写出去 }
catch (写数据时出错) {
从已连接的Socket的IList里面将此Socket删除
释放该Socket的所有资源 }
循环直到可写IList完成遍历
如果出错IList有剩余-->
对于每个出错的Socket:
从已连接的Socket的IList里面将此Socket删除
释放该Socket的所有资源
循环直到出错IList完成遍历}这个过程负责所有低级的操作。仔细阅读可以发现两个“缓冲区”,一个“读入缓冲区”,一个“写出缓冲区”。这两个缓冲区就是用来防止我这个回复第一个内容里描述的情况。在“处理这个信息”的过程里,如果服务器有消息要回复给客户端,就把消息写到该Socket的写出缓冲区,下一个循环的时候就会把它们写出去。整个过程都在单一进程内完成,所以不会出现死锁或者资源争用情况,但是如果某个处理过程太漫长,一些客户端就可能会抱怨连接不够快了。但是因为你本身就想一次只处理一个客户端的事务,你就可以跟客户们说这是“设计使然”,抱怨无效。以此推断,这个模型可能更适合你现在的能力。你只需要研制出一个有“读入缓冲区”和“写出缓冲区”的可以与单一Socket连接起来的类就可以了。示范类结构:class SocketCollection { // 管理已连接的Sockets
Hashtable sockets; // Key = Socket; Value = SocketBuffers
public IList GetSocketsCopy() {
// 返回 sockets.Keys 集合的复制品,用于可读测试和出错测试IList
}
public IList GetSocketsCopyWithOutputData() {
// 返回 sockets.Keys 集合的复制品,其中没有输出数据的都先去掉
// 用于可写测试
}
public void AddSocket(Socket s) { // 添加一个已连接Socket
sockets.Add(s, new SocketBuffers());
}
public void RemoveSocket(Socket s) { // 删除一个Socket并将其断开、释放资源
sockets.Remove(s);
try { s.Shutdown(SocketShutdown.Both); } catch { }
try { s.Close(); } catch { }
}
}
class SocketBuffers { // 一个包含读入缓冲区和写出缓冲区的类
public InputBuffer InputBuffer { get; }
public OutputBuffer OutputBuffer { get; }
}
class InputBuffer { // 读入缓冲区
public void AppendData(byte[] data); // 从Socket读出的数据会写入这个缓冲区
public bool HasCompletePieceOfData(); // 判断是否有完整的数据
public byte[] GetCompletePieceOfData(); // 获取一组完整的数据并从缓冲区删除
}
class OutputBuffer { // 写出缓冲区
public void AppendData(byte[] data); // 从主过程写给客户段的数据会首先写入这里
public bool HasData(); // 判断是否有数据要发送出去
public byte[] GetData(int maxBytes); // 获取一截数据并从缓冲区删除
}
从写出缓冲区获取并移除一小截数据(最大约几KB)
>>>请问这个作用是什么?
为什么还说它是在同步情况下使用的呢?疑惑ing...望各位大大解惑...
简易入门:
http://www.jmarshall.com/easy/http/HTTP1.0
http://www.ics.uci.edu/pub/ietf/http/rfc1945.htmlHTTP1.1
http://www.w3.org/Protocols/rfc2616/rfc2616.html
1) NetworkStream 的同步问题:
的确有人提到过,.NET 1.x 里面的NetworkStream,它所提供的BeginXXX和EndXXX其实是另开一个线程同步执行,于是如果大量使用的话就会造成线程池枯竭。但是.NET 2.0已经将其改为真正的异步了。2) 常见的做法,是在数据的开头附上数据长度
一般来说,2字节的ushort已经够大,因为一条数据64KB已经比较恐怖
但是你可能还会担心会超出,所以就写int吧,一条数据怎么也超不过2GB吧
但是还是要小心恶意客户端用这个int值造成拒绝服务攻击(占用2GB内存的缓冲区?!)
无论如何,以下用4字节的长度标识来举例;具体应该用几个字节,应该怎么在可扩展性和安全性之间权衡,是你自己要思考的事情。从二进制网络流的角度看,互相传送的数据的格式都是:[4字节][ n 字 节 ][4字节][ n 字 节 ][4字节][ n 字 节 ]...其中所有的[4字节]都表示紧接着的真正的数据的长度
这样也方便缓冲区的分配
首先读取4个字节,然后用BitConverter类将其转成int,
然后在接着的读取中,用一个累加器时刻监测读够这个数字没有
读够了就可以开始处理了(但是在截取这条完整的消息的时候,小心不要丢掉“读得太多”的部分——那是下一条消息的长度标识和数据的开头)这样的话,即使一条大数据被分成无数块,而且因为网络堵塞需要一天才能传完,也因为双方“心里有数”而不会贸然开始处理收了一半的数据
的确有人提到过,.NET 1.x 里面的NetworkStream,它所提供的BeginXXX和EndXXX其实是另开一个线程同步执行,于是如果大量使用的话就会造成线程池枯竭。但是.NET 2.0已经将其改为真正的异步了。>>>>>>>按照我的理解,不单是NetworkStream的BeginXXX和EndXXX是另开一个线程同步执行,我觉得Socket类所提供的BeginXXX和EndXXX也是另开一个线程同步执行...不知道我是不是又理解错了...那么真正的异步又是什么概念呢?
这一段数据可能会被斩件传送;甚至连最开头的4字节也可能被斩断之所以不怕被斩件,是因为另一头知道它首先一定要读完4个字节,得到了下一条数据的长度,然后就一定要读完这n字节,才是一条完整的数据。2)使用TCP通讯不需要担心重组;TCP会保证数据会按照发送的顺序到达对方。3)如果你知道.NET里面所有委托都可以用.NET自己的异步调用机制来异步调用,就大概可以明白为什么NetworkStream被别人称为“是同步的东西”。它利用的就是.NET的异步调用委托的功能,另开一个线程同步收发数据但是Socket就不同了;它是直接封装操作系统级别的异步I/O,即你调用BeginXXXX之后,它把必要的东西封装好之后就直接把调用封送到系统级别,自己就退出了。到系统通知它I/O完成之后,它就把结果反封送回来,所以是“不独占一个线程”的
其实,因为你现在已经预先知道要读多少了,所以读的流程就可以简化成这样:
(假设从0或者从一条新的数据开始)首先的任务,是读取4个字节所以,
buffer = new byte[4]; (byte[] buffer 在某个用来包装的类里面已经定义好)
read = 0; (int read 在同一个类里面已经定义好;意义:已读取的字节数;read是过去时)
length = 4; (int length 表示本消息的总长度)
isReadingLength = true; (bool isReadingLength 表示当前是否正在读取数据长度)然后在可读的时候:int available = Socket.Available;
if (available == 0) // 可读而且有0个字节=连接死掉了
throw new SocketException(10035); // 这个只是用来代码重用;
// 在外边的catch()里面有中断连接的代码
int remaining = length - read; // 当前还有这么多字节没有读
int toRead = available > remaining ? remaining : available; // 决定要读多少
int received = Socket.Receive(buffer, read, toRead, SocketFlags.None); // 将可用的数据接入缓冲区的适当的位置
read += received; // 累加
if (read == length) // 已经读完
{
if (isReadingLength) // 读完的是下个数据的长度
{
read = 0;
length = BitConverter.ToInt32(buffer, 0); // 将读到的4个字节转成 int
// 提示:写出去的时候,用的是 byte[] BitConverter.GetBytes(int)
buffer = new byte[length];
isReadingLength = false;
// 这样就准备好接收真正的数据了
}
else { ... } // 这里就可以处理保存在buffer里面的信息了
}再啰嗦一句,int做长度还是有点危险……要么换成ushort(System.UInt16),要么在读完length之后判断一下是否太夸张,例如在 if (read == length) if (isReadingLength) 里面,将 length 计算出来之后,if (length > 1000000 /* 1MB */) throw new NotSupportedException();