问题概述

最近在处理一些TCP客户端的项目,服务端是C语言开发的socket. 实际项目开始的时候使用默认的阻塞模式并未发现异常。代码如下

 1   public class SocketService
2 {
3 public delegate void TcpEventHandler1(byte[] receivebody, int length);
4 public event TcpEventHandler1 OnGetCS;
5 Socket client = null;
6 IPEndPoint endPoint = null;
7 public SocketService(string ip, int port)
8 {
9 client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
10 //client.Blocking = false;默认是阻塞模式
11 endPoint = new IPEndPoint(IPAddress.Parse(ip), port);
12 IsRcv = true;
13 }
14
15 Thread rthr = null;//异步线程用于接收数据
16
17 /// <summary>
18 /// 表示是否继续接收数据
19 /// </summary>
20 public bool IsRcv { get; set; }
21 /// <summary>
22 /// 打开连接
23 /// </summary>
24 /// <returns></returns>
25 public bool Open()
26 {
27 if (client != null && endPoint != null)
28 {
29 try
30 {
31 client.Connect(endPoint);
32 Console.WriteLine("连接成功");
33
34 //启动异步监听
35 rthr = new Thread(ReceiveMsg);
36 rthr.IsBackground = true;
37 rthr.SetApartmentState(ApartmentState.STA);
38 rthr.Start();
39 return true;
40 }
41 catch
42 {
43 AbortThread();
44 Console.WriteLine("连接失败!");
45 }
46 }
47 return false;
48 }
49
50 /// <summary>
51 /// 关闭接收数据线程
52 /// </summary>
53 private void AbortThread()
54 {
55 if (rthr != null)
56 {
57 rthr.Abort();
58 }
59 }
60
61 /// <summary>
62 /// 关闭连接
63 /// </summary>
64 public void Close()
65 {
66 if (client.Connected)
67 {
68 client.Close();
69 }
70 }
71
72 /// <summary>
73 /// 接收数据
74 /// </summary>
75 private void ReceiveMsg()
76 {
77 byte[] arrMsg = new byte[1024 * 1024];
78 try
79 {
80 while (IsRcv)
81 {
82 int length = client.Receive(arrMsg);//阻塞模式,此次线程会停止继续执行,直到socket内核有数据
83 byte type;
84 if (length > 0)
85 OnGetCS(arrMsg, length); //出发数据接收事件
86 }
87 }
88 catch (Exception ex)
89 {
90 rthr.Abort();
91 client.Close();
92 client = null;
93 Console.WriteLine("服务器断开连接");
94 }
95 }
96 }

阻塞模式代码

当客户运行久后就发现 从服务器端发过来的数据到处理完成整个环节消耗的时间比较多(比同行慢)。

使用TCP 监听助手,和客户端程序在OnGetCS处打印出时间比较发现TCP助手显示收到的时间会比客户端程序显示的快500-800MS左右。

.也就是说服务器已经吧数据发送到客户端TCP缓冲区了,只是客户端 int length = client.Receive(arrMsg); 并么有及时获得相应。

查了很多资料都没有查到有类似的问题。最后我用C#模拟做了一个TCP服务端与自己的TCP客户端之间通信,则完全没有延迟。

因此只能考虑语言特性的差别了。C#毕竟封装了很多信息。这个时候再查看TCP监听助手对比服务器是C的和C#的发现 C服务器在指令标记位没有PSH标记位,而C#的则有这个标记位,如下图(此处C#作为服务器的有兴趣的可以自己去试)

查询网络上的一段解释如下

PSH 的作用

TCP 模块什么时候将数据发送出去(从发送缓冲区中取数据),以及 read 函数什么时候将数据从接收缓冲区读取都是未知的。

如果使用 PSH 标志,上面这件事就确认下来了:

  • 发送端

对于发送方来说,由 TCP 模块自行决定,何时将接收缓冲区中的数据打包成 TCP 报文,并加上 PSH 标志(在图 1 中,为了演示,我们假设人为的干涉了 PSH 标志位)。一般来说,每一次 write,都会将这一次的数据打包成一个或多个 TCP 报文段(如果数据量大于 MSS 的话,就会被打包成多个 TCP 段),并将最后一个 TCP 报文段标记为 PSH。

