- 浏览: 129248 次
- 性别:
- 来自: 上海
最新评论
-
ranweizheng:
luokery 写道有失真...显然,存在失真是必然的,只是怎 ...
Java图片缩小后不失真的代码(缩略图) -
szhnet:
kree 写道szhnet 写道其实我在1楼说的不对,那个不能 ...
非阻塞通信,《Java网络编程精解》指误。 -
kree:
szhnet 写道其实我在1楼说的不对,那个不能用到达输入流的 ...
非阻塞通信,《Java网络编程精解》指误。 -
szhnet:
其实我在1楼说的不对,那个不能用到达输入流的未尾来理解。
非阻塞通信,《Java网络编程精解》指误。 -
leaow567:
同意楼上的观点
非阻塞通信,《Java网络编程精解》指误。
本文介绍如何使用非阻塞方式的Socket通信,并且创建了一个聊天程序的例子来帮助说明。
介绍
本文介绍如何在多个应用程序之间创建和使用TCP/IP Socket来进行通信。这些应用程序可以运行在同一台机器,也可以在局域网内,甚至也可以是跨越Internet的*。这种方法的好处是不需要你自己来使用线程,而是通过调用Socket的非阻塞模式来实现。在例子中:服务器创建病侦听客户端的连接,一旦有客户连接,服务器就将其加入到一个活动客户的列表中,某个客户端发送的消息也有服务器发送到各个连接的客户端,就好像聊天室中的那样。或许Remoting (远程调用)是做这种工作更好的办法,但是我们这里还是来学习学习如何使用Socket来实现。
*注意:跨越Internet的通讯要求服务器有独立的IP地址并且不在代理或是放火墙之后。
事件时序
服务器必须要先侦听,客户端才能够连接。下面的图例说明了在一个异步Socket会话中的事件时序。
运行示例
实例代码分为两部分:ChatServer 和ChatClient. 我们首先来创建ChatServer ,然后使用下面的Telnet命令来测试它。
telnet {server machine IP address or machine name} 399
telnet 10.328.32.76 399
这时,服务器上应该出现一条消息来表明这个客户连接的地址和端口。在任一个telnet窗口中键入的字符都会回显到所有与服务器连接的telnet的窗口中。试试从多台机器上并发连接服务器。不要使用localhost或者127.0.0.1来作为服务器程序唯一的侦听地址。
然后运行ChatClient实例作相同的试验和多个客户端和多个telnet并存的测试。
为什么要使用.NET的Socket?
.NET在很多地方都用到了sockets,比如:WebServices和Remoting。但是在那些应用中底层的Socket支持已经做好了,不需要直接使用。但是,和其他非.NET系统的Socket打交道或简单通信的场合中Socket的使用还是很有必要的。它可以用来和诸如DOS,Windows和UNIX系统进行通信。底层的Socket应用也可以让你减少了诸如组测,权限,域(domains),用户ID,密码等这些麻烦的安全方面的顾虑。
ChatServer / Listener
服务器侦听端口,当有连接请求时,接受该连接并返回一条欢迎信息。在例子中客户连接被加到一个活动客户列表m_aryClients中去。这个列表会根据客户加入和离开作相应的增删。在某些情况下可能会丢失连接,所以在实际的系统中还应该有轮询侦测客户端是否在线的部分。当服务器端的listener收到客户端发来的信息后,它会把消息广播到所有连接的客户端。
下面讨论两种侦听的方法,一个是用轮询(polling),另外一个在使用事件来侦测连接的请求。
方法1 – 使用轮询的 TcpListener
System.Net.Sockets中的TcpListener 类为我们提供了一个侦听和处理客户连接的简单手段。下面的代码侦听连接,接受连接,并且向客户连接发回一个带有时间戳的欢迎信息。如果有另外一个连接请求到来,原来的连接将会丢失。注意,欢迎信息是采用ASCII编码,而不是UNICODE。
方法2 – 使用带事件的Socketprivate Socket client = null;
const int nPortListen = 399;
try
{
TcpListener listener = new TcpListener( nPortListen );
Console.WriteLine( "Listening as {0}", listener.LocalEndpoint );
listener.Start();
do
{
byte [] m_byBuff = new byte[127];
if( listener.Pending() )
{
client = listener.AcceptSocket();
// Get current date and time.
DateTime now = DateTime.Now;
string strDateLine = "Welcome " + now.ToString("G") + "\n\r";
// Convert to byte array and send.
Byte[] byteDateLine = System.Text.Encoding.ASCII.GetBytes( strDateLine.ToCharArray() );
client.Send( byteDateLine, byteDateLine.Length, 0 );
}
else
{
Thread.Sleep( 100 );
}
} while( true ); // Don't use this.
}
catch( Exception ex )
{
Console.WriteLine ( ex.Message );
}
一个更为优雅的方法是创建一个事件来捕捉连接请求。ChatServer实例就采用了这种方法。首先服务器的名字和地址用下面的代码取得。
IPAddress [] aryLocalAddr = null;
string strHostName = "";
try
{
// NOTE: DNS lookups are nice and all but quite time consuming.
strHostName = Dns.GetHostName();
IPHostEntry ipEntry = Dns.GetHostByName( strHostName );
aryLocalAddr = ipEntry.AddressList;
}
catch( Exception ex )
{
Console.WriteLine ("Error trying to get local address {0} ", ex.Message );
}
// Verify we got an IP address. Tell the user if we did
if( aryLocalAddr == null || aryLocalAddr.Length < 1 )
{
Console.WriteLine( "Unable to get local address" );
return;
}
Console.WriteLine( "Listening on : [{0}] {1}", strHostName, aryLocalAddr[0] );
得到地址之后,我们要把listener这个Socket绑定到这个地址。我们这里使用的侦听端口是399。此外,从位于"C:\WinNT\System32\drivers\etc\Services"的服务文件中读取端口号应该是一个很好的练习。下面的代码绑定Listener并且开始侦听。一个事件handler把所有的连接请求都指向了OnConnectRequest。这样程序就可以不需要等待或者轮询来处理客户连接了。
const int nPortListen = 399;
// Create the listener socket in this machines IP address
Socket listener = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
listener.Bind( new IPEndPoint( aryLocalAddr[0], 399 ) );
//listener.Bind( new IPEndPoint( IPAddress.Loopback, 399 ) ); // For use with localhost 127.0.0.1
listener.Listen( 10 );
// Setup a callback to be notified of connection requests
listener.BeginAccept( new AsyncCallback( app.OnConnectRequest ), listener );
当客户连接请求到达时,就会激发下面的处理事件。下面的代码首先创建了client (Socket),然后发回欢迎信息,接着重新建立了接受事件处理(accept event handler)。Socket client;
public void OnConnectRequest( IAsyncResult ar )
{
Socket listener = (Socket)ar.AsyncState;
client = listener.EndAccept( ar );
Console.WriteLine( "Client {0}, joined", client.RemoteEndPoint );
// Get current date and time.
DateTime now = DateTime.Now;
string strDateLine = "Welcome " + now.ToString("G") + "\n\r";
// Convert to byte array and send.
Byte[] byteDateLine = System.Text.Encoding.ASCII.GetBytes( strDateLine.ToCharArray() );
client.Send( byteDateLine, byteDateLine.Length, 0 );
listener.BeginAccept( new AsyncCallback( OnConnectRequest ), listener );
}
这段代码可以扩展,维护客户Socket的列表,监控数据接收和连接断开。对于连接断开的侦测放在AsyncCallback 事件处理中。ChatClient部分将在下面细述该机制。
ChatClient
ChatClient是一个Windows Form应用程序,用来连接服务器,收发消息。
连接
当点击界面上的连接按钮使执行下面的程序使客户连接到服务器。
如果连接已经存在就销毁它。创建一个Socket和指定的端点相连。 被注释掉部分的代码采用简单的阻塞式连接方法。BeginConnect 则用来做一个非阻塞的连接请求。注意,即使是一个非阻塞的用户连接请求,连接也回被阻塞知道机器名称被解析为IP地址。所以,要尽量使用IP地址而不是机器名来避免这种情况。一旦连接请求处理完毕就会调用下面的方法,它显示连接错误或者在成功连接的情况下建立起接收数据的回调。 private Socket m_sock = null;
private void m_btnConnect_Click(object sender, System.EventArgs e)
{
Cursor cursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
try
{
// Close the socket if it is still open
if( m_sock != null && m_sock.Connected )
{
m_sock.Shutdown( SocketShutdown.Both );
System.Threading.Thread.Sleep( 10 );
m_sock.Close();
}
// Create the socket object
m_sock = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
// Define the Server address and port
IPEndPoint epServer = new IPEndPoint( IPAddress.Parse( m_tbServerAddress.Text ), 399 );
// Connect to the server blocking method and setup callback for recieved data
// m_sock.Connect( epServer );
// SetupRecieveCallback( m_sock );
// Connect to server non-Blocking method
m_sock.Blocking = false;
AsyncCallback onconnect = new AsyncCallback( OnConnect );
m_sock.BeginConnect( epServer, onconnect, m_sock );
}
catch( Exception ex )
{
MessageBox.Show( this, ex.Message, "Server Connect failed!" );
}
Cursor.Current = cursor;
}
public void OnConnect( IAsyncResult ar )
{
// Socket was the passed in object
Socket sock = (Socket)ar.AsyncState;
// Check if we were sucessfull
try
{
// sock.EndConnect( ar );
if( sock.Connected )
SetupRecieveCallback( sock );
else
MessageBox.Show( this, "Unable to connect to remote machine",
"Connect Failed!" );
}
catch( Exception ex )
{
MessageBox.Show( this, ex.Message, "Unusual error during Connect!" );
}
}
接收数据
为了异步接收数据,有必要建立一个AsyncCallback 来处理被诸如接到数据和连接断开所激发的事件。用下面的方法。
private byte [] m_byBuff = new byte[256]; // Recieved data buffer
public void SetupRecieveCallback( Socket sock )
{
try
{
AsyncCallback recieveData = new AsyncCallback( OnRecievedData );
sock.BeginReceive( m_byBuff, 0, m_byBuff.Length, SocketFlags.None,
recieveData, sock );
}
catch( Exception ex )
{
MessageBox.Show( this, ex.Message, "Setup Recieve Callback failed!" );
}
}
SetupRecieveCallback 方法启动了BeginReceive ,并利用代理指针把回调指向OnReceveData 方法。同时它也把一个用来接收数据的缓冲传递过去。
public void OnRecievedData( IAsyncResult ar )
{
// Socket was the passed in object
Socket sock = (Socket)ar.AsyncState;
// Check if we got any data
try
{
int nBytesRec = sock.EndReceive( ar );
if( nBytesRec > 0 )
{
// Wrote the data to the List
string sRecieved = Encoding.ASCII.GetString( m_byBuff, 0, nBytesRec );
// WARNING : The following line is NOT thread safe. Invoke is
// m_lbRecievedData.Items.Add( sRecieved );
Invoke( m_AddMessage, new string [] { sRecieved } );
// If the connection is still usable restablish the callback
SetupRecieveCallback( sock );
}
else
{
// If no data was recieved then the connection is probably dead
Console.WriteLine( "Client {0}, disconnected", sock.RemoteEndPoint );
sock.Shutdown( SocketShutdown.Both );
sock.Close();
}
}
catch( Exception ex )
{
MessageBox.Show( this, ex.Message, "Unusual error druing Recieve!" );
}
}
当上面的事件被激发时,接收到的数据被默认为是ASCII编码的。新数据也会被激发的事件显示出来。尽管可以调用Add() 在列表中显示新数据,但这并不是一个好主意,因为收到的数据很有可能要被送到其他线程中去处理。注意,需要在接收之后重建接收回调,来确保可以继续接收数据。因为有可能数据很多,超过最初的buffer容量。
创建 AddMessage 委托可以降低Socket线程和用户界面线程的耦合程度,如下所示:
// Declare the delegate prototype to send data back to the form
delegate void AddMessage( string sNewMessage );
namespace ChatClient
{
. . .
public class FormMain : System.Windows.Forms.Form
{
private event AddMessage m_AddMessage;
// Add Message Event handler for Form
. . .
public FormMain()
{
. . .
// Add Message Event handler for Form decoupling from input thread
m_AddMessage = new AddMessage( OnAddMessage );
. . .
}
public void OnAddMessage( string sMessage )
{
// Thread safe operation here
m_lbRecievedData.Items.Add( sMessage );
}
public void OnSomeOtherThread()
{
. . .
string sSomeText = "Bilbo Baggins";
Invoke( m_AddMessage, new string [] { sSomeText } );
}
. . .
}
}
使用UNICODE
当时用比特流来发送接收数据时,数据就需要被适当的编码。C# 采用多字节字符编码,尽管这里使用Encoding.ASCII ,但如果需要也可以使用Encoding.UNICODE
不要相信发出什么就能收到什么
当接收数据事件被激发,接收的数据被放置到接收缓冲中去。在我们的开发中,分组发送往往对应一个分组接收事件。但是在真正的系统中并非如此。数据并不是都是规规矩矩的在报文中,而有可能被拆分到若干个分组中。不要指望总能收到完整的报文,也不要指望建立自己的符号标记报文的开始和结束就万事大吉了。
结论
尽管使用Socket并不难,但是要用的很好还是需要大量的实践练习。当然在合适的场合你也应该试试使用WebServices或Remoting。此外,Wrox出版社的Professional ADO.NET Programming这本书很不错,值得一看。
发表评论
-
如果你对JavaSocket依然陌生,看看这个最简单的例子.
2010-05-06 10:21 1230下面程序实现利用 Socket 套接字进行面向连接通信的编程。 ... -
使用NIO实现非阻塞Socket通信
2010-05-05 16:17 1943从JDK 1.4开始,Java提供的NIO API来开发高性能 ... -
一个Java多线程阻塞模式通信的例子
2010-05-05 16:04 1880程序分Server和Client 服务器端打开侦听的端口,一 ... -
非阻塞通信,《Java网络编程精解》指误。
2010-05-05 15:57 2133对于用ServerSocket和Socket写的 ... -
JAVA非阻塞Socket服务程序
2010-05-05 15:46 2926import java.nio.channels.*; im ... -
非阻塞Socket客户端程序
2010-05-05 15:44 2095import java.nio.*; import java ... -
Java非阻塞聊天室源码 Client
2010-05-05 15:38 1584//client public class NBChatCl ... -
Java非阻塞聊天室源码 Server
2010-05-05 15:31 1772//server public class NBCh ... -
非阻塞式Socket举例
2010-05-05 15:10 1572package NonBlockingSocket; i ... -
JAVA技巧(充分利用资源,非阻塞的Socket链接)
2010-05-05 15:06 1565我们建立普通的Socket时,我们必须等待连接建立成功,才 ...
相关推荐
用一个最简单的例子说明异步非阻塞Socket的基本原理和工作机制
"异步非阻塞socket聊天室程序"是一个使用C++语言,并基于MFC(Microsoft Foundation Classes)库构建的项目,旨在实现高效的多用户通信。下面将详细阐述这个程序背后的关键知识点。 首先,我们关注的是"异步非阻塞...
本文将深入探讨如何使用MFC实现非阻塞Socket通信,并结合protobuf(Protocol Buffers)作为数据交换格式,构建一个允许多个客户端与单一服务器进行通信的系统。 首先,我们来理解“非阻塞Socket”。在传统的阻塞...
在异步模式下,可能需要使用非阻塞模式或缓冲区管理策略,以确保数据的正确传输。 6. **关闭Socket**:完成通信后,使用`close()`函数关闭socket。 在实际应用中,为了实现更复杂的异步通信,开发者通常会使用库如...
在这个"socket多线程例程非阻塞模式"的示例中,我们将深入探讨如何在Windows平台上实现多线程的socket通信,并了解非阻塞模式的工作原理。 首先,让我们来理解Socket的基本概念。Socket是进程间通信(IPC)的一种...
异步Socket通信是一种非阻塞的通信方式,它允许应用程序在等待数据传输的同时执行其他任务,提高了程序的响应性和效率。在Windows平台上,这通常通过Windows套接字API(Winsock)中的异步方法实现,如WSAAsyncSelect...
而异步socket通信,也称为非阻塞模式,允许程序在发送或接收数据时不必等待,可以立即返回继续执行其他任务,从而提高程序的响应速度和并发处理能力。 在Windows操作系统中,实现异步socket通信通常采用I/O完成端口...
### Socket C++ TCP阻塞与非阻塞服务器客户端开发 #### 概述 本文档将详细介绍如何使用C++ Winsock库来开发TCP非阻塞服务器。通过本篇内容的学习,您将了解到设置socket函数为非阻塞模式的方法,并且能够深入了解...
在本文中,我们将探讨异步非阻塞套接字在Winsock编程中的应用,这是开发网络通信程序的基础。异步非阻塞模式是Windows网络通信软件开发中的常见选择,尤其适用于C/S架构的软件。 首先,理解同步和异步、阻塞和非...
在软件开发领域,尤其是在涉及输入输出(IO)操作时,理解同步与异步、阻塞与非阻塞的概念是非常重要的。这些概念对于设计和实现高效的程序至关重要,尤其是在高并发和分布式系统中。 一、同步与异步 同步和异步是...
总之,通过这个使用AIO实现的非阻塞socket通信项目,我们可以学习到如何利用Java AIO进行高效的网络编程,理解和实践异步I/O模型,这对于构建高性能、高并发的网络应用至关重要。通过实际操作,你可以更好地理解非...
在IT行业中,网络通信是不可或缺的一部分,而基于异步I/O(Asynchronous Input/Output)的socket通信程序设计则是高效处理并发连接的关键技术。本文将深入探讨如何利用异步I/O进行socket通信,重点关注在Java中如何...
非阻塞Socket(Non-blocking Socket)则提供了另一种解决方案。在非阻塞模式下,当尝试读取数据但缓冲区为空,或者尝试写入但网络不可写时,recv()和send()函数并不会挂起,而是立即返回一个错误代码。开发者需要...
总结来说,C#的Socket异步通信是通过事件驱动、非阻塞的方式实现的,它提高了应用程序的效率,同时保持了良好的响应性。在VS2005中,我们可以方便地创建、调试和测试这样的异步Socket应用,为开发高效网络服务提供了...
阻塞和非阻塞Socket Socket 编程中有两种方式:阻塞(Blocking)和非阻塞(Non-Blocking)。阻塞 Socket 指的是在执行某个操作时,程序将等待该操作完成,而非阻塞 Socket 则可以继续执行其他操作,不会被阻塞。 ...
异步socket(又称非阻塞socket)允许程序在等待数据到达时继续执行其他任务,而不是被阻塞。当数据准备就绪时,操作系统会通知应用程序,此时程序可以处理数据。这种模式显著减少了CPU空闲时间,提高了系统整体效率...
异步Socket通信的核心在于非阻塞模式,这种模式下,当Socket调用无法立即返回结果时,程序不会被挂起,而是立即返回,然后通过事件或回调函数来通知结果。这种方式极大地提高了程序的响应性和效率,尤其适合处理大量...
下面,我们简要分析一下简单的异步Socket通信源码: - **创建ServerSocket**:服务器端首先创建一个ServerSocket,指定监听的端口号。 - **接受连接**:ServerSocket的accept()方法是非阻塞的,它会返回一个...
"socket_异步通信"意味着该模型使用了非阻塞的Socket操作,以实现高效的数据传输。 线程在这里扮演了关键角色。在多线程环境中,每个线程可以独立执行任务,这使得程序能同时处理多个请求。在Socket异步通信模型中...
这种非阻塞模式对于构建能够同时处理多个连接的服务器至关重要,因为它可以提高系统的并发性和响应性。 1. **异步Socket基本概念** - 异步操作:在C#中,异步操作通过事件驱动的方式实现,允许程序在等待操作完成...