using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Sockets.CommonClass;
using System.Text.RegularExpressions;
using log4net;
namespace Sockets
{
//服务器端:
//第一步:建立一个用于通信的Socket对象
//第二步:使用bind绑定IP地址和端口号
//第三步:使用listen监听客户端
//第四步:使用accept中断程序直到连接上客户端
//第五步:接收来自客户端的请求
//第六步:返回客户端需要的数据
//第七步:如果接收到客户端已关闭连接信息就关闭服务器端
//————————————————
//客户端:
//第一步:建立一个用于通信的Socket对象
//第二步:根据指定的IP和端口connet服务器
//第三步:连接成功后向服务器端发送数据请求
//第四步:接收服务器返回的请求数据
//第五步:如果还需要请求数据继续发送请求
//第六步:如果不需要请求数据就关闭客户端并给服务器发送关闭连接信息
//————————————————
public partial class Frm_Sockets : Form
{
public static Socket ServerSocket; //声明用于监听的套接字
public static Socket ClientSocket; //声明负责通信的socket
public static Socket socketAccept; //声明绑定了客户端的套接字
public static Socket socket; //声明用于与某一个客户端通信的套接字
public static int StatusFlag = 0; //连接服务器状态标志:0=未链接;1=success;2=fail;
public static int SendFlag = 0; //发送状态标志:0=待机;1=success;2=fail;
Thread thread_Server;
Thread thread_Client;
/// <summary>
/// 初始化一个日志类
/// </summary>
private static readonly ILog LogInfo = LogManager.GetLogger(typeof(Frm_Sockets));//在Properties里,AssemblyInfo.cs添加配置文件,文件保存路径设置在DUG文件夹在"logconfig.xml"。
public Frm_Sockets()
{
InitializeComponent();
}
private void Frm_Sockets_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;//执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
cb_Protocol.Text = "TCP Server";
cb_IP.Text = "127.0.0.1";
cb_Port.Text = "8000";
SendFlag = 0;
string dataPath = @"\Logs";
string fullPath = Application.StartupPath + dataPath;
DeleteOldFiles(fullPath, 7);//删除7天日志
ReadConfig();//读取ini历史配置
}
/// <summary>
/// 打开链接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bt_Open_Click(object sender, EventArgs e)
{
try
{
if (StatusFlag == 0 || StatusFlag == 2)
{
//创建负责通信的Socket,当点击开始监听的时候 在服务器端创建一个负责监IP地址跟端口号的Socket
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ShowMsg("正在连接...");
if (cb_Protocol.Text == "TCP Server")
{
cb_ClientUser.Visible = true;
lb_ClientUserName.Visible = true;
IPAddress ip = IPAddress.Any;//IPAddress.Parse(txtServer.Text);
//创建端口号对象
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(cb_Port.Text));
//监听,处理IP地址和端口封装类
socket.Bind(point);
ShowMsg("连接成功!");
socket.Listen(10);
/*
* tip:
* Accept会阻碍主线程的运行,一直在等待客户端的请求,
* 客户端如果不接入,它就会一直在这里等着,主线程卡死
* 所以开启一个新线程接收客户单请求
*/
thread_Server = new Thread(Listen);
thread_Server.IsBackground = true;
thread_Server.Start(socket);
}
if (cb_Protocol.Text == "TCP Client")
{
cb_ClientUser.Visible = false;
lb_ClientUserName.Visible = false;
IPAddress ip = IPAddress.Parse(cb_IP.Text);
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(cb_Port.Text));
//获得要连接的远程服务器应用程序的IP地址和端口号
socket.Connect(point);
ShowMsg("服务器/客户端链接成功!" + cb_IP.Text + ":" + cb_Port.Text);
//开启一个新的线程不停的接收服务端发来的消息
thread_Client = new Thread(Recive);
thread_Client.IsBackground = true;
thread_Client.Start();
}
StatusFlag = 1;
cb_Protocol.Enabled = false;
cb_IP.Enabled = false;
cb_Port.Enabled = false;
btn_Open.Text = "链接成功!";
btn_Open.BackColor = Color.Green;
SaveIni();
}
else
{
StatusFlag = 0;
cb_Protocol.Enabled = true;
cb_IP.Enabled = true;
cb_Port.Enabled = true;
btn_Open.Text = "打开端口!";
btn_Open.BackColor = Color.AntiqueWhite;
ShowMsg("服务器/客户端已关闭!" + cb_IP.Text + ":" + cb_Port.Text);
if (cb_Protocol.Text == "TCP Server")
{
//thread_Server.Abort(); //关闭Server线程
//socketAccept.Close(); //关闭与客户端绑定的套接字
}
if (cb_Protocol.Text == "TCP Client")
{
//thread_Client.Abort();
}//关闭Server线程
socket.Close(); //关闭用于通信的套接字
}
}
catch (Exception ex)
{
StatusFlag = 2;
cb_Protocol.Enabled = true;
cb_IP.Enabled = true;
cb_Port.Enabled = true;
btn_Open.Text = "打开端口!";
btn_Open.BackColor = Color.AntiqueWhite;
ShowMsg("服务器/客户端连接失败!错误代码:" + ex.ToString());
}
}
/// <summary>
/// 数据Log
/// </summary>
/// <param name="str"></param>
private void ShowMsg(string str)
{
string str_End = "";
if (chk_LineFeed.Checked)
{
str_End = "\r\n";
}
if (chk_LogShow.Checked)
{
tb_Log.AppendText(DateTime.Now.ToString() + str + str_End);
}
else
{
tb_Log.AppendText(str + str_End);
}
AppendToLog(LogInfo, rtb_Log, string.Format(str, DateTime.Now.ToString()));
}
/// <summary>
/// 等待客户端的连接 并且创建与之通信用的Socket
/// </summary>
///
private void Listen(object obj)
{
Socket socketWatch = obj as Socket;
//等待客户端的连接 并且创建一个负责通信的Socket
while (true)
{
try
{
//负责跟客户端通信的Socket
socket = socketWatch.Accept();
//将远程连接的客户端的IP地址和Socket存入集合中
dicSocket.Add(socket.RemoteEndPoint.ToString(), socket);
将远程连接的客户端的IP地址和端口号存储下拉框中
cb_ClientUser.Items.Add(socket.RemoteEndPoint.ToString());
ShowMsg(socket.RemoteEndPoint.ToString() + ":" + "连接成功!");
//开启 一个新线程不停的接受客户端发送过来的消息
Thread thread_Server = new Thread(Recive);
thread_Server.IsBackground = true;
thread_Server.Start(socket);
}
catch
{ }
}
}
//将远程连接的客户端的IP地址和Socket存入集合中
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
/// <summary>
/// 服务器端不停的接受客户端发送过来的消息
/// </summary>
/// <param name="obj"></param>
private void Recive(object obj)
{
Socket socketReceive = obj as Socket;
string str = "";
while (true)
{
try
{
//客户端连接成功后,服务器应该接受客户端发来的消息
byte[] temp = new byte[1024 * 2];
//实际接受到的有效字节数
int length = 0;
if (cb_Protocol.Text == "TCP Server" && StatusFlag == 1)
{ length = socketReceive.Receive(temp); }
if (cb_Protocol.Text == "TCP Client" && StatusFlag == 1)
{
length = socket.Receive(temp);
}
byte[] buffer = new byte[length];
for (int i = 0; i < length; i++)
{
buffer[i] = temp[i];
}
if (length == 0)
{
break;//没有数据,返回
}
if (chk_SaveFile.Checked)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.InitialDirectory = @"C:\Users\Desktop";
sfd.Title = "请选择要保存的文件";
sfd.Filter = "所有文件|*.*";
sfd.ShowDialog(this);
string path = sfd.FileName;
using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
{
fsWrite.Write(buffer, 1, length - 1);
}
MessageBox.Show("保存成功");
}
if (rdo_R_HEX.Checked)
{
str = ConvertType.ToHexString(buffer).ToString();
}
if (rdo_R_ASCII.Checked)
{
str = Encoding.ASCII.GetString(buffer, 0, length);
}
ShowMsg(socket.RemoteEndPoint + " Socket接收:" + str);
timer_Dog.Stop();
}
catch (Exception ex)
{
ShowMsg("服务器/客户端连接失败!错误代码:" + ":" + ex.ToString());
}
}
}
#region 发送按钮
private void btn_Send_Click(object sender, EventArgs e)
{
if (SendFlag != 1)
{
SendFlag = 1;
btn_Send.Text = "停止发送";
SendMsg();
if (false)
{
timer_Dog.Interval = 5000;//设置默认5000毫秒未发聩信息,信息提示,并且复位标志,暂未启用该功能
timer_Dog.Start();
}
}
else
{
btn_Send.Text = "发送";
SendFlag = 0;
}
}
private void SendMsg()
{
try
{
string str = "";
if (chk_EscapeChar.Checked) str = RemoveEscapeChar(tb_Send.Text);//如果选择去除转义符,先执行除去转义符函数
//else if(chk_ECK.Checked)//自动发送校验符
else str = tb_Send.Text;//否则发送内容保留原样
byte[] buffer = null;
buffer = System.Text.Encoding.ASCII.GetBytes(str);
List<byte> list = new List<byte>();
list.Add(0);
list.AddRange(buffer);
//将泛型集合转换为数组
byte[] newBuffer = list.ToArray();
//buffer = list.ToArray();不可能
//获得用户在下拉框中选中的IP地址
string ip = cb_IP.SelectedItem.ToString();
//dicSocket[ip].Send(newBuffer);
socket.Send(buffer);
if (!chk_AutoSend.Checked)
{
btn_Send.Text = "发送";
SendFlag = 0;
}
ShowMsg("Socket发送:" + ":" + str);
}
catch (Exception ex)
{
SendFlag = 2;
ShowMsg("信息发送失败!错误代码:" + ":" + ex.ToString());
}
}
/// <summary>
/// 发送快捷键
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bt_Send_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)//如果输入的是回车键
{
this.btn_Send_Click(sender, e);//触发button事件
}
}
#endregion
/// <summary>
/// 接收内容以log文件形式保存
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void chk_OpenFile_CheckedChanged(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = @"C:\Users\SpringRain\Desktop";
ofd.Title = "请选择要发送的文件";
ofd.Filter = "所有文件|*.*";
ofd.ShowDialog();
//获得要发送文件的路径
string path = ofd.FileName;
if (path == "")
{
return;//如果取消,直接跳过流程
}
using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
int r = fsRead.Read(buffer, 0, buffer.Length);
List<byte> list = new List<byte>();
list.Add(1);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
dicSocket[cb_IP.Text].Send(newBuffer, 0, r + 1, SocketFlags.None);
}
}
private void btn_ClearLog_Click(object sender, EventArgs e)
{
tb_Log.Clear();
}
/// <summary>
/// 发送内容转换为ASCII
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
///
private void rdo_S_ASCII_Click(object sender, EventArgs e)
{
byte[] buffer = null;
buffer = ConvertType.StringsToHexbytes(tb_Send.Text);
tb_Send.Text = System.Text.Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// 发送内容转换为HEX
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void rdo_S_HEX_Click(object sender, EventArgs e)
{
byte[] buffer = null;
buffer = System.Text.Encoding.ASCII.GetBytes(tb_Send.Text);
tb_Send.Text = ConvertType.ToHexString(buffer).ToString();
}
#region 读取ini配置
/// <summary>
/// 读取ini配置
/// </summary>
private void ReadConfig()
{
cb_Protocol.Text = iniFile.ReadValue("Protocol", "Protocol", AppConfig.ConfigFileName);
cb_IP.Text = iniFile.ReadValue("IP", "IP", AppConfig.ConfigFileName);
cb_Port.Text = iniFile.ReadValue("Port", "Port", AppConfig.ConfigFileName);
if (iniFile.ReadValue("R_ASCII", "R_ASCII", AppConfig.ConfigFileName) == "TRUE") { rdo_R_ASCII.Checked = true; }
else { rdo_R_ASCII.Checked = false; }
if (iniFile.ReadValue("R_HEX", "R_HEX", AppConfig.ConfigFileName) == "TRUE") { rdo_R_HEX.Checked = true; }
else { rdo_R_HEX.Checked = false; }
if (iniFile.ReadValue("chk_LogShow", "chk_LogShow", AppConfig.ConfigFileName) == "TRUE") { chk_LogShow.Checked = true; }
else { chk_LogShow.Checked = false; }
if (iniFile.ReadValue("chk_SaveFile", "chk_SaveFile", AppConfig.ConfigFileName) == "TRUE") { chk_SaveFile.Checked = true; }
else { chk_LineFeed.Checked = false; }
if (iniFile.ReadValue("chk_LineFeed", "chk_LineFeed", AppConfig.ConfigFileName) == "TRUE") { chk_LineFeed.Checked = true; }
else { chk_SaveFile.Checked = false; }
if (iniFile.ReadValue("S_ASCII", "S_ASCII", AppConfig.ConfigFileName) == "TRUE") { rdo_S_ASCII.Checked = true; }
else { rdo_R_ASCII.Checked = false; }
if (iniFile.ReadValue("S_HEX", "S_HEX", AppConfig.ConfigFileName) == "TRUE") { rdo_S_HEX.Checked = true; }
else { rdo_R_HEX.Checked = false; }
if (iniFile.ReadValue("chk_EscapeChar", "chk_EscapeChar", AppConfig.ConfigFileName) == "TRUE") { chk_EscapeChar.Checked = true; }
else { chk_EscapeChar.Checked = false; }
if (iniFile.ReadValue("chk_ATEnter", "chk_ATEnter", AppConfig.ConfigFileName) == "TRUE") { chk_ECK.Checked = true; }
else { chk_ECK.Checked = false; }
}
#endregion
#region 保存ini配置
/// <summary>
/// 保存ini配置
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SaveIni()
{
iniFile.WriteValue("Protocol", "Protocol", cb_Protocol.Text, AppConfig.ConfigFileName);
iniFile.WriteValue("IP", "IP", cb_IP.Text, AppConfig.ConfigFileName);
iniFile.WriteValue("Port", "Port", cb_Port.Text, AppConfig.ConfigFileName);
if (rdo_R_ASCII.Checked) { iniFile.WriteValue("R_ASCII", "R_ASCII", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("R_ASCII", "R_ASCII", "R_ASCII", AppConfig.ConfigFileName); }
if (rdo_R_HEX.Checked) { iniFile.WriteValue("R_HEX", "R_HEX", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("R_HEX", "R_HEX", "FASLE", AppConfig.ConfigFileName); }
if (chk_LogShow.Checked) { iniFile.WriteValue("chk_LogShow", "chk_LogShow", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("chk_LogShow", "chk_LogShow", "FASLE", AppConfig.ConfigFileName); }
if (chk_LogShow.Checked) { iniFile.WriteValue("chk_LineFeed", "chk_LineFeed", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("chk_LineFeed", "chk_LineFeed", "FASLE", AppConfig.ConfigFileName); }
if (chk_LogShow.Checked) { iniFile.WriteValue("chk_SaveFile", "chk_SaveFile", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("chk_SaveFile", "chk_SaveFile", "FASLE", AppConfig.ConfigFileName); }
if (rdo_S_ASCII.Checked) { iniFile.WriteValue("S_ASCII", "S_ASCII", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("S_ASCII", "S_ASCII", "FASLE", AppConfig.ConfigFileName); }
if (rdo_S_HEX.Checked) { iniFile.WriteValue("S_HEX", "S_HEX", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("S_HEX", "S_HEX", "FASLE", AppConfig.ConfigFileName); }
if (chk_LogShow.Checked) { iniFile.WriteValue("chk_EscapeChar", "chk_EscapeChar", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("chk_EscapeChar", "chk_EscapeChar", "FASLE", AppConfig.ConfigFileName); }
if (chk_LogShow.Checked) { iniFile.WriteValue("chk_ATEnter", "chk_ATEnter", "TRUE", AppConfig.ConfigFileName); }
else { iniFile.WriteValue("chk_ATEnter", "chk_ATEnter", "FASLE", AppConfig.ConfigFileName); }
}
#endregion
private void timer_AutoSend_Tick(object sender, EventArgs e)
{
if (SendFlag == 1)
{
SendMsg();//调用按钮事件进行一次串口触发
timer_AutoSend.Interval = (int)Cycle_NumericUpDown.Value;
}
}
private void chk_AutoSend_CheckedChanged(object sender, EventArgs e)
{
if (chk_AutoSend.Checked)
{
timer_AutoSend.Interval = (int)Cycle_NumericUpDown.Value;
timer_AutoSend.Start();
}
else
{
timer_AutoSend.Stop();
}
}
private void timer_Dog_Tick(object sender, EventArgs e)
{
if (SendFlag == 1)
{
ShowMsg("Socket发送信息:" + tb_Send.Text + ",等待5000毫秒无回应超时!");
btn_Send.Text = "发送";
SendFlag = 0;
timer_Dog.Stop();
}
}
#region"运行日志"
/// <summary>
/// 追加日记记录,委托显示
/// </summary>
/// <param name="txt"></param>
public void AppendToLog(ILog LogInfo, RichTextBox Log, string txt)
{
Log.Invoke(new Action(() =>
{
if (!(Log.Disposing || Log.IsDisposed))
{
if (Log.TextLength >= 5000)
{
Log.Text = Log.Text.Substring(Log.TextLength - 4000);
}
Log.Select(Log.TextLength, 0);
Log.AppendText(string.Format("{0}-{1}", DateTime.Now.ToLongTimeString(), txt + "\n"));
if (chk_SaveFile.Checked)
{
Log.ScrollToCaret();
switch (Log.Name)
{
case "rtb_Log":
LogInfo.Debug(txt);
break;
case "rtb_ErrorLog":
LogInfo.Error(txt);
break;
default:
break;
}
}
}
}));
}
/// <summary>
/// 删除日记
/// </summary>
/// <param name="txt"></param>
public void DeleteOldFiles(string dir, int days)
{
try
{
if (!Directory.Exists(dir) || days < 1)
{
return;
}
else
{
var now = DateTime.Now;
foreach (var ParentFile in Directory.GetFileSystemEntries(dir))
{
var FileLastWriteTime = File.GetLastWriteTime(ParentFile);
if (CompareDays(FileLastWriteTime, now) > days)//判断父目录
{
DirectoryInfo ParentFileInfo = new DirectoryInfo(ParentFile);
ParentFileInfo.Attributes = FileAttributes.Normal & FileAttributes.Directory;
Directory.Delete(ParentFile, true);
}
else //判断子目录
{
foreach (var Childfile in Directory.GetFileSystemEntries(ParentFile))
{
var ChildfileLastWriteTime = File.GetLastWriteTime(Childfile);
if (CompareDays(ChildfileLastWriteTime, now) > days)//删除子目录
{
File.Delete(Childfile);
}
}
}
}
//判断是否为空文件夹
foreach (var ParentFile in Directory.GetFileSystemEntries(dir))
{
if ((Directory.GetDirectories(ParentFile).Length + Directory.GetFiles(ParentFile).Length) == 0)
{
DirectoryInfo ParentFileInfo = new DirectoryInfo(ParentFile);
ParentFileInfo.Attributes = FileAttributes.Normal & FileAttributes.Directory;
Directory.Delete(ParentFile, true);
}
}
}
}
catch { }
}
/// <summary>
/// 比较日期
/// </summary>
/// <param name="txt"></param>
public int CompareDays(DateTime DJStart, DateTime DJEnd)
{
DateTime start = Convert.ToDateTime(DJStart.ToShortDateString());
DateTime end = Convert.ToDateTime(DJEnd.ToShortDateString());
TimeSpan sp = end.Subtract(start);
return sp.Days;
}
#endregion
/*
* tip:转义字符转义字符是很多程序语言、数据格式和通信协议的形式文法的一部分。对于一个给定的字母表,一个转义字符的目的是开始一个字符序列,使得转义字符开头的该字符序列具有不同于该字符序列单独出现时的语义。因此转义字符开头的字符序列被叫做转义序列。
* 转义序列通常有两种功能。第一个是编码一个句法上的实体,如设备命令或者无法被字母表直接表示的特殊数据。第二种功能,也叫字符引用,用于表示无法在当前上下文中被键盘录入的字符(如字符串中的回车符),或者在当前上下文中会有不期望的含义的字符。
* (如C语言字符串中的双引号字符",不能直接出现,必须用转义序列表示)。在后面那种情况,转义序列是一种由转义字符自身和一个被引用的字符组成的一个二合字母(digraph)情形。
* \a, 响铃 (BEL) 007\b
* 退格(BS) ,将当前位置移到前一列 008\f
* 换页(FF),将当前位置移到下页开头 012\n
* 换行(LF) ,将当前位置移到下一行开头 010\r
* 回车(CR) ,将当前位置移到本行开头 013\t
* 水平制表(HT) (跳到下一个TAB位置) 009\v
* 垂直制表(VT) 011\\
* 代表一个反斜线字符''\' 092\'
* 代表一个单引号(撇号)字符 039\"
* 代表一个双引号字符 034\?
* 代表一个问号 063\0
* 空字符(NUL) 000\ddd
* 1到3位八进制数所代表的任意字符
* 三位八进制 \xhh
* 十六进制所代表的任意字符 十六进制
*/
/// <summary>
/// 转义字符移除-仅处理部分
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string RemoveEscapeChar(string str)
{
char[] temp_char = null;
int j = 0;
if (!String.IsNullOrEmpty(str))
{
int len = (str.Trim()).Length;
temp_char = new char[len];
for (int i = 0; i < len; i++)
{
char c = str[i];//先识别'\',再判断后面字母,如果是转义符,移除。
if (c == '\\')
{
i++;
c = str[i];
}
switch (c)
{
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '\\':
case '\'':
case '\"':
//case '\?':
//case '\0':
//case '\ddd':
continue;
default:
temp_char[j++] = c;
break;
}
}
}
string str_return = new string(temp_char, 0, j);
return str_return;
}
}
}