精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
In the previous article, I discussed a chat application using TCP sockets. In this part, I will show how to do it using UDP sockets.
在前一篇文章中,我讨论了一个使用TCP套接字聊天应用程序。在本部分中,我将展示如何使用UDP套接字。
TCP is connection oriented, and provides error and flow control. UDP provides no such services, and relies on the application layer for them. UDP allows sending a packet with or without checksum; no connection is maintained, so each packet is sent independently. If a packet gets lost or the packets arrive out of order, then the application should detect and remedy the situation on its own. Also, UDP doesn’t give the security features of TCP like the three-way handshake.
TCP是面向连接的,并提供错误和流量控制。 UDP不提供这样的服务,并且依赖于它们的应用程序层上。UDP允许发送的数据包,或无校验;没有保持连接,所以每个数据包独立传送。如果一个数据包丢失或数据包到达的顺序混乱,那么应用程序应检测和依赖自己纠正此问题。此外,UDP不提供安全功能,如TCP的三次握手。
So what is in UDP that TCP doesn’t do? Firstly, UDP supports multicast – sending a single packet to multiple machines. This is useful as it saves bandwidth, each packet is transmitted only once and the entire network receives it. UDP is also used in places where the overhead (delay) involved with TCP is expensive.
那么,什么是UDP有而TCP没有的?首先,UDP支持组播 - 发送一个数据包到多台机器。这是有用的,因为它节省了带宽,各分组仅发送一次和整个网络接收它。 UDP也用于下述场合:TCP所导致的开销(延迟)是昂贵的。
Some applications of UDP are in VoIP, streaming of audio and video, DNS, TFTP, SNMP, online gaming, and etcetera.
UDP的有些应用程序在网络电话,音频和视频,DNS,TFTP,SNMP,在线游戏,以及诸如此类的流。
Asynchronous UDP sockets have a Begin and End appended to the standard socket functions, like BeginSendTo
, BeginReceiveFrom
, EndSendTo
, and EndReceiveFrom
. Let's take a look at one of them:
异步UDP套接字有开始和结束附加到标准插座的功能,如开始的SendTo,BeginReceiveFrom,最终的SendTo和EndReceiveFrom。让我们来看看其中之一:
IAsyncResult BeginReceiveFrom(byte[] buffer, int offset, int size,
SocketFlags sockflag, ref EndPoint ep, AsyncCallback callback, object state)
The BeginReceiveFrom()
method accepts data from any remote host on a connectionless socket. Notice that the BeginReceiveFrom()
method is similar to the BeginReceive()
method, except that it specifies a reference to an EndPoint
object. The EndPoint
object defines the remote host IP address and the port number that sent the data.
该BeginReceiveFrom()方法从一个连接的套接字任何远程主机接受的数据。注意,BeginReceiveFrom()方法是类似于BeginReceive()方法,不同之处在于它指定端点对象的引用。端点对象定义远程主机的IP地址和发送数据的端口号。
The AsyncCallback
function is called when the function completes. Just as events can trigger delegates, .NET also provides a way for methods to trigger delegates. The .NET AsyncCallback
class allows methods to start an asynchronous function and supply a delegate method to call when the asynchronous function completes.
该函数完成时的AsyncCallback函数被调用。正如事件可以触发代表,.NET也提供了方法来引起与会代表的一种方式。在.NET的AsyncCallback类允许的方法来启动异步功能,并提供一个委托方法异步函数完成时调用。
The state object is used to pass information between the BeginAccept
and the corresponding AsyncCallback
function.
使用状态对象之间传递信息 BeginAccept 和相应的 AsyncCallback 函数。
A sample BeginReceiveFrom()
method would look like this:
一个示例 BeginReceiveFrom() 方法是这样的:
sock.BeginReceive(data, 0, data.Length, SocketFlags.None,
ref iep, new AsyncCallback(ReceiveData), sock);
The corresponding EndReceiveFrom()
method is placed in the appropriate AsyncCallback
method:
相应的 EndReceiveFrom() 被放置在适当的方法 AsyncCallback 方法:
void ReceiveData(IasyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
int recv = remote.EndReceiveFrom(iar);
string stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
}
The EndReceiveFrom()
method returns the number of bytes read from the socket, and places the received data in the data buffer defined in the BeginReceiveFrom()
method. To access this data, the data buffer should be accessible from both the methods.
EndReceiveFrom() 方法返回从套接字读取的字节数,并将接收到的数据在数据缓冲区中定义 BeginReceiveFrom() 方法。 访问这些数据,应该可以从数据缓冲区的方法。
The architecture of the application using TCP sockets and the one using UDP sockets is very similar. Both applications use the same data structures to communicate between the server and the client.
该应用程序的架构,使用TCP套接字和一个使用UDP套接字是非常相似的。 两个应用程序使用相同的数据结构的服务器和客户端之间的通信。
For exchange of messages between the client and the server, both of them use the following trivial commands:
对于客户机和服务器之间的消息交换,他们使用以下简单的命令:
//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
}
The data structure used to exchange between the client and the server is shown below. Sockets transmit and receive data as an array of bytes, the overloaded constructor and the ToByte
member function performs this conversion.
数据结构用于在客户机和服务器之间交换如下所示。 套接字传输和接收数据的字节数组,重载的构造函数和 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;
}
The following are some of the data members used by the server application:
以下是一些服务器应用程序所使用的数据成员:
//The ClientInfo structure holds the
//required information about every
//client connected to the server
struct ClientInfo
{
//Socket of the client
public EndPoint endpoint;
//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];
One important point to note here is that, in UDP, there is no such distinction between the client and server applications. Unlike TCP, UDP servers don’t listen for incoming clients; they just look for data coming from other clients. Once we receive data, we see if it’s a message for login, logout, etc.
很重要的一点需要注意的是,在UDP,没有这样的客户端和服务器应用程序之间的区别。 与TCP、UDP服务器侦听传入的客户,他们不只是寻找数据来自其他客户。 一旦我们收到数据,我们看看它的信息登录,注销等。
private void Form1_Load(object sender, EventArgs e)
{
try
{
//We are using UDP sockets
serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
//Assign the any IP of the machine and listen on port number 1000
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 1000);
//Bind this address to the server
serverSocket.Bind(ipeServer);
IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0);
//The epSender identifies the incoming clients
EndPoint epSender = (EndPoint) ipeSender;
//Start receiving data
serverSocket.BeginReceiveFrom (byteData, 0, byteData.Length,
SocketFlags.None, ref epSender,
new AsyncCallback(OnReceive), epSender);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSServerUDP",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
With IPAddress.Any
, we specify that the server should accept client requests coming on any interface. To use any particular interface, we can use IPAddress.Parse (“192.168.1.1”)
instead of IPAddress.Any
. The Bind
function then bounds the serverSocket
to this IP address. The epSender
identifies the clients from where the data is coming.
与 IPAddress.Any ,我们指定该服务器应该接受客户端请求来在任何界面。 使用任何特定的接口,我们可以使用 IPAddress。 解析(“ 192.168 。 1.1 ”) 而不是 IPAddress.Any 的 绑定 函数然后界限 考察一下 这个IP地址。 的 epSender 识别客户的数据。
With BeginReceiveFrom
, we start receiving the data that will be sent by the client. Note that we pass epSender
as the last parameter of BeginReceiveFrom
, the AsyncCallback OnReceive
gets this object via the AsyncState
property of IAsyncResult
, and it then processes the client requests (login, logout, and send message to the users). Please see the code attached to understand the implementation of OnReceive
.
与 BeginReceiveFrom ,我们开始接受将由客户端发送的数据。 注意,我们通过 epSender 作为最后一个参数 BeginReceiveFrom , AsyncCallback OnReceive 获得通过这个对象 AsyncState 的属性 IAsyncResult ,然后处理客户机请求(登录、注销和发送消息给用户)。 请参阅附上代码理解的实现 OnReceive。
Some of the data members used by the client are:
由客户机使用的一些数据成员是:
public Socket clientSocket; //The main client socket
public string strName; //Name by which the user logs into the room
public EndPoint epServer; //The EndPoint of the server
byte []byteData = new byte[1024];
The client firstly connects to the server:
客户端首先连接到服务器:
try
{
//Using UDP sockets
clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
//IP address of the server machine
IPAddress ipAddress = IPAddress.Parse(txtServerIP.Text);
//Server is listening on port 1000
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 1000);
epServer = (EndPoint)ipEndPoint;
Data msgToSend = new Data ();
msgToSend.cmdCommand = Command.Login;
msgToSend.strMessage = null;
msgToSend.strName = strName;
byte[] byteData = msgToSend.ToByte();
//Login to the server
clientSocket.BeginSendTo(byteData, 0, byteData.Length,
SocketFlags.None, epServer,
new AsyncCallback(OnSend), null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "SGSclient",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
The client after connecting to the server sends a login message. And, after that, we send a List message to get the names of clients in the chat room.
客户端连接到服务器发送登录信息。 在那之后,我们发送消息列表获取客户的名字在聊天室。
//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;
byte[] byteData = msgToSend.ToByte();
//Send it to the server
clientSocket.BeginSendTo (byteData, 0, byteData.Length,
SocketFlags.None, epServer,
new AsyncCallback(OnSend), null);
txtMessage.Text = null;
}
catch (Exception)
{
MessageBox.Show("Unable to send message to the server
"SGSclientUDP: " + strName, MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
The message typed by the user is sent as a Command message to the server which then sends it to all other users in the chat room.
消息类型的用户发送一个命令消息到服务器,然后将其发送给所有其他用户在聊天室。
Upon receiving a message, the client processes it accordingly (depending on whether it’s a login, logout, command, or a list message). The code for this is fairly straightforward, kindly see the attached project.
当收到一条消息时,客户端相应处理它(取决于是否登录,注销,命令,或列表消息)。 这个相当简单的代码,请参阅附上的项目。
As you would have noticed, the UDP chat application is not very different from the TCP one; in fact, converting the TCP one into UDP was less than a day's work for me. I hope you find these articles useful. Thank you.
您会注意到,UDP与TCP聊天应用程序差异不大;事实上,TCP转化为UDP的工作不到一天完成。 我希望你会发现这些文章有用。 谢谢你!