谈到服务器的并行处理能力,人们总是会很容易想到TPS等数据指标,当然得利于前人的许多工具我们可以通过十分简单的步骤得出这些数据,然后输出一份十分养眼的报告。不得不承认这些工具提高了生产力,不过也正是这些东西让我们误以为“到此为止”了
当今许多项目,框架,甚至协议都被设为一层层的结构操作系统是的应用程序,网络中的通信协议,当然社会生活中也是这样层层分工。有了这样的结构完成目标变的非常的容易,再也不用关注”无关“的事物了。(socket正是一个这样的例子,它由操作系统实现,让网络通信变的无比简单)
不过个人认为作为保证事物按预期发展的测试人员应该了解更多,高并发到底意味着什么,socket背后的网络行为等等
其实网络数据的并发当然是个十分复杂的东西,不过前提是网络,我们不能简单的描述为”发送一次请求“,当我们向操作系统申请”发送一次请求“,后面发生了很多事情这些事情我们有必要知道
我们先简单的回忆一下必要的网络知识
以上是TCP/IP的四层模型(TCP/IP不是只包括tcp跟ip这2个东西,是一堆协议的集合),后面2层路由等设备关注可能会比较多,我们现在暂时只关注传输层
那既然是网络并发总是要”发“的吧,提到发那还必须先知道是使用合种协议去发送,主流的tcp,http等都很常见(通常很少有自己创造一种协议去进行通信的,即使有的公司内部的通信协议一般也是会基于tcp等基础传输协议)
现在以tcp协议为例,既然要发送那肯定是要先连接起来的(大多数协议都是需要连接的,即便http称是无连接的,不过http也是基于tcp的应用协议,所以连接肯定是少不了的,只是连接完成后会主动断开),为了了解连接的过程先回顾下tcp的握手过程吧
完成3次握手后连接就算完成了,那问题来了发送数据前要先连接,那连接的数量有没有限制呢,一些观点认为计算机端口的数量限制着连接数量,这种看法当然是不正确的,端口的数量只是限制着当前计算机客户端的数量而已。其实理解这个东西也十分简单,只是一直借助socket让我们对其本身少了很多思考,socket由操作系统构架与传输层与应用层直接,它的便利性让我们对端口模糊化了。而实际上在网络层的ip包并没有端口号这个数据位,显然端口其实是应用层面的数据分组,操作系统(当然包括网络设备的操作系统)接收所有数据,然后根据数据包里面的端口信息决定数据的去向,对于windows来说上层应用程序向系统申请监听xxx端口,你们操作系统会在自己接收到的包中发现是xxx端口的就会给前面的应用程序一份(还有一点提一下:端口不一定会被某一个应用程序独占)。也就是说服务器应用程序只要监听一个端口就可以收到任意计算机的任意端口的数据包。
事实上单台PC可以同时维持的tcp的连接数是十分巨大的,通过对协议的分析我们会发现,建立一个连接也只需要3次握手就完成了如果后面没有其他数据通信(如心跳什么的业务数据)这个连接是不会有什么其他消耗的,所以为了维持连接也仅仅需要储存远程客户端地址等少量信息而已,通过实际测试个人计算机的同时连接数可以轻松达到10w的数量级(由于单台pc客户端的数量有限制,而测试的pc数量有限也没有继续向上测试,有兴趣的朋友可以自己尝试下)
1 IPAddress ip;
2 TcpListener listener;
3 List<Socket> mySockets;
4 Thread myListenThread;
5
6
7 private void Form1_Load(object sender, EventArgs e)
8 {
9 ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
10 //ip = IPAddress.Parse("127.0.0.1");
11 listener = new TcpListener(ip, 8500);
12 }
13
14 private void bt_start_Click(object sender, EventArgs e)
15 {
16 try
17 {
18 ip = IPAddress.Parse(tb_ip.Text);
19 listener = new TcpListener(ip, 8500);
20 }
21 catch(Exception ex)
22 {
23 MessageBox.Show(ex.Message);
24 return;
25 }
26 if (myListenThread == null)
27 {
28 myListenThread = new Thread(new ThreadStart(StartGetSocket));
29 myListenThread.IsBackground = true;
30 myListenThread.Start();
31 }
32 else
33 {
34 if(myListenThread.IsAlive)
35 {
36 listener.Stop();
37 myListenThread.Abort();
38 ShowMes("STOP");
39 }
40 else
41 {
42 myListenThread = null;
43 }
44 bt_start_Click(null,null);
45 }
46 }
47
48
49 private void StartGetSocket()
50 {
51 byte[] myBytes=new byte[1024];
52 listener.Start();
53 mySockets = new List<Socket>();
54 while(true)
55 {
56 while(listener.Pending())
57 {
58 Socket nowSocket = listener.AcceptSocket();
59 ShowMes("成功连接主机:" + nowSocket.RemoteEndPoint.ToString());
60 mySockets.Add(nowSocket);
61 }
62 foreach(Socket tempSocket in mySockets)
63 {
64 while (tempSocket.Available > 0)
65 {
66 tempSocket.Receive(myBytes);
67 ShowMes(tempSocket.RemoteEndPoint.ToString());
68 //ShowMes(byteToHexStr(myBytes));
69 ShowMes(Encoding.Unicode.GetString(myBytes));
70 }
71 }
72 }
73 }
74
75 private void ShowMes(string mes)
76 {
77 if(this.rtb_info.InvokeRequired)
78 {
79 rtb_info.BeginInvoke(new Action<string>((arg1) => rtb_info.AppendText(arg1)), mes+"\r\n");
80 }
81 else
82 {
83 rtb_info.AppendText(mes + "\r\n");
84 }
85 }
以上是简单的服务端测试代码,十分清晰简单所以也没有写任何注释。
以上可以看出来连接的数量一般是不会成为瓶颈(注意连接的建立还是需要少量消耗的,当然这个消耗一般存在于网络上我们现在暂时只关注与对服务器本身的消耗),当然重要的还是发的内容,一般情况下在网络条件允许的情况下,任何个人计算机都可以发起相当的服务请求。假如数据很快通过网络设备到达服务器,这个时候服务器对这些数据的处理能力及方式就显的至关重要。打个比方操作系统但钱已经获取到1000条针对当前服务的请求,如果服务程序依次向操作系统获取然后处理那后面的请求必然会阻塞。以前面提供的code为例,即时单独开辟一条线程出来获取服务器的数据,然后再以异步的形式把数据委托到UI线程去显示,再请求过多(或每秒事物达到一定量)时,就会明显发现显示滞后。
服务器接收到的业务数据绝不是为了简单的显示,它需要做的事情更多,如何用最小的代价完成当前业务成为了制约处理能力(并发量)的关键(当然也许多分布式部署几台服务器或增加些硬件配置会有不错的效果)
若是作为测试人员就必须了解服务的应用场景,实际业务,及用户的可能行为,当然每次业务的每一步服务器的行为及消耗也应该了解,这也是制定高效测试业务流程的基础。