一个简单的TCP/IP聊天客户端/服务器






1.38/5 (13投票s)
2004 年 11 月 3 日
3分钟阅读

189953

12519
这篇文章有助于理解简单的线程使用、TCP/IP网络以及XML的基本用法。
您可以在此处找到更新。
引言
这是一个简单的TCP/IP聊天示例。该解决方案由三个不同的项目组成。主要项目是“Listener”,您可以在其中找到该程序的核心。在Rete.cs文件中,包含使该程序工作的核心方法。
请注意,源代码注释(//)是用意大利语编写的。
最重要的类是rete.cs中的Poller
。在Main.cs中创建了一个Poller
对象,该对象用于连接、轮询和写入套接字。
在Main.cs中启动了一个线程,用于刷新GUI;该线程执行
public void StartGUIRefreshThread()
{
ThreadStart GuiRefreshDelegate=new ThreadStart(this.GUIRefreshThread);
GUIRefresh= new Thread(GuiRefreshDelegate);
GUIRefresh.Name="GUIRefresh";
GUIRefresh.Start();
}
这是一个使用线程的简单示例。启动一个新线程意味着在“同一时间”运行代码的不同部分。当一个线程正在运行循环时,“主程序”(它是另一个线程)会执行其他指令。
ThreadStart
对象包含一个指向线程启动时要运行的方法的引用。如果该函数不包含循环,线程将在一段时间后停止。如果引用的方法包含无限循环,线程将一直运行直到应用程序停止。
GUIRefresh.Start();
执行“this.GUIRefreshThread()
”方法,如“new ThreadStart(this.GUIRefreshThread);
”中所指定的。
我的“StartGUIRefreshThread
”方法启动了一个包含无限循环的线程
public void GUIRefreshThread()
{
int localDelay, totalDelay=1200, newDataPresentIconDelay=300;
while (true)
{
...
Thread.Sleep(localDelay);
}
}
这会检查Poller
的状态,并向用户显示相应的输出。
Poller
对象处理TCP连接,使用“原始”Socket
对象,如果当前应用程序作为客户端运行,则使用“InizializzaTX()
”方法进行初始化;如果应用程序作为服务器(监听器)运行,则使用“InizializzaRX
”进行初始化。
服务器情况
public bool InizializzaRX()
{
if (this.IsListen)
this.FermaListening();
this.ListenSocket= new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
try
{
this.ListenSocket.Bind(LIPE);
this.ListenSocket.Listen(10);
this.IsListen=true;
return true;
}
...
此代码创建一个新套接字(this.ListenSocket
),该套接字绑定到监听IP地址和端口(LIPE)。
现在套接字已正确配置,但它不是“活动”的。我们必须使用<Socket>.Accept()
方法启动它以开始监听,该方法将套接字置于“等待状态”以接收客户端连接。
<Socket>.Blocking
属性决定执行是否会停止等待客户端连接(true
=停止)。我使用的是blocking=true
。请注意,如果主线程使用blocking=true
,您的应用程序将挂起直到接受连接。
private void PollingRX()
{
string s="Inizio";
while (s!=MSGTag.ConnectionStop+"\n")
{
if (this.UsedSocket==null&&this.ListenSocket!=null)
{
this.DebugRefresh("PollingRX: fermo in accept() su"+ this.LIPE.Port);
this.IsListen=true;
this.UsedSocket=this.ListenSocket.Accept();
this.DebugRefresh("PollingRX: Accept() eseguita");
this.IsConnectedAsRX=true;
//Comunica il nome al client, come in InizializzaTX()
//il client comunica il suo nome al server
this.PushString(MSGTag.UserName/*+"Server "*/+this.LocalName);
this.RTM.RemoteIP=this.UsedScocket.RemoteEndPoint.ToString();
this.RTM.LocalIP=this.LIPE.ToString();
//Non uso UsedScocket.LocalEndPoint xchè non è definito l'IP locale
this.ControlClear();
}
s=this.CheckAndRetrive();
int n=0;
if (s!=null)
n=s.Length;
this.DebugRefresh("PollingRX: eseguito ("+n+" bytes)");
System.Threading.Thread.Sleep(500);
}
this.DebugRefresh("PollingRX: ricevuto EXIT");
this.KillRX();
}
当客户端建立连接时,Accept()
会返回一个与客户端连接的新套接字,用于传输数据,使用我的CheckAndRetrive()
(从套接字读取)和PushString()
(写入套接字)方法。有关读取相关代码,请参见下文。
此循环将继续,直到从客户端接收到“停止字符串”。
客户端情况
public void InizializzaTX()
{
if ((!this.IsConnected))
{
try
{
this.UsedScocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
this.UsedScocket.Connect(this.RIPE);
this.IsConnectedAsTX=true;
this.PushString(MSGTag.UserName+/*"Client "+*/this.LocalName);
this.ControlClear();
//E'= a RIPE
this.RTM.RemoteIP=this.UsedScocket.RemoteEndPoint.ToString();
this.RTM.LocalIP=this.UsedScocket.LocalEndPoint.ToString();
}
...
这是RX的对等操作:创建一个新套接字,并建立与远程IP和端口(RIPE)的连接。
private void PollingTX()
{
string s="inizio";
while (s!=MSGTag.ConnectionStop+"\n")
{
if (this.UsedScocket!=null)
{
s=this.CheckAndRetrive();
}
int n=0;
if (s!=null)
n=s.Length;
this.DebugRefresh("PollingTX: eseguito("+n+" bytes)");
System.Threading.Thread.Sleep(500);
}
this.DebugRefresh("PollingTX: ricevuto EXIT");
this.KillTX();
}
与RX非常相似……一旦套接字连接成功,通信就是“对称”的。
读写数据
这是我如何从套接字获取字符串(CheckAndRetrive()
使用此函数从套接字获取数据)
public static string GetStringFromSocket(Socket s)
{
Byte[] recBytes=new Byte[10001];
int i=0;
string st=null;
if (s.Available>0)
{
i=s.Receive(recBytes,0,s.Available,SocketFlags.None);
char[] ac=Encoding.UTF8.GetChars(recBytes,0,i);
foreach (char c in ac)
{
st+=c.ToString();
}
}
return st;
}
套接字必须是一个已连接的套接字;您必须使用字节数组来存放接收到的数据。然后,您必须使用一种编码机制对其进行编码,该机制与解码数据使用的机制相同。我使用UTF8编码。
这会从套接字读取最多10001字节的数据块。要读取更多字节,需要一个循环,类似于
while (s.Available>0){...}
这是如何将字符串放入套接字
public static int PutStringInSocket(Socket s, string msg)
{
Byte[] sendBytes=Encoding.UTF8.GetBytes(Framing.IncapsulaMSG(msg));
if (s.Connected)
return s.Send(sendBytes);
else return 0;
}
Framing.IncapsulaMSG()
仅仅是一个方法,它将标签写入消息中,使其易于解释,并返回一个字符串。
轮询线程
在Poller
内部执行其他线程,负责通过TCP连接接收/检查和发送数据。PollingRX()
和PollingTX()
中的循环在两个不同的线程中运行。
public bool StartPollingTX()
{
try
{
if (!this.IsRunningPollingTX)
{
ThreadStart TXPollingDelegate = new ThreadStart(this.PollingTX);
TXPolling= new Thread(TXPollingDelegate);
TXPolling.Name="TXPolling";
TXPolling.Start();
this.IsRunningPollingTX=true;
this.DebugRefresh("PollingTX: (Start) eseguito");
}
return true;
}
catch
{
return false;
}
}
这是一个显示“传输”线程如何启动的示例。(它运行上面描述的“this.PollingTX
”。设置选项卡存储在单独的DLL中,配置实用程序也是如此。设置存储在XML文件中。
我知道有很多不完善的地方,但这仅仅是为了举例。
我希望您喜欢这篇文章。