再次特别感谢张子阳老师的文章,是我深感益处。
在前一篇文章中可以看到,尽管消息分成了三条单独发送,但是服务端却将后两条合并成了一条。对于这些情况,我们可以这样处理:就好像HTTP协议一样,在实际的请求和应答内容之前包含了HTTP头,其中是一些与请求相关的信息。我们也可以订立自己的协议,来解决这个问题,比如说,对于上面的情况,我们就可以定义这样一个协议: [length=XXX]:其中xxx是实际发送的字符串长度(注意不是字节数组buffer的长度),那么对于上面的请求,则我们发送的数据为:“[length=25]Welcome to TraceFact.Net!”。而服务端接收字符串之后,首先读取这个“元数据”的内容,然后再根据“元数据”内容来读取实际的数据,它可能有下面这样两种情况: NOTE:我觉得这里借用“元数据”这个术语还算比较恰当,因为“元数据”就是用来描述数据的数据。 “[“”]”中括号是完整的,可以读取到length的字节数。然后根据这个数值与后面的字符串长度相比,如果相等,则说明发来了一条完整信息;如果多了,那么说明接收的字节数多了,取出合适的长度,并将剩余的进行缓存;如果少了,说明接收的不够,那么将收到的进行一个缓存,等待下次请求,然后将两条合并。
“[”“]”中括号本身就不完整,此时读不到length的值,因为中括号里的内容被截断了,那么将读到的数据进行缓存,等待读取下次发送来的数据,然后将两次合并之后再按上面的方式进行处理。 转载请说明出处。

所以在此先拟定一个协议。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions; namespace SeverClass.RequestHanderSpace {
public class RequestHander {
private string temp = string.Empty; public string[] GetActualString(string input) {
return GetActualString(input, null);
} private string[] GetActualString(string input, List<string> OutputList) {
if(OutputList==null) OutputList = new List<string>();
if(!string.IsNullOrEmpty(temp)) input = temp +input; string output = "";
string pattern = @"(?<=^\[length=)(\d+)(?=\])";
int length; if (Regex.IsMatch(input, pattern)) {
Match m = Regex.Match(input, pattern); // get the length of string input;
length = Convert.ToInt32(m.Groups[0].Value); //获取需要截取的位置
int startIndex = input.IndexOf(']') + 1; //获取这个位置之后的字符串
output = input.Substring(startIndex); if (length == output.Length) {
//如果output的长度与消息字符串的长度相等,说明刚好是一条消息;
OutputList.Add(output);
temp = "";
} else if (output.Length < length) {
//说明截取之后的长度小于应有的长度,说明没有发完整,应将整条信息包括元数据全部缓存,与下一条数据一并处理。
temp = input;
} else if (output.Length > length) {
//说明截取之后的长度大于应有的长度,说明消息发完整了,但是有多余的数据,多余的数据可能是截断信息,也可能是多条完整信息。
output = output.Substring(0, length);
OutputList.Add(output);
temp = ""; input = input.Substring(startIndex + length);//截取剩下的字符串
GetActualString(input, OutputList);//递归调用
}
} else {
temp = input;//说明[]本身就不完整
}
return OutputList.ToArray();
}
}
}

对得到的字符串进行解析,在此用到了正则表达式,

string pattern = @"(?<=^\[length=)(\d+)(?=\])";

关于正则表达式,请自行学习。

