随心所欲

做个幸福的人
posts - 147, comments - 1402, trackbacks - 28, articles - 0
  博客园 :: 首页 :: 新随笔 ::  :: 订阅 订阅 :: 管理

C#的Socket程序(TCP)

Posted on 2007-10-15 14:36 随心所欲 阅读(10450) 评论(20)  编辑 收藏 网摘 所属分类: 通讯/WebServer

其实只要用到Socket联接,基本上就得使用Thread,是交叉使用的。
C#封装的Socket用法基本上不算很复杂,只是不知道托管之后的Socket有没有其他性能或者安全上的问题。
在C#里面能找到的最底层的操作也就是socket了,概念不做解释。
程序模型如下:
WinForm程序 : 启动端口侦听;监视Socket联接情况;定期关闭不活动的联接;
Listener:处理Socket的Accept函数,侦听新链接,建立新Thread来处理这些联接(Connection)。
Connection:处理具体的每一个联接的会话。

1:WinForm如何启动一个新的线程来启动Listener:
       //start the server
        private void btn_startServer_Click(object sender, EventArgs e)
        {
            //this.btn_startServer.Enabled = false;
            Thread _createServer = new Thread(new ThreadStart(WaitForConnect));
            _createServer.Start();
        }
        //wait all connections
        private void WaitForConnect()
        {
            SocketListener listener = new SocketListener(Convert.ToInt32(this.txt_port.Text));
             listener.StartListening();
        }
因为侦听联接是一个循环等待的函数,所以不可能在WinForm的线程里面直接执行,不然Winform也就是无法继续任何操作了,所以才指定一个新的线程来执行这个函数,启动侦听循环。
这一个新的线程是比较简单的,基本上没有启动的参数,直接指定处理函数就可以了。
2:Listener如何启动循环侦听,并且启动新的带有参数的线程来处理Socket联接会话。
先看如何建立侦听:(StartListening函数)
IPEndPoint localEndPoint = new IPEndPoint(_ipAddress, _port);
        // Create a TCP/IP socket.
        Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            // Bind the socket to the local endpoint and  listen for incoming connections.
            try
            {
                listener.Bind(localEndPoint);
                listener.Listen(20);//20 trucks

                // Start listening for connections.
                while (true)
                {
                   // here will be suspended while waiting for a new connection.
                    Socket connection = listener.Accept();
                    Logger.Log("Connect", connection.RemoteEndPoint.ToString());//log it, new connection
                ……
           }
         }……
基本步骤比较简单:
建立本机的IPEndPoint对象,表示以本机为服务器,在指定端口侦听;
然后绑定到一个侦听Socket上;
进入while循环,等待新的联接;
如果有新的联接,那么建立新的socket来对应这个联接的会话。
   值得注意的就是这一句联接代码:listener.Accept()。执行这一句的时候,程序就在这个地方等待,直到有新的联检请求的时候程序才会执行下一句。这是同步执行,当然也可以异步执行。
 
   新的联接Socket建立了(Accept之后),对于这些新的socket该怎么办呢?他们依然是一个循环等待,所以依然需要建立新的Thread给这些Socket去处理会话(接收/发送消息),而这个Thread就要接收参数了。
   Thread本身是不能接收参数的,为了让它可以接收参数,可以采用定义新类,添加参数作为属性的方法来解决。
   因为每一个Socket是一个Connection周期,所以我定义了这么一个类public class Connection。这个类至少有这样一个构造函数public Connection(Socket socket); 之所以这么做,就是为了把Socket参数传给这个Connection对象,然后好让Listener启动这个Thread的时候,Thread可以知道他正在处理哪一个Socket。
    具体处理的方法:(在Listener的StartListening函数,ocket connection = listener.Accept();之后)
    Connection gpsCn = new Connection(connection);
                    //each socket will be wait for data. keep the connection.
                    Thread thread = new Thread(new ThreadStart(gpsCn.WaitForSendData));
                    thread.Name = connection.RemoteEndPoint.ToString();
                    thread.Start();
 如此一来,这个新的socket在Accept之后就在新的Thread中运行了。
   3:Connection的会话处理
   建立了新的Connection(也就是socket),远程就可以和这个socket进行会话了,无非就是send和receive。
   现在先看看怎么写的这个线程运行的Connection. WaitForSendData函数
    while (true)
            {
                bytes = new byte[1024];
                string data = "";
                //systm will be waiting the msg of receive envet. like Accept();
                //here will be suspended while waiting for socket income msg.
                int bytesRec = this._connection.Receive(bytes);
                _lastConnectTime = DateTime.Now;
                if (bytesRec == 0)//close envent
                {
                    Logger.Log("Close Connection", _connection.RemoteEndPoint.ToString());
                    break;
                }
                data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
                //…….handle your data.
             }
可以看到这个处理的基本步骤如下:
   执行Receive函数,接收远程socket发送的信息;
   把信息从字节转换到string;
   处理该信息,然后进入下一个循环,继续等待socket发送新的信息。
值得注意的有几个:
   1:Receive函数。这个函数和Listener的Accept函数类似。在这个地方等待执行,如果没有新的消息,这个函数就不会执行下一句,一直等待。
   2:接收的是字节流,需要转化成字符串
   3:判断远程关闭联接的方式
   4:如果对方的消息非常大,还得循环接收这个data。