当然上面说的只是一般的情况,如果发送缓冲区满了,TCP 同样会将发送缓冲区中的所有数据打包发送。

  • 接收端

如果接收方接收到了某个 TCP 报文段包含了 PSH 标志,则立即将缓冲区中的所有数据推送给应用进程(read 函数返回)。

当然有时候接收缓冲区满了,也会推送。

通过这个解释瞬间总算是明白了,早期C开发的很多TCP通信,都是不带PSH标记位的,后来的产品很多都遵守这个模式了,然后我们的C#默认就是使用PSH标记位。 因此就导致了数据接收延迟500-800MS(根据PSH的解释这个延迟具体多久是未知的)。

解决方案

 最简单的是服务器端增加这个标记位发送过来。一番讨论后,人家写这个服务器的人都已经离职了,没人会处理。那么客户是上帝,只能我们这边来处理了。这里就要用到非阻止模式的socket了。

首先我在网上查到很多人说异步就是非阻止模式。这个完全是错误的。异步同步与阻止模式是没有关系的两个概念。 当阻塞模式下有一个线程不断在等待缓冲区把数据交给它处理,异步的话就是触发回调方法,同步的话就继续执行同步的业务代码。

而非阻塞模式的逻辑是,客户端的连接,读取数据线程都不会被阻塞,也就是会立即返回。比如连接的逻辑是客户端发起connect连接,因为TCP连接有几次握手的情况,需要一定的时间,然而非阻塞要求立即返回,这个时候系统会抛一个异常(Win32Excetion)。

我们则只需要在异常里处理这个TCP连接需要一定时间的问题。可以循环读取TCP连接状态来确认是否连接成功。client.Poll 方法来查询当前连接状态。同理读取的时候也是在该异常里循环读取。

  1     public class SocketService
