在第一篇中,已经实现了异步通信的控制台应用程序。服务端能够连接多个客户端并发送消息。
在这一篇中,将实现两个功能:
1. 实现GUI的客户端,能够与服务器进行双向通信
2. 实现socket通信的长度校验
客户端的GUI实现
在控制台程序中,客户端不能方便的发送消息给服务器,使用C#编写用户界面,实现通信和打印消息。
在GUI的socket编程中,必须使用异步编程,或者多线程编程,否则主线程在进行通信时就无法刷新界面。
发送消息
点击发送键将发送文本框的内容,这里不是连续发送,不需要使用异步编程。
public bool sendMsg(string message)
{
try
{
msg = message;
sendBuffer = Encoding.Unicode.GetBytes(message);
cSocket.Send(sendBuffer, sendBuffer.Length, 0);
}
catch (Exception e)
{
msg = "send exception: " + e.Message;
}
return true;
}
实现效果
左边是服务器,右边是客户端。点击按钮连接服务器,开始接收服务器定时发送的信息。在文本框输入信息,点击按键发送到服务端。
长度校验
TCP/IP协议在进行数据发送的时候,并不是严格按照编程写的一条一条发送,为了提高效率,会有如下的改变:
1. 当数据长度较短时,会把数据累计到一定的长度后发送,一次接收的可能是几条数据;
2. 当数据长度过长的时,会把数据拆成几条进行发送。
但TCP/IP协议保证这些数据是有序到达而且不会丢失,是可靠的。
解决方案
因此,为了将接收的信息进行正确的解析,我们必须对数据进行一定的包装操作,以正确组装接收的信息。
1. 在每一个数据前添加一个长度,以特殊符号进行标识,在这里使用[
和]
;
2. 由于在数据中可能含有特殊符号,会对标记进行干扰,因此可以使用一定的可逆编码技术,编码后保证不会出现我们标记的字符,如base64编码。
编码
public string createStr()
{
string s = "";
if (sr.Peek()>=0) // 判断结尾
{
s = sr.ReadLine();
// 进行base64编码
byte[] b = Encoding.Unicode.GetBytes(s);
s = Convert.ToBase64String(b);
s = "[" + s.Length + "]" + s;
}
return s;
}
长度校验
长度校验使用标记字段进行校验
1. 如果之前一条已经解析完毕,查找[]
获得长度,然后进行装配
2. 如果之前一条未解析完毕,根据还缺的长度进行装配查找
这里写的不是很完善,一些情况会出问题:
1. 比如标识字段被分成两条时,下面代码无法进行解析
private bool complete; // 之前解析是否完成
private int strLength; // 字符串长度
private string unStr; // 记录已经累计的长度
public bool writeToFile(string s)
{
sw_o.WriteLine(s);
sw_o.Flush();
int idx1, idx2;
try
{
if(complete) // 上一轮已经查找完毕
{
idx1 = s.IndexOf('[');
idx2 = s.IndexOf(']');
while(idx1!=-1) //能够找到一个标识符
{
// 获取长度
strLength = int.Parse(s.Substring(idx1 + 1, idx2 - idx1 - 1));
Console.WriteLine(strLength);
if (s.Length - idx2 - 1 < strLength) // 这一个段已经不够使用
{
unStr = s.Substring(idx2 + 1, s.Length - idx2 - 1);
strLength -= (s.Length - idx2 - 1);
complete = false;
break;
}
else if (s.Length - idx2 - 1 == strLength) // 刚好够用
{
unStr = s.Substring(idx2 + 1, strLength);
string str = decoderBase64(unStr);
writeStr(str);
Console.WriteLine("finished1: " + str);
complete = true;
break;
}
else
{
string str = s.Substring(idx2 + 1, strLength); // 写入一个完整字符串
str = decoderBase64(str);
writeStr(str);
Console.WriteLine("finished2: " + str);
// 截取新的字符串进行重复操作
s = s.Substring(idx2 + strLength + 1, s.Length - idx2 - strLength - 1);
idx1 = s.IndexOf('[');
idx2 = s.IndexOf(']');
}
}
}
else
{
if(s.Length<strLength) // 不够使用
{
strLength -= s.Length;
unStr += s;
complete = false;
}
else if(s.Length == strLength) // 刚好完全
{
complete = true;
unStr += s;
string str = decoderBase64(unStr);
writeStr(str);
Console.WriteLine("finished3: "+str);
}
else
{
idx1 = s.IndexOf('[');
unStr += s.Substring(0, idx1); // 上次遗留的进行完全
string str = decoderBase64(unStr);
writeStr(str);
idx2 = s.IndexOf(']');
while(idx1 !=-1)
{
// 获取长度
strLength = int.Parse(s.Substring(idx1 + 1, idx2 - idx1 - 1));
Console.WriteLine(strLength);
if (s.Length - idx2 - 1 < strLength) // 这一个段已经不够使用
{
unStr = s.Substring(idx2 + 1, s.Length - idx2 - 1);
strLength -= (s.Length - idx2 - 1);
complete = false;
break;
}
else if (s.Length - idx2 - 1 == strLength) // 刚好够用
{
unStr = s.Substring(idx2 + 1, strLength);
str = decoderBase64(unStr);
writeStr(str);
Console.WriteLine("finished4: " + str);
complete = true;
break;
}
else
{
str = s.Substring(idx2 + 1, strLength); // 写入一个完整字符串
str = decoderBase64(str);
writeStr(str);
Console.WriteLine("finished5: " + str);
// 截取新的字符串进行重复操作
s = s.Substring(idx2 + strLength + 1, s.Length - idx2 - strLength - 1);
idx1 = s.IndexOf('[');
idx2 = s.IndexOf(']');
}
}
}
}
}
catch(Exception e)
{
Console.WriteLine("writefile Exception: "+e.Message);
}
return true;
}
实现效果
左边是组装后的文本,右边是实际接收到的经过base64
编码的,TCP/IP协议自动拆分合并的,实际发送的数据。对于这种情况能够进行正确的组装。
欢迎补充更完善的校验~