服务器代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using SeverClass.RequestHanderSpace; namespace SeverClass {
public class RomoteClient {
private TcpClient client;
private NetworkStream streamToclient;
private const int bufferSize = 8192;
private byte[] buffer;
private RequestHander hander; public RomoteClient(TcpClient client) {
this.client = client;
//the info of client connected
Console.WriteLine("client connected{0} <-- {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
//获得流
streamToclient = client.GetStream();
buffer = new byte[bufferSize]; hander = new RequestHander();
//在构造函数中准备读取
AsyncCallback callback = new AsyncCallback(ReadComplete);
streamToclient.BeginRead(buffer, 0, bufferSize, callback, null);
} //在读取完时进行回调
private void ReadComplete(IAsyncResult ar) {
int bytesRead = 0;
try {
lock (streamToclient) {
bytesRead = streamToclient.EndRead(ar);
Console.WriteLine("读取到{0}字节", bytesRead);
}
if (bytesRead == 0) {
throw new Exception("读取到0字节");
}
string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
Array.Clear(buffer, 0, buffer.Length);//清空缓存,避免脏读
string[] msgArray = hander.GetActualString(msg);//获取实际的字符串 //遍历得到的字符串;
foreach (string str in msgArray) {
Console.WriteLine("Recived: {0}", str);
Console.WriteLine();
string back = str.ToUpper();
back = string.Format("[length={0}]{1}",back.Length,back); //将得到的字符串转换为大写重新发送给客户端
byte[] send = Encoding.Unicode.GetBytes(back);
lock (streamToclient) {
streamToclient.Write(send, 0, send.Length);
streamToclient.Flush();
}
Console.WriteLine("Send: {0}", back);
Console.WriteLine();
}
//再次回调,无限循环,直到异常退出
lock (streamToclient) {
AsyncCallback callback = new AsyncCallback(ReadComplete);
streamToclient.BeginRead(buffer, 0, bufferSize, callback, null);
}
} catch(Exception ex) {
if (streamToclient != null) {
streamToclient.Dispose();
}
client.Close();
Console.WriteLine(ex.Message);
}
} }
class Program {
static void Main(string[] args) {
Console.WriteLine("Severe is running!");
IPAddress ip = new IPAddress(new byte[4] { 127, 0, 0,1 });
TcpListener listener = new TcpListener(ip, 8501); listener.Start();
Console.WriteLine("Severe is listenning"); while (true) {
TcpClient client = listener.AcceptTcpClient();
RomoteClient remote = new RomoteClient(client);
} ConsoleKey key;
while (Console.ReadKey().Key != ConsoleKey.Q) {
continue;
}
}
}
}

这是客户端代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using SeverClass.RequestHanderSpace;
using System.Threading;
namespace ClientClass { public class SeverClient {
private const int bufferSize = 8192;
private byte[] buffer;
private TcpClient client;
private NetworkStream streamToSever;
private string msg = "welcome to tracefact .net";
RequestHander hander = new RequestHander(); public SeverClient() {
try {
client = new TcpClient();
client.Connect("127.0.0.1", 8501);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
} buffer = new byte[bufferSize];
Console.WriteLine("连接成功! \n {0}-->{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
streamToSever = client.GetStream();
Thread readThread = new Thread(read);
readThread.Start(); }
//send message to Sever
public void sendMessage() {
msg = string.Format("[length={0}]{1}", msg.Length, msg); for (int i = 0; i < 5; i++) {
byte[] temp = Encoding.Unicode.GetBytes(msg);
try {
lock (streamToSever) {
streamToSever.Write(temp, 0, temp.Length);
streamToSever.Flush();
}
Console.WriteLine("Send: {0}", msg);
Console.WriteLine();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
break;
}
}
}
public void read() {
lock (streamToSever) {
AsyncCallback callback = new AsyncCallback(ReadComplete);
streamToSever.BeginRead(buffer, 0, bufferSize, callback, null);
}
} void ReadComplete(IAsyncResult ar) {
int bytesRead = 0;
try {
lock (streamToSever) {
bytesRead = streamToSever.EndRead(ar);
} if (bytesRead == 0) {
throw new Exception("读取到0字节");
}
Console.WriteLine("读取了{0}字节", bytesRead);
string msg = Encoding.Unicode.GetString(buffer,0,bytesRead); Console.WriteLine(msg);
string[] strArray = hander.GetActualString(msg);
foreach (string str in strArray) {
Console.WriteLine("Recived: {0}", str);
Console.WriteLine();
} Array.Clear(buffer, 0, buffer.Length);//清空缓存 避免脏读 lock (streamToSever) {
AsyncCallback callback = new AsyncCallback(ReadComplete);
streamToSever.BeginRead(buffer, 0, bufferSize, callback, null);
}
} catch (Exception ex) {
if (streamToSever != null) {
streamToSever.Dispose();
}
client.Close();
Console.WriteLine(ex.Message);
}
}
} class Program {
static void Main(string[] args) {
SeverClient myClient = new SeverClient();
myClient.sendMessage();
Console.ReadLine();
}
}
}

在客户端,另建立了一个线程read,用来同步接受服务器返回的代码。关于多线程的知识,请看我之前写的一个多线程监听小程序。

转载请标明出处。

c#用socket异步传输字符串的更多相关文章

  1. [转]C#网络编程(异步传输字符串) - Part.3

    本文转自:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part3.aspx 这篇文章我们将前进一大步,使用异步的方式 ...

  2. C#网络编程(异步传输字符串) - Part.3

    这篇文章我们将前进一大步,使用异步的方式来对服务端编程,以使它成为一个真正意义上的服务器:可以为多个客户端的多次请求服务.但是开始之前,我们需要解决上一节中遗留的一个问题. 消息发送时的问题 这个问题 ...

  3. Windows下通过socket进行字符串和文件传输

    今天在windows平台下,通过socket实现了简单的文件传输.通过实现这一功能,了解基本的windows网络编程和相关函数的使用方法. 在windows平台上进行网络编程,首先都需要调用函数WSA ...

  4. socket 发送字符串0x00时被截断

    发送数据如下: aa 02 02 00 00 00 6f 6b 02 00 00 00 55 数据是以字符数组的形式(char msg[])存储发送的,send时发送长度填写的strlen(msg), ...

  5. TCP学习之五:客户端、服务端异步传输字符串

    参考学习张子阳大神的博客:http://www.cnblogs.com/JimmyZhang/category/101698.html 消息发送接口: 消息接收接口: 客户端: 服务端: 消息发送类: ...

  6. socket详解

    <?php /* * * socket主要翻译为套接字 * socket_accept — Accepts a connection on a socket * 接受一个socket链接 * s ...

  7. JAVA网络编程Socket常见问题 【长连接专题】

    一. 网络程序运行过程中的常见异常及处理 第1个异常是 java.net.BindException:Address already in use: JVM_Bind. 该异常发生在服务器端进行new ...

  8. 基于 socket.io, 简单实现多平台类似你猜我画 socket 数据传输

    一.前言 socket.io 实现了实时双向的基于事件的通讯机制,是基于 webSocket 的封装,但它不仅仅包括 webSocket,还对轮询(Polling)机制以及其它的实时通信方式封装成了通 ...

  9. java 和 C++ Socket通信(java作为服务端server,C++作为客户端client,解决中文乱码问题GBK和UTF8)

    原文链接: http://www.cnblogs.com/kenkofox/archive/2010/04/25/1719649.html 代码: http://files.cnblogs.com/k ...

随机推荐

  1. [Leetcode]Palindrome Number

    Determine whether an integer is a palindrome. Do this without extra space. 这题貌似解法挺多,直接用简单的把数倒置,没有考虑数 ...

  2. linux指令之文件的创建、查询、修改

    mkdir(make directory) 功能:创建目录 案例: mkdir test 点评:将创建一个目录名为test的目录 rmdir(remove directory) 功能:删除目录 案例: ...

  3. Yii2中的环境配置

    默认的Debug配置 在入口文件中 defined ( 'YII_DEBUG' ) or define ( 'YII_DEBUG', true ); defined ( 'YII_ENV' ) or ...

  4. 使用statsd+graphite+grafana构建业务及性能监控模块

    近些年随着DevOps概念越来越收到重视,除了传统的Splunk,Zabbix外在开源领域也有越来越多的软件可供使用.从数据收集,时序数据库,图形展示等主要方面有各类可扩展的软件用于搭建一个数据监控平 ...

  5. 可在广域网部署运行的QQ高仿版 -- GG叽叽V3.2,增加离线消息、离线文件功能(源码)

    (几句题外话:虽然就如何将GG发展为一个有商业价值的产品,我还没有很清晰明确的思路,但是从GG发布以来,通过GG认识了一些朋友,也接了一些小单子,赚了一点小钱.有了一点甜头,目前和2.3个好朋友一起做 ...

  6. STC12C5A60S2笔记7(定时器)

    1. 基本特性 STC12C5A60S2单片机集成了两个16位定时/计数器. 1)寄存器 1.1)TMOD 定时器工作方式控制寄存器,包括13位寄存器.16位寄存器.8位寄存器等: 1.2)TCON ...

  7. Windows Phone App的dump文件实例分析- System.ExecutionEngineException

    前言 在开始这篇文章之前我们先来讲讲如何从高度优化的Release版的Dump中找到正确的异常上下文地址,并手动恢复异常发生的第一现场. 1. 什么是异常上下文 简单来说,在windows体系的操作系 ...

  8. 人人都是 DBA(VIII)SQL Server 页存储结构

    当在 SQL Server 数据库中创建一张表时,会在多张系统基础表中插入所创建表的信息,用于管理该表.通过目录视图 sys.tables, sys.columns, sys.indexes 可以查看 ...

  9. 使用mybatis访问sql server

    原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com mybatis作为一种半自动化的ORM工具,可以提供更大的灵活性,逐渐受到社区的欢迎. 官方下载地址是:https ...

  10. 《OOC》笔记(1)——C语言const、static和extern的用法

    <OOC>笔记(1)——C语言const.static和extern的用法 C语言中const关键字用法不少,我只喜欢两种用法.一是用于修饰函数形参,二是用于修饰全局变量和局部变量. 用c ...