2 {
3 public delegate void TcpEventHandler1(byte[] receivebody, int length);
4 public event TcpEventHandler1 OnGetCS;
5 Socket client = null;
6 IPEndPoint endPoint = null;
7 public SocketService(string ip, int port)
8 {
9 client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
10 client.Blocking = false;//非阻塞模式,定时循环读取缓冲区的数据把它拼接到缓冲区数据队列 arrMsg
11 endPoint = new IPEndPoint(IPAddress.Parse(ip), port);
12 }
13
14 Thread rthr = null;
15 /// <summary>
16 /// 表示是否继续接收数据
17 /// </summary>
18 public bool IsRcv { get; set; }
19 /// <summary>
20 /// 非阻塞模式
21 /// </summary>
22 /// <param name="timeout"></param>
23 /// <returns></returns>
24 public bool Open(int timeout = 1000)
25 {
26 bool connected = false;
27 if (client != null && endPoint != null)
28 {
29 try
30 {
31 client.Connect(endPoint);//此处不会阻塞,如果是正在连接服务器的话,则会跑出win32Excetion异常(这里如果是netcore在linux上的话,怎么也会抛出异常,具体异常自行查阅)
32 Console.WriteLine("连接成功");
33 //启动异步监听
34 connected = true;
35 }
36 catch (Win32Exception ex)
37 {
38 if (ex.ErrorCode == 10035) // WSAEWOULDBLOCK is expected, means connect is in progress
39 {
40 var dt = DateTime.Now;
41 while (true)//循环读取当前连接的状态,如果timeout时间内还没连接成功,则反馈连接失败。
42 {
43 if (dt.AddMilliseconds(timeout) < DateTime.Now)
44 {
45 break;
46 }
47 connected = client.Poll(1000000, SelectMode.SelectWrite);//不会阻塞
48 if (connected)
49 {
50 connected = true;
51 break;
52 }
53 }
54 }
55 }
56 catch (Exception ex)
57 {
58 AbortThread();
59 Console.WriteLine("连接失败");
60 }
61 }
62 if (connected)
63 {
64 StartReceive();//连接成功则启动数据读取线程
65 }
66 return connected;
67 }
68
69 private void StartReceive()
70 {
71 rthr = new Thread(ReceiveMsgNonBlock);
72 rthr.IsBackground = true;
73 rthr.SetApartmentState(ApartmentState.STA); //设置通信线程通信线程同步设置,才能在打开接受文件时 打开 文件选择框
74 rthr.Start();
75 }
76
77 private void AbortThread()
78 {
79 if (rthr != null)
80 {
81 rthr.Abort();
82 }
83 }
84
85 public void Close()
86 {
87 if (client.Connected)
88 {
89 client.Close();
90 }
91 }
92
93 /// <summary>
94 /// app端缓冲池
95 /// </summary>
96 byte[] arrMsg = new byte[1024 * 1024];
97 /// <summary>
98 /// 当前缓冲池的长度
99 /// </summary>
100 int currentlength = 0;
101
102 /// <summary>
103 /// 读取TCP缓冲数据
104 /// </summary>
105 private void ReceiveMsgNonBlock()
106 {
107 while (true)
108 {
109 try
110 {
111 byte[] tempBytes = new byte[1024 * 1024];
112
113 int length = client.Receive(tempBytes);//此处不会阻塞,如果有数据则继续,如果没有数据则抛出Win32Exception异常(linux 下netcore自行查找异常类型 )
114
115 DealMsg(tempBytes, length);
116 }
117 catch (Win32Exception ex)
118 {
119
120 if (ex.ErrorCode == 10035) // WSAEWOULDBLOCK is expected, means connect is in progress
121 {
122 Thread.Sleep(50);
123 }
124
125 }
126 catch (Exception ex)
127 {
128 rthr.Abort();
129 client.Close();
130 client = null;
131 Console.WriteLine("服务器断开连接");
132 break;
133 }
134 }
135 }
136
137 /// <summary>
138 /// 把当前读取到的数据添加到app,并且根据自己的TCP约定的规则分析包头包尾长度校验等等信息,来确认在arrMsg中获取自己想要的数据包最后交给OnGetCS事件
139 /// </summary>
140 /// <param name="bytes"></param>
141 /// <param name="length"></param>
142 public void DealMsg(byte[] bytes, int length)
143 {
144 //先把数据拷贝到 全局数组arrMsg
145 if (bytes.Length + this.currentlength > 1024 * 1024)
146 {
147 byte[] arrMsg = new byte[1024 * 1024];
148 }
149
150 Array.Copy(bytes, 0, arrMsg, this.currentlength, length);
151 this.currentlength += length;
152
153
154 ///根据自己的包头包尾的规则来截取TCP数据包,因为实际运行当中要考虑到服务端发送特别大的数据包,以及服务器太忙的时候分段发送数据包的情况。因此不能盲目的以为读取的缓冲区的数据就是一个完成的数据包。
155 ///最终生成tmpMsg。
156 var tmpMsg = new byte[1000];
157 OnGetCS(tmpMsg, tmpMsg.Length);
158 }
159 }

非阻止模式

经过测试,通过循环主动去读取缓冲带完美的解决了客户端缓慢的问题,实际运行的时候读取缓冲区的时间间隔可以根据需求自行更改,本例中用了50ms。

