在第一版 使用C# 实现串口拨号器的SIM卡通信 的代码中,存在一些实际的问题,经过反复测试和实际使用,对原代码进行了一些改进。
首先,ㄟ荖樹炪厊ㄖ同学提出将拨号指令ATD发出后,不必使用 Thread.Sleep(20 * 1000) 方法等待20秒后进行挂机,而改用AutoResetEvent来处理,不必让线程死等,也能提高你程序的性能。但修改后效果并不理想,还是使用Thread.Sleep(20 * 1000) 方法快捷实用。
其次,由于拨号器以及服务器等硬件设备的差异,导致反馈信息的速度不一致,以前采用Thread.Sleep() 方法绑定固定秒数然后查看返回信息的方式存在一定的问题,改用如下代码就行:
int i = 0;
while (read.Trim() == "" && i <15)
{
Thread.Sleep(100);
i++;
}
if (read.Contains("ATH") && read.Contains("OK"))
{ ...... }
其余的不详细介绍,看代码即可。
MySerialPort 类
对每一个连接到COM 串行端口的拨号器实例化 MySerialPort 对象,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;
namespace Con_SerialPortExample
{
public class MySerialPort
{
private SerialPort com;
public MySerialPort(string _portName)
{
this.com = new SerialPort();
//this.autoResetEvent_HasModem = new AutoResetEvent(true);
//this.autoResetEvent_Dialing = new AutoResetEvent(false);
//接收数据事件
this.com.DataReceived += new SerialDataReceivedEventHandler(com_DataReceived);
//串口名
com.PortName = _portName;
this.PortName = _portName;
// BaudRate 串行波特率
com.BaudRate = 9600; //默认值
// 是否启用请求发送 (RTS) 信号。
com.RtsEnable = true; //由计算机发送 Request To Send 信号到联接的调制解调器,以请示允许发送数据。
// 是否使Data Terminal Ready (DTR)线有效。 xugang 2012.8.20 添加
//com.DtrEnable = true; //Data Terminal Ready 是计算机发送到调制解调器的信号,指示计算机在等待接受传输。
try
{
com.Open();
}
catch //(Exception)
{
Close();
}
}
public MySerialPort(string _portName, int _baudRate)
: this(_portName)
{
if (_baudRate != 0)
{
// BaudRate 串行波特率
com.BaudRate = _baudRate;
}
}
private string portName;
//串口名称
public string PortName
{
get { return portName; }
set { portName = value; }
}
// BaudRate 串行波特率
public int BaudRate
{
get { return com.BaudRate; }
set { com.BaudRate = value; }
}
private bool isWorking;
//设置是否正在使用
public bool IsWorking
{
get { return isWorking; }
set { isWorking = value; }
}
//private enum AutoResetEvent_Type
//{
// None, HasModem, Dialing
//}
//private AutoResetEvent autoResetEvent_HasModem;
//private AutoResetEvent autoResetEvent_Dialing;
//private AutoResetEvent_Type autoResetEvent_Type = AutoResetEvent_Type.None;
//private bool enableUse;
private DateTime lastUseTime = DateTime.MinValue;
//当前拨号器能否使用 (解决连续使用拨号问题)
public bool EnableUse
{
get
{
TimeSpan span = DateTime.Now - lastUseTime;
return span.TotalSeconds >= 60;
}
//set { enableUse = value; }
}
// 检测当前端口是否安装有拨号器
public bool HasModem()
{
//this.autoResetEvent_Type = AutoResetEvent_Type.HasModem;
//read = ""; //清空返回缓冲区
WriteSerial("AT\r\n");
//Thread.Sleep(500);
//this.autoResetEvent_HasModem.WaitOne(5 * 1000);
//this.autoResetEvent_Type = AutoResetEvent_Type.None;
int i = 0;
while (read.Trim() == "" && i < 40)
{
Thread.Sleep(100);
i++;
}
Console.WriteLine(read.TrimEnd('\r', '\n'));
//Console.Write(read);
//if (read.Contains("AT") && read.Contains("OK"))
if (read.Contains("OK"))
{
Console.WriteLine(this.com.PortName + "端口能使用!");
return true;
}
else
{
Console.WriteLine(this.com.PortName + "端口无法使用!");
return false;
}
}
//进行拨号,唤醒上位机
public void Dialing(string _SIM)
{
IsWorking = true; //正在拨号
try
{
//this.autoResetEvent_Type = AutoResetEvent_Type.Dialing;
read = ""; //清空返回缓冲区
StringBuilder sb = new StringBuilder(36);
sb.Append(this.com.PortName);
sb.Append("端口开始对SIM卡:");
sb.Append(_SIM);
sb.Append(" 进行拨号...");
Console.WriteLine(sb.ToString());
WriteSerial(string.Format("ATD{0};\r\n", _SIM));
System.Threading.Thread.Sleep(20 * 1000);
//this.autoResetEvent_Dialing.WaitOne(20 * 1000);
//this.autoResetEvent_Type = AutoResetEvent_Type.None;
read = ""; //清空返回缓冲区
WriteSerial("ATH\r\n");
//this.autoResetEvent.WaitOne(1000);
int i = 0;
while (read.Trim() == "" && i <15)
{
Thread.Sleep(100);
i++;
}
Console.WriteLine(read);
if (read.Contains("ATH") && read.Contains("OK"))
{
Console.WriteLine(this.com.PortName + "端口拨号已完成!");
}
else
{
read = ""; //清空返回缓冲区
//System.Threading.Thread.Sleep(1000);
WriteSerial("ATH\r\n");
//this.autoResetEvent.WaitOne(1000);
i = 0;
while (read.Trim() == "" && i < 20)
{
Thread.Sleep(100);
i++;
}
Console.WriteLine(read);
if (read.Contains("ATH") && read.Contains("OK"))
{
Console.WriteLine(this.com.PortName + "端口拨号已完成!");
}
else
{
//IsWorking = false; //拨号完成
string strExc = this.com.PortName + "拨号异常!";
Console.WriteLine(strExc);
throw new Exception(strExc);
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
IsWorking = false; //拨号完成
}
}
/// <summary>
/// 向串口端发送命令!
/// </summary>
/// <param name="s">命令字符串</param>
private void WriteSerial(string s)
{
//this.autoResetEvent_HasModem.WaitOne(20*1000);
//mLogger.Info(s);
byte[] buff = Encoding.ASCII.GetBytes(s);
try
{
this.com.Write(buff, 0, buff.Length);
}
catch (Exception ex)
{
//WriteExecLog.Writing(ex);
Console.WriteLine(ex.Message);
}
}
//int n = 0;
string read = "";
//接收数据事件方法
void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (sender is SerialPort)
{
try
{
Thread.Sleep(500);
read = ""; //清空返回缓冲区
SerialPort mySerial = sender as SerialPort;
if (mySerial.IsOpen == false)
{
mySerial.Open();
}
//方法一
//read += mySerial.ReadLine().Trim();
//Console.WriteLine(mySerial.PortName + " 第" + n.ToString() + "接收数据:" + read);
//方法二
byte[] readBuffer = new byte[1024];
int count = mySerial.Read(readBuffer, 0, 1023);
if (count > 0)
{
read = System.Text.Encoding.ASCII.GetString(readBuffer, 0, count);
//Console.WriteLine(read);
}
//AT 返回
//if (autoResetEvent_Type == AutoResetEvent_Type.HasModem
// && read.Contains("OK"))
//{
// this.autoResetEvent_HasModem.Set();
//}
//ATD 返回
//if (autoResetEvent_Type == AutoResetEvent_Type.Dialing
// && (read.Contains("ERROR") || read.Contains("BUSY") || read.Contains("OK") || read.Contains("NO CARRIER"))
// )
//{
// this.autoResetEvent_Dialing.Set();
//}
}
catch (TimeoutException)
{
return; //xg备忘:可以将异常写入日志!
}
catch (Exception)
{
return; //xg备忘:可以将异常写入日志!
}
//finally
//{
// this.autoResetEvent_HasModem.Set();
//}
}
}
//private string ReadSerial()
//{
// while (_keepReading)
// {
// if (com.IsOpen)
// {
// //byte[] readBuffer = new byte[com.ReadBufferSize + 1];
// byte[] readBuffer = new byte[10];
// try
// {
// //int count = com.Read(readBuffer, 0, com.ReadBufferSize);
// int count = com.Read(readBuffer, 0, 9);
// String SerialIn = System.Text.Encoding.ASCII.GetString(readBuffer, 0, count);
// if (count != 0)
// {
// return SerialIn;
// }
// }
// catch (TimeoutException)
// {
// return "";
// }
// }
// else
// {
// TimeSpan waitTime = new TimeSpan(0, 0, 0, 0, 50);
// Thread.Sleep(waitTime);
// }
// }
// return "";
//}
//关闭
public void Close()
{
if (com != null)
{
com.Close();
com.Dispose();
}
}
}
}
SerialPortList 类
定义一个 SerialPortList 类,实现对所有连接上的拨号器 MySerialPort 对象进行管理和调度使用。代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;
namespace Con_SerialPortExample
{
public class SerialPortList
{
//已经安装了拨号器的串口对象
private List<MySerialPort> al = null;
//可使用的拨号器个数
public int WorkableCount
{
get
{
if (al == null) return 0;
else return al.Count;
}
}
private Dictionary<string, int> portBaudRate = null;
//波特率配置列表
public Dictionary<string, int> PortBaudRate
{
get { return portBaudRate; }
set { portBaudRate = value; }
}
private bool hasPort = false;
//当前有无可使用的串口拨号器
public bool HasPort
{
get { return hasPort; }
//set { hasPort = value; }
}
private int reCheckMinutes = 30; //默认30分钟
//串口拨号器的重新检测间隔分钟
public int ReCheckMinutes
{
get { return reCheckMinutes; }
set { reCheckMinutes = value; }
}
#region 构造方法重载
public SerialPortList() { }
public SerialPortList(Dictionary<string, int> _portBaudRate)
{
this.portBaudRate = _portBaudRate;
}
public SerialPortList(int _reCheckMinutes)
{
this.reCheckMinutes = _reCheckMinutes;
}
public SerialPortList(Dictionary<string, int> _portBaudRate, int _reCheckMinutes)
{
this.portBaudRate = _portBaudRate;
this.reCheckMinutes = _reCheckMinutes;
}
#endregion 构造方法重载
/// <summary>
/// 获得串口上已经安装了拨号器的对象
/// </summary>
public void GetSerialPortList()
{
al = new List<MySerialPort>();
//步骤一: 获得所有的串口名称(列表)
string[] ports = SerialPort.GetPortNames();
foreach (string port in ports)
{
MySerialPort mySerialPort = null;
Console.WriteLine("正在检测:" + port); //测试使用
//是否设置波特率?
if (portBaudRate != null
&& portBaudRate.ContainsKey(port)
&& portBaudRate[port] != 0)
{
mySerialPort = new MySerialPort(port, portBaudRate[port]);
}
else mySerialPort = new MySerialPort(port);
bool ok = mySerialPort.HasModem();
if (ok)
{
al.Add(mySerialPort);
}
else
{
mySerialPort.Close();
mySerialPort = null;
}
}
// 判断当前计算机有无可使用串口端
hasPort = al.Count <= 0 ? false : true;
}
/// <summary>
/// 重新获得串口上已经安装了拨号器的对象
/// </summary>
public void ReGetSerialPortList()
{
if (al == null) GetSerialPortList();
else
{
//步骤一: 重新获得所有的串口名称(列表)
string[] portsName_2 = SerialPort.GetPortNames();
//如果当前串口数目 > 正在使用的COM
if (portsName_2.Length > al.Count)
{
Console.WriteLine("正在重新检测可以使用的拨号器!"); //测试使用
foreach (string pName_2 in portsName_2)
{
//当前串口名是否存在拨号列表中
bool hasAt = al.Exists(delegate(MySerialPort port_1)
{
return pName_2 == port_1.PortName;
});
//如果当前串口名不存在拨号列表中,则重新检测!
if (!hasAt)
{
Console.WriteLine("正在重新检测:" + pName_2); //测试使用
MySerialPort mySerialPort = null;
//是否设置波特率?
if (portBaudRate != null
&& portBaudRate.ContainsKey(pName_2)
&& portBaudRate[pName_2] != 0)
{
mySerialPort = new MySerialPort(pName_2, portBaudRate[pName_2]);
}
else mySerialPort = new MySerialPort(pName_2);
bool ok = mySerialPort.HasModem();
if (ok)
{
al.Add(mySerialPort);
}
else
{
mySerialPort.Close();
mySerialPort = null;
}
}
}
}
}
// 判断当前计算机有无可使用串口端
hasPort = al.Count <= 0 ? false : true;
}
/// <summary>
/// 重新获得串口上已经安装了拨号器的对象 (波特率使用默认值)
/// </summary>
public void ReGetSerialPortList(int _reCheckMinutes)
{
//串口拨号器的重新检测间隔分钟
reCheckMinutes = _reCheckMinutes;
ReGetSerialPortList();//波特率全部使用默认值
}
/// <summary>
/// 释放所有串口资源组件
/// </summary>
public void ClearAllSerialPort()
{
if (al != null)
{
for (int i = 0; i < al.Count; i++)
{
al[i].Close();
al[i] = null;
}
al = null;
}
if (portBaudRate != null)
{
portBaudRate = null;
}
}
private int index_Number = -1;
//串口的调度号
private int IndexNumber()
{
lock (this)
{
if (index_Number + 1 >= al.Count)
{
if (al.Count == 0) index_Number = -1;
else index_Number = 0;
}
else
{
index_Number++;
}
return index_Number;
}
}
/// <summary>
/// 对已经安装了拨号器的串口调度使用
/// </summary>
private void UseingSerialPort(string _SIM)
{
if (al == null) return;
// 等待线程进入
Monitor.Enter(al);
MySerialPort getPort = null;
try
{
//获得当前调用的串口对象的索引号
int num = IndexNumber();
if (num >= 0) //判断是否存在拨号器
{
getPort = al[num];
if (getPort != null && !getPort.IsWorking)
{
getPort.Dialing(_SIM); //对 SIM 进行拨号,唤醒上位机
}
}
else
{
//没有可使用的拨号器,则重新检测。
this.ReGetSerialPortList();
}
}
catch
{
//再一次检查该 COM 能否使用! (范工提议)
if (getPort != null)
{
string re_PortName = getPort.PortName;
al.Remove(getPort); //从可用列表去除
getPort.Close();
MySerialPort mySerialPort = new MySerialPort(re_PortName);
bool ok = mySerialPort.HasModem();
if (ok)
{
al.Add(mySerialPort); //重新添加到列表
}
else
{
mySerialPort.Close();
mySerialPort = null;
}
}
}
finally
{
// 通知其它对象
Monitor.Pulse(al);
// 释放对象锁
Monitor.Exit(al);
}
}
//重新检测端口时间
private DateTime dtCheck = DateTime.Now;
/// <summary>
/// 调用拨号器
/// </summary>
/// <param name="_SIM"></param>
public void InvokingSerialPort(string _SIM)
{
if (hasPort == false)
{
// 获得串口上已经安装了拨号器的对象
this.GetSerialPortList();
}
if (hasPort == true)
{
this.UseingSerialPort(_SIM);
//Thread.Sleep(5000);
}
//定期检测串口列表
//if (dtCheck.AddMinutes(reCheckMinutes) <= DateTime.Now)
if (dtCheck.AddMinutes(5) <= DateTime.Now)
{
// 重新获得串口上已经安装了拨号器的对象
this.ReGetSerialPortList();
dtCheck = DateTime.Now;
}
}
}
}
测试代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//
//using System.IO.Ports;
using System.Threading;
//using System.Collections;
namespace Con_SerialPortExample
{
class Program
{
static void Main(string[] args)
{
// 获得串口上已经安装了拨号器的对象 (自定义波特率)
Dictionary<string, int> _portBaudRate = new Dictionary<string, int>();
_portBaudRate["COM1"] = 9600;
_portBaudRate["COM2"] = 9600;
_portBaudRate["COM7"] = 9600;
SerialPortList list = new SerialPortList(_portBaudRate,5);
try
{
// 获得串口上已经安装了拨号器的对象
list.GetSerialPortList();
if (list.HasPort == false)
{
Console.WriteLine("当前计算机无可使用的串口拨号器!");
}
while (list.HasPort)
{
// 调用拨号器
list.InvokingSerialPort("13500000000");
Thread.Sleep(5000);
}
Console.WriteLine("程序运行结束!");
}
finally
{
// 释放所有串口资源组件
list.ClearAllSerialPort();
}
Console.ReadLine();
}
}
}