看到那么多人支持,我很感动,所以临时决定从今天开始讲述如何编写IM。
那么今天开始第一讲, Socket.
为什么上来就讲Socket呢?因为我觉得作为一个对于IM很感兴趣的人,应该瞬间提升对于网络编程的高度认知,只有这样才能快速的学到东西。
那么好了,开始实战!
Socket又分为异步套接字和同步套接字,我在项目中基本上都是用的同步(当然,您可以使用异步),然后自己New的线程,这样的话,我感觉有几点好处。
1. 对于线程拥有更加的认知度。
2. 启用了自己制作的线程池(ThreadPool <-这个我自己写了个)
那么先说线程,Socket监听一般是需要2个While--true的,这个也是通用写法,貌似很多学校也这样教?
那么下面进入代码时间,我先来粘贴出一段有问题的代码(服务端部分):
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
while (_IsListen) //<----监听标示
{
Listener.Start(); //一直监听
//尝试接受连接
this.socket = Listener.AcceptSocket(); //获得连接信息
//MessageBox.Show("有人连接了:Socket Port:" + this.socket.RemoteEndPoint.ToString());
//如果此套接字已经连接上
if (this.socket.Connected)
{
while(true)
{
//do sth
}
}
}
catch(Exception ex)
{
}
上面这段代码减去了没必要的部分,但是足足可以说明问题,上面这段代码运行是没有问题的,而且能监听指定端口。
不知道您看出问题了吗?
慢慢的,你就会发现,这段代码有问题了,而且有大问题,因为当我有1个用户上线并且成功与服务器连接后,再来一个用户上线,那么第一个
用户便不会再与服务器进行任何响应了,这是为什么呢?
呵呵 ,可能有人看出来了?服务器第一个While循环为了得到用户的SOCKET,当得到后,传递到第二个While,这样一来,就会一直在第二个While循环中
出不来了……而且整个窗体会卡住。
那么好,我们改下,比如现在把第二个While用多线程处理,看代码(服务端部分):
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public void Lis_Port()
{
Listener = new TcpListener(Globle._OnLinePort); //默认上线端口:10000
try
{
Globle._isRuning = 1; //开启启动
this.Main_f.BeginInvoke(new Pt(Main_f.Update_TM_Run));
this.Main_f.BeginInvoke(new Pt(Main_f.Update_State));
while (_IsListen)
{
Listener.Start(); //一直监听
//尝试接受连接
this.socket = Listener.AcceptSocket(); //获得连接信息
//MessageBox.Show("有人连接了:Socket Port:" + this.socket.RemoteEndPoint.ToString());
//如果此套接字已经连接上
if (this.socket.Connected)
{
Thread thread = new Thread(new ThreadStart(this.Rev));
thread.Start();
}
}
}
catch(Exception ex)
{
Globle._isRuning = 0; //停止启动
this.Main_f.BeginInvoke(new Pt(Main_f.Update_State));
//MessageBox.Show("端口监听出现错误:" + ex.Message);
}
}
#endregion
#region 此方法用于监听用户命令
/*
* 此方法用于监听用户命令
*/
public void Rev()
{
while (_IsListen)
{
try
{
byte[] bb = new byte[512];
//如果此套接字已经连接上
if (this.socket.Connected)
{
Stream = new NetworkStream(this.socket); //获取用户流信息
Stream.Read(bb, 0, bb.Length); //读取内容
Stream.Flush(); //清空
//MessageBox.Show(Encoding.Default.GetString(bb));
this.Rev_Order = (Encoding.Default.GetString(bb)).Replace("\0", ""); //转换成字符串
String[] Orders_sp = this.Rev_Order.Split(new String[] { "$$" }, StringSplitOptions.RemoveEmptyEntries);
//如果得到的命令不是登录后的信息传递
if (Orders_sp.Length <= 1)
{
String[] Orders = this.Rev_Order.Split(new String[] { "|**|" }, StringSplitOptions.RemoveEmptyEntries);
if (Orders.Length <= 1)
{
String Ip = IPAddress.Parse(((IPEndPoint)this.socket.RemoteEndPoint).Address.ToString()).ToString();
Rev2Client_SendMessage Rev = new Rev2Client_SendMessage(Stream, this.Rev_Order, Ip);
}
catch (Exception ex)
{
MessageBox.Show("显示用户上线的时候出现错误:" + ex.Message);
}
}
//QQ注册
else
{
//do sth
}
}
else
{
//do sth
}
}
else
{
//MessageBox.Show("套接字已经断开了");
}
}
}
}
呵呵 太多了,我就不精简了,总体来说,这次是用了多线程,将第二个While包在了方法体内,
然后第二个While监听全局的Socket。
那么这样一来,是不是比刚才好了呢???
但是我告诉你,这段代码还是有问题,而且是一个大问题,你看出来了吗?
那么,这段代码运行后,出现的问题是,虽然窗体不卡了,但是,当第二个用户登录到服务器后,第一个用户同样还是
无法与服务器产生任何响应了!!
这是为什么呢??
让我们来仔细研究代码,当地一个用户连接服务器的时候,服务器得到了第一个用户的Socket且把它赋值到全局
然后第二个While就会循环监听这个全局的Socket,那么,当第二个用户连接服务器的时候,还是按照规矩,将第二个用户的Socket
赋值全局,这样问题就来了!!第一个用户的Socket不是就被覆盖了吗?是的!!这个就是问题所在。
那么我现在给出本次讲解中,真正正确的解决方案,同时这个方案也被包含在了我自己的通讯框架中。
我们需要2个类。第一个类负责监听端口,(当然了还是While,但是要运行在多线程中,否则会卡屏哦),当接受了用户连接后,
得到用户连接,开始实例化第二个类,我管第二个类叫做"MessageListener",第一个类叫做"PortListener",那么我的第二个类构造是这样写的(简单版本)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public MessageListener(Socket UserSocket)
{
}
这样一来,实例化的时候,传递当前得到的用户SOCKET,然后调用MessageListener的Start()方法就可以了。
MessageListener的Start()方法(简单版本)无非就是这样:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public void Start()
{
Thraed thread = new Thread(new ThreadStart(this.INNER_LISTEN));
thread.Start();
}
调用了内部的私有方法private void INNER_LISTENER()来进行While循环。
以上是简单版本的解决方案。下面提供了我的UML框架部分截图 (我的框架支持UDP和TCP 2种)
大家先看下,作为了解:
好了,截图了我的UML框架作为结尾,是再好不过了,新手作为了解,老鸟作为补习。
如果有错的地方 请勿见怪(以上代码实例,来自我的N年前接触网络时的处女作品)。