C# socket 阻止模式与非阻止模式应用实例的更多相关文章

  1. UDP socket 设置为的非阻塞模式

    UDP socket 设置为的非阻塞模式 Len = recvfrom(SocketFD, szRecvBuf, sizeof(szRecvBuf), MSG_DONTWAIT, (struct so ...

  2. socket异步通信-如何设置成非阻塞模式、非阻塞模式下判断connect成功(失败)、判断recv/recvfrom成功(失败)、判断send/sendto

    socket异步通信-如何设置成非阻塞模式.非阻塞模式下判断connect成功(失败).判断recv/recvfrom成功(失败).判断send/sendto 博客分类: Linux Socket s ...

  3. XCode工程中ARC模式与非ARC模式共用(转)

    Xcode 项目中经常会融合一些老的代码,它们可能采用非ARC的模式.混合编译时,就会碰到编译出错的情况. 如何共用ARC模式和非ARC模式呢? XCode除了提供整个项目是否使用ARC模式的选择外, ...

  4. JS严格模式和非严格模式的区别

    严格模式和非严格模式的区别 //f1.js 'use strice'; //整个js文件都是严格模式下执行的 var n = 1; var foo = function(){...}; //... v ...

  5. oracle11g设置归档模式和非归档模式

    1.首先查看当前数据库是否处于归档模式            可使用如下两种方式查看 1.1  select name, log_mode from v$database;   log_mode的值为 ...

  6. Oracle之归档模式与非归档模式

    归档模式和非归档模式 在DBA部署数据库之初,必须要做出的最重要决定之一就是选择归档模式(ARCHIVELOG)或者非 归档模式(NOARCHIVELOG )下运行数据库.我们知道,Oracle 数据 ...

  7. Oracle归档模式和非归档模式的区别

    一.查看oracle数据库是否为归档模式: Sql代码1.select name,log_mode from v$database; NAME LOG_MODE ------------------ ...

  8. Oracle归档模式与非归档模式设置

    (转自:http://www.cnblogs.com/spatial/archive/2009/08/01/1536429.html) Oracle的日志归档模式可以有效的防止instance和dis ...

  9. JS-严格模式、非严格模式

    2018年11月14日晚上,我在“深入理解javascript”书上第一次知道“严格模式”“非严格模式”这2个名词: “严格模式”使用指令:“use strict”: 这个指令我其实有经常看到,在其他 ...

  10. 怎样将Oracle数据库设置为归档模式及非归档模式

    怎样将Oracle数据库设置为归档模式及非归档模式 1.Oracle日志分类 分三大类: Alert log files--警报日志,Trace files--跟踪日志(用户和进程)和 redo lo ...

随机推荐

  1. 纯Python绘制艺术感满满的山脊地图,创意满分

    1 简介 下面的这幅图是英国摇滚乐队 Joy Division 在1979年发行的其第一张录音室专辑 Unknown Pleasures 的封面,由艺术家 Peter Saville 基于射电脉冲星信 ...

  2. matlab多项式拟合以及指定函数拟合

    clc;clear all;close all;%% 多项式拟合指令:% X = [1 2 3 4 5 6 7 8 9 ];% Y = [9 7 6 3 -1 2 5 7 20]; % P= poly ...

  3. 多图详解Go的sync.Pool源码

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码时14.4 Pool介绍 总所周知Go 是一个自动垃圾回收的编程语言 ...

  4. java中使用IO流复制文件

    public class TestIO { public static void main(String[] args) { // TODO Auto-generated method stub tr ...

  5. HTTP 常用状态码200 301 302 403 500

    200(OK):成功处理了请求. 301 redirect: 301 代表永久性转移(Permanently Moved) //助记 1 永恒,如果你记住了这一条就算这篇博客没白写.302 redir ...

  6. .net通过iTextSharp.pdf操作pdf文件实现查找关键字签字盖章

    之前这个事情都CA公司去做的,现在给客户做demo,要模拟一下签字盖章了,我们的业务PDF文件是动态生成的所以没法通过坐标定位,只能通过关键字查找定位了. 之前在网上看了许多通多通过查询关键字,然后图 ...

  7. 关于IP的相关计算

    不论是考研还是考各种计算机类的证,大家或多或少都会遇到网络部分的一种题型,大体的归类就是以下几种: 已知一个IP是192.XX.XX.XX,子网掩码是255.255.255.0,那么它的网络地址是多少 ...

  8. 网页短信平台源码和开发功能介绍 思路和功能 G客短信平台

    G客短信源码介绍 (只介绍现有功能模块文字介绍配系统截图) 一:后台首页 ​ QQ:290615413 VX:290615413

  9. 建立索引和创建视图(结合YGGL.sql)

    一.请按要求对YGGL库建立相关索引 (1)使用create index 语句创建索引 1.对employees表中的员工部门号创建普通索引depart_ind. mysql> create i ...

  10. Laya 踩坑日记-BitmapFont 不显示空格

    项目中有用到艺术字,美术通过 bmfont64 将字体导给我了,结果发现在应用上 空格不显示 如图: 今天去深究了一下这个问题,发现是底层没封装好,然后自己改了一下下面是改过的 BitmapFont ...