4:如何管理这些联接(thread)
通过上边的程序,基本上可以建立一个侦听,并且处理联接会话。但是如何管理这些thread呢?不然大量产生thread可是一个灾难。
管理的方法比较简单,在Listener里面我定义了一个静态的哈希表(static public Hashtable Connections=new Hashtable();),存储Connection实例和它对应的Thread实例。而connection中也加入了一个最后联接时间的定义(private DateTime _lastConnectTime;)。在新链接建立的时候(Listener的Accept()之后)就把Connection实例和Thread实例存到哈希表中;在Connection的Receive的时候修改最后联接时间。这样我们就可以知道该Connection在哪里,并且会话是否活跃。
然后在Winform程序里头可以管理这些会话了,设置设置超时。

Feedback

#1楼    回复  引用  查看    

2007-10-15 15:20 by Zealic      
看到过很多这样的异步 socket

client.BeginRecive(args...,new AsyncCallback(SocketProc),client);


void SocketProc(object state)
{
Socket client = (Socket)state;
int count = client.EndRecive();
...
client.BeginRecive(args...,new AsyncCallback(SocketProc),client);
}

不得不说
这样的代码太糟糕了

#2楼 [楼主]   回复  引用  查看    

2007-10-15 15:31 by 随心所欲      
每个联接用了一个新的线程,不知道在联接数量过大的情况下程序会不会崩溃.

#3楼    回复  引用  查看    

2007-10-15 15:54 by Zealic      
上述的方法我测试过
在处理逻辑不长的情况下,大概200ms以内,SocketProc 调用的 BeginRecive 依然在本线程!
这大概是由系统线程池决定的,在多核情况下可能有所不同
所以我觉得
虽然这样的设计简单
但是一旦处理复杂,维护性就会变差
并且
短连接还好说,遇到长连接,代码就不好控制了。

#4楼 [楼主]   回复  引用  查看    

2007-10-15 18:32 by 随心所欲      
@Zealic
你测试的是"异步",还是我的那种同步的方法?

#5楼    回复  引用  查看    

2007-10-15 20:12 by Zealic      
当然是我上面写的代码
我大多数情况下都是写同步代码的
除了线程工作量小的情况下用线程池
其他情况自己创建线程控制

#6楼 [楼主]   回复  引用  查看    

2007-10-16 09:07 by 随心所欲      
@Zealic
我想问题还是在于用途.
我的这个只是GPS通讯,数据量几十个字节而已,快速.所以同步也无所谓.
但是有的应用比如大文件传输,或者服务器计算,这样的应用需要长时间的响应,所以最好还是作成异步比较方便,不然会造成客户响应上的问题.

#7楼    回复  引用  查看    

2007-10-16 09:11 by henry      
@Zealic
那异步代码很正常啊,你必须在那里归递发送没有完成的数据.
我从学Socket开始就喜欢用异步,你说的总是都是一些程序容错的问题.
写过一些东西
http://www.cnblogs.com/henryfan/archive/2006/11/28/574918.html
组件的设计并不好,有时间自己会整理一下重新编写这组件.

#8楼    回复  引用  查看    

2007-10-18 22:15 by Zealic      
@henry
我想表达的是
异步调用并不太合适长连接(长时间占用)。
毕竟那是由FCL线程池分配的线程,程序小的时候看不出来,程序变大的时候,可控性很差。

代码是没有问题的,隐患也是存在的。

#9楼    回复  引用    

2007-12-31 10:40 by KL323 [未注册用户]
还不错!!!!!!!!!!!!!!!

#10楼    回复  引用  查看    

2008-01-08 09:53 by Zealic      
纠正下我自己的错误
.Net 中的异步应该是使用 IOCP 实现的
因此不会导致线程长时间占用
在此,我检讨
希望没有误导到人

#11楼 [楼主]   回复  引用  查看    

2008-01-08 10:17 by 随心所欲      
@Zealic
真是有心人。谢谢

#12楼    回复  引用    

2008-05-28 00:16 by ngnono [未注册用户]
我正在研究SOCKET通信的问题,此篇文章给我帮助很大.

#13楼    回复  引用    

2008-06-30 11:20 by hxzwu [未注册用户]
好,有很大帮助.

#14楼    回复  引用  查看    

2008-07-19 10:46 by 少林      
请问楼主一下,出现"你的主机软件已经放弃一个已有的连接"的错误,是怎么回事拉?希望能够得到你的回复,谢谢

#15楼 [楼主]   回复  引用  查看    

2008-07-21 09:49 by 随心所欲      
@少林
可能,
1:如果已经连接成功,然后出这个错误,可能是因为链接超时。或者服务器的链接程序被迫关闭
2:如果从一开始就没有链接上,就出现这个错误,可能是找不到服务器,或者该服务器的端口没有开放,无法连接。

#16楼 [楼主]   回复  引用  查看    

2008-07-30 09:28 by 随心所欲      
qq号码已经删掉。

#17楼    回复  引用    

2008-09-26 18:05 by asdfds [未注册用户]
少林
可能,
1:如果已经连接成功,然后出这个错误,可能是因为链接超时。或者服务器的链接程序被迫关闭
2:如果从一开始就没有链接上,就出现这个错误,可能是找不到服务器,或者该服务器的端口没有开放,无法连接。

#18楼    回复  引用  查看    

2008-10-31 01:58 by 大城小格      
@少林

你的client端口发送了数据之后没有调用socket.Close();

#19楼    回复  引用  查看    

2008-11-10 15:14 by Jerry Qian      
SocketListener 是楼主自己扩展的吗.我的怎么没有啊.

#20楼 [楼主]   回复  引用  查看    

2008-11-11 12:40 by 随心所欲      
@Jerry Qian
是的。
就是第2的那个Listner

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
Google站内搜索

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》



相关文章:

相关链接:
 
Google