使用异步 TCP 套接字的聊天应用程序






4.90/5 (62投票s)
2006年12月28日
3分钟阅读

491011

33114
本文将讨论一个使用 C# 中的异步 TCP Socket 的聊天应用程序。
引言
在本文中,我将讨论一个使用 C# 中的异步 TCP Socket 的聊天应用程序。 在本文的下一部分中,我将介绍一个基于异步 UDP Socket 的聊天应用程序。
TCP 异步 Socket
TCP 异步 Socket 在标准 Socket 函数后面附加了Begin 和 End,例如 BeginConnect
、BeginAccept
、BeginSend
和 BeginReceive
。 让我们来看其中一个。
IAsyncResult BeginAccept(AsyncCallback callback, object state);
当函数完成时,会调用 AsyncCallback
函数。 就像事件可以触发委托一样,.NET 也提供了一种方法让方法触发委托。.NET AsyncCallback
类允许方法启动异步函数,并提供一个委托方法,以便在异步函数完成时调用。
state
对象用于在 BeginAccept
和相应的 AsyncCallback
函数之间传递信息。
以下展示了它的实现:
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9050);
sock.Bind (iep);
sock.Listen (5);
sock.BeginAccept (new AsyncCallback(CallAccept), sock);
private void CallAccept(IAsyncResult iar)
{
Socket server = (Socket)iar.AsyncState;
Socket client = server.EndAccept(iar);
}
原始的 sock
对象在 CallAccept
中通过使用 IAsyncResult
的 AsyncState
属性来检索。 一旦我们有了原始的 Socket(操作在其上完成),我们就可以获取客户端 Socket 并对其执行操作。
所有其他的异步 Socket 函数都像这样工作,您应该在 MSDN 上查找更多详细信息。网上有一些不错的文章解释了这些细节,也请您搜索一下。
入门
为了在客户端和服务器之间交换消息,它们都使用以下简单的命令:
//The commands for interaction between the server and the client
enum Command
{
//Log into the server
Login,
//Logout of the server
Logout,
//Send a text message to all the chat clients
Message,
//Get a list of users in the chat room from the server
List
}
用于在客户端和服务器之间交换数据的数据结构如下所示。 Socket 以字节数组的形式发送和接收数据;重载的构造函数和 ToByte
成员函数执行此转换。
//The data structure by which the server and the client interact with
//each other
class Data
{
//Default constructor
public Data()
{
this.cmdCommand = Command.Null;
this.strMessage = null;
this.strName = null;
}
//Converts the bytes into an object of type Data
public Data(byte[] data)
{
//The first four bytes are for the Command
this.cmdCommand = (Command)BitConverter.ToInt32(data, 0);
//The next four store the length of the name
int nameLen = BitConverter.ToInt32(data, 4);
//The next four store the length of the message
int msgLen = BitConverter.ToInt32(data, 8);
//Makes sure that strName has been passed in the array of bytes
if (nameLen > 0)
this.strName = Encoding.UTF8.GetString(data, 12, nameLen);
else
this.strName = null;
//This checks for a null message field
if (msgLen > 0)
this.strMessage = Encoding.UTF8.GetString(data,
12 + nameLen, msgLen);
else
this.strMessage = null;
}
//Converts the Data structure into an array of bytes
public byte[] ToByte()
{
List<byte> result = new List<byte>();
//First four are for the Command
result.AddRange(BitConverter.GetBytes((int)cmdCommand));
//Add the length of the name
if (strName != null)
result.AddRange(BitConverter.GetBytes(strName.Length));
else
result.AddRange(BitConverter.GetBytes(0));
//Length of the message
if (strMessage != null)
result.AddRange(BitConverter.GetBytes(strMessage.Length));
else
result.AddRange(BitConverter.GetBytes(0));
//Add the name
if (strName != null)
result.AddRange(Encoding.UTF8.GetBytes(strName));
//And, lastly we add the message text to our array of bytes
if (strMessage != null)
result.AddRange(Encoding.UTF8.GetBytes(strMessage));
return result.ToArray();
}
//Name by which the client logs into the room
public string strName;
//Message text
public string strMessage;
//Command type (login, logout, send message, etc)
public Command cmdCommand;
}
TCP 服务器
服务器应用程序监听特定的端口并等待客户端。 客户端连接到服务器并加入聊天室。 然后客户端向服务器发送消息,服务器然后将此消息发送给聊天室中的所有用户。
服务器应用程序具有以下数据成员:
//The ClientInfo structure holds
//the required information about every
//client connected to the server
struct ClientInfo
{
//Socket of the client
public Socket socket;
//Name by which the user logged into the chat room
public string strName;
}
//The collection of all clients logged
//into the room (an array of type ClientInfo)
ArrayList clientList;
//The main socket on which the server listens to the clients
Socket serverSocket;
byte[] byteData = new byte[1024];
以下是它如何开始监听客户端并接受传入的请求:
private void Form1_Load(object sender, EventArgs e)
{
try
{
//We are using TCP sockets
serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
//Assign the any IP of the machine and listen on port number 1000
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 1000);
//Bind and listen on the given address
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(4);
//Accept the incoming clients
serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSserverTCP",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
使用 IPAddress.Any
,我们指定服务器应接受来自任何接口上的客户端请求。 要使用任何特定的接口,我们可以使用 IPAddress.Parse (“192.168.1.1”)
而不是 IPAddress.Any
。 然后,Bind
函数将 serverSocket
绑定到此 IP 地址。 Listen
函数使服务器等待客户端,传递给 Listen
的值指定操作系统将排队多少个传入的客户端请求。 如果有挂起的客户端请求,并且又有新的请求进入,则该请求将被拒绝。
以下是对应的 OnAccept
函数:
private void OnAccept(IAsyncResult ar)
{
try
{
Socket clientSocket = serverSocket.EndAccept(ar);
//Start listening for more clients
serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);
//Once the client connects then start
//receiving the commands from her
clientSocket.BeginReceive(byteData, 0,
byteData.Length, SocketFlags.None,
new AsyncCallback(OnReceive), clientSocket);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSserverTCP",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
EndAccept
返回连接客户端的 clientSocket
对象。 然后,我们再次通过调用 serverSocket.BeginAccept
开始监听更多传入的客户端请求;这是必不可少的,因为如果没有它,服务器将无法处理任何其他传入的客户端请求。
通过 BeginReceive
,我们开始接收客户端将要发送的数据。 请注意,我们将 clientSocket
作为 BeginReceive
的最后一个参数传递;AsyncCallback OnReceive
通过 IAsyncResult
的 AsyncState
属性获取此对象,然后处理客户端请求(登录、注销以及向用户发送消息)。 请参阅附加的代码以了解 OnReceive
的实现。
TCP 客户端
客户端使用的一些数据成员是:
//The main client socket
public Socket clientSocket;
//Name by which the user logs into the room
public string strName;
private byte[] byteData = new byte[1024];
客户端首先连接到服务器。
try
{
//We are using TCP sockets
clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse(txtServerIP.Text);
//Server is listening on port 1000
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 1000);
//Connect to the server
clientSocket.BeginConnect(ipEndPoint,
new AsyncCallback(OnConnect), null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSclient",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
连接后,将登录消息发送到服务器。 之后,我们发送一个列表消息以获取聊天室中客户端的名称。
//Broadcast the message typed by the user to everyone
private void btnSend_Click(object sender, EventArgs e)
{
try
{
//Fill the info for the message to be send
Data msgToSend = new Data();
msgToSend.strName = strName;
msgToSend.strMessage = txtMessage.Text;
msgToSend.cmdCommand = Command.Message;
byteData = msgToSend.ToByte();
//Send it to the server
clientSocket.BeginSend (byteData, 0, byteData.Length,
SocketFlags.None,
new AsyncCallback(OnSend), null);
txtMessage.Text = null;
}
catch (Exception)
{
MessageBox.Show("Unable to send message to the server.",
"SGSclientTCP: " + strName,
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
用户输入的消息作为命令消息发送到服务器,然后服务器将其发送给聊天室中的所有其他用户。
收到消息后,客户端会相应地处理它(取决于它是登录、注销、命令还是列表消息)。 此代码非常简单,请参阅附加的项目。