Socket通信总结(C#)
最近参与了一个项目,里面用到了socket,串口通信和GDI+等等,本以为socket是里面比较容易的部分,结果到项目结束,里面问题出得最多的就是socket,个人感觉C#里面的socket类过于简单了,很多问题不得不靠开发人员的实际经验去解决,不过到最后项目好歹是完成了,现在把经验总结一下,希望大家以后能够少走点弯路。完整的代码(我不是代码的作者)我就不提供了,我只讲讲设计思路,如果需要完整代码的可以去参考这两篇文章:
Socket编程基础:http://www.cnblogs.com/playboy840616/archive/2007/02/15/651077.html
异步socket通信总结:http://blog.csdn.net/lnheel/archive/2009/10/31/4751443.aspx
至于什么是socket,socket的通信原理是什么这类基础问题,不在本文讨论范围之内,请读者自行查阅相关资料。
(一) 基础篇
要使用socket,首先需要要知道socket的基础通讯方法:
客户端代码的基本思路就是先建立连接,然后用一个字节缓冲区接收数据。
连接:
socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPoint ip = new IPEndPoint(IPAddress.Parse(Environment.IP), Environment.Port);
socket.Connect(ip);
数据接收(服务端类似):
if (socket != null)
{
string data = string.Empty;
byte[] bytes = new byte[1024];
int bytesRec = this.socket.Receive(bytes);
if (bytesRec == 0) { }
data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
}
服务端先启动监听,一旦有客户端连接,就会获得连接,发送数据。
启动监听:
int port = Convert.ToInt32(cfg.GetServerParm().Port);
//byte[] ips = { Convert.ToByte(ipt[0]), Convert.ToByte(ipt[1]), Convert.ToByte(ipt[2]), Convert.ToByte(ipt[3]) };
//IPAddress ip = new IPAddress(ips);o
IPEndPoint ip = new IPEndPoint(long.Parse("0"), port);
if (listener == null)
listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(ip);
listener.Listen(20);
获得连接:
Socket clientSocket = listener.Accept();
发送数据:
byte[] sceneData = Encoding.ASCII.GetBytes(sceneMsg);
clientSocket.Send(sceneData, sceneData.Length, 0);
上面这些代码就是socket的基础通信代码(直接从项目里面摘抄下来的,可能有些命名比较怪异),但是仅仅靠这些代码是完全不够的,需要对服务端和客户端的各个通信阶段进行改写,才能完成一些基本任务。下面探讨socket的同步和异步通信问题。
(二) 进阶篇
从服务端的监听模式来看,socket通信似乎更适合异步,因为服务端只监听某个端口,而并不关心连接是否存在。但是有些时候我们需要知道连接的状态,特别是当一个服务端连接多个客户端的时候,甚至还需要根据连接来辨识客户端的ID,否则服务端无法向多个客户端主动发起通信要求。那么,怎么解决这个问题呢?首先,我们需要用一个线程来管理一个连接,这样多个客户端建立的连接就能够在一个服务端并行不悖。
RemoteClient wapper = new RemoteClient(clientSocket);
Thread wapperThread = new Thread(new ThreadStart(wapper.ReceivedFromClient));
wapperThread.Start();
如上述代码,每建立一个连接就建立一个线程对该连接进行管理,并且标识该连接。但是问题来了,你并不知道过了一段时间之后,这个连接是否存在,客户端可能因为其他原因把连接关掉了,但是服务端这个线程还存在,因此在客户端也需要用一个线程来保持与服务端的同步,只要客户端的这个线程存在,就能够不断接受从服务端发来的消息,一旦线程结束,它就通知服务端连接结束。在下面的代码中,isConnected用来标识连接状态,isLogin用来标识数据接收。在客户端上的过程是这样的,客户端首先打开一个线程,保持连接状态,一旦连接建立,开启一个新的线程接收数据,只要login状态还在,客户端就能持续地从服务端接收数据。相当于客户端用一个线程“监听”了连接状态。
public void KeepAlieve()
{
while (true)
{
while (!isConnected)
{
try {
socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPoint ip = new IPEndPoint(IPAddress.Parse(Environment.IP), Environment.Port);
socket.Connect(ip);
//socket.Connect("localhost", 8500);
if (socket.Connected)
{
isConnected = true;
isLogin = false;
}
}
catch(Exception ex) {
Thread.Sleep(100);
isConnected = false; }
}
while (!isLogin)
{
try
{
isLogin = true;// 开a Recive线?程¨¬
string msg = string.Format("<MSG CNo=/"{0}/" Type=/"Login/" SceneName=/"/" Time=/"{1}/"></MSG>",Environment.CNo,Comm.ToTinyDateTime(DateTime.Now));
byte[] data = Encoding.ASCII.GetBytes(msg);
socket.Send(data,data.Length,0);
receiveThread = new Thread(new ThreadStart(Received));
receiveThread.Start();
}
catch { isConnected = false; isLogin = true; }
}
//ts = DateTime.Now.Subtract(lastReceiveTime);
//if (ts.Seconds > 30)
//{
// isConnected = false;
// isLogin = false;
// lastReceiveTime = DateTime.Now;
//}
}
}
相应的,服务端每获得一个连接就创建一个RemoteClient,分配一个线程来管理这个连接,然后将该连接加入一个列表,如果连接结束,就将该连接移出列表。
连接维护
public void ReceivedFromClient()
{
try
{
while (clientSocket != null)
{
string data = string.Empty;
byte[] bytes = new byte[1024];
int bytesRec = this.clientSocket.Receive(bytes);
if (bytesRec == 0) { LeftJoin(); }
data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
switch (node.Attributes["Type"].Value.ToString().ToUpper())
{
case"LOGIN":
Cno = node.Attributes["CNo"].Value.ToString();
Join();
string scuMsg = string.Format("<MSG CNo=/"{0}/" Type=/"SuccessConn/" SceneName=/"/" Time=/"{1}/"></MSG>",Cno,Comm.ToTinyDateTime(DateTime.Now));
byte[] back = Encoding.ASCII.GetBytes(scuMsg);
ClientSocket.Send(back, back.Length, 0);
break;
case "LOGOUT":
break;
}
}
}
catch (Exception ex) { LeftJoin(); }
}
值得注意的是,在维护连接表的时候,加入和删除是不能同时进行的,必须要将二者设为互斥操作。
public void Join()
{
//IList<RemoteClient> tempList = WinformTest.MainWindow.connectionList;
bool bNeedAdd = true;
lock (WinformTest.MainWindow.connectionList)
{
foreach (var item in WinformTest.MainWindow.connectionList)
{
if (item.CNo == this.Cno)
{
//item._Socket = ClientSocket;
bNeedAdd = false;
break;
}
}
if (bNeedAdd)
{
WinformTest.ClientSocketStruce css = new WinformTest.ClientSocketStruce();
css.CNo = Cno;
css._Socket = ClientSocket;
WinformTest.MainWindow.connectionList.Add(css);
}
}
}
public void LeftJoin()
{
lock (WinformTest.MainWindow.connectionList)
{
foreach (var item in WinformTest.MainWindow.connectionList)
{
if (item.CNo == Cno)
{
WinformTest.MainWindow.connectionList.Remove(item);
break;
}
}
}
}
这样,借助线程就解决了socket服务端和客户端的同步问题,还有其他一些细节问题,如结束线程,数据接收事件等就不一一列出了。希望能给大家带来参考。