运行环境: vs2013
框架: .net4.5
上次实验中已经实现了单线程下的socket的tcp服务器
由于使用浏览器并不能直观的显示socket之间的交互相应,所以这次实验我们先完成客户端部分的编程再进行服务端编程的完善
同样的先建立好一个新的项目
客户端部分需要两个操作才能成功连接
套接字建立部分与服务端一致
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
然后就是进入连接部分
先建立待连接的ip与端口对象
IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7777);
s.Connect(ipe);
知道这些后我们编程实现一下连接功能
编码完成进行一下测试,先启动我们的server程序等待连接
再打开client程序请求连接
此时我们观察到两个程序直接已经通过socket建立通信
此时再完善客户端的读写操作
对于客户端而已读写操作跟服务端是没有任何区别的,本质是这个阶段并不区分客户端与服务端,其读写是对等的
s.Send(buf, buf.Length, SocketFlags.None);
bytes = s.Receive(buf, buf.Length, SocketFlags.None);
发送与接收
确定没问题后我们进行测试,测试方法同上
上面是服务端下面是客户端
过程中客户端发送接受一次数据,服务端接受发送一次数据
由于服务器部分并没有使用多线程的设置导致该程序不能同时连接多个客户端,这时候我们需要把服务器的Accept()方法放入线程中,通过while(true)方式循环多次接受客户端的连接,为了能更快的处理客户端的一次连接,以及处理长时间连接的客户端,还虽然再产生新的线程去处理接收和发送部分的工作
确定设计思路后我们先引入线程的引用部分
using System.Threading; 
对原先的代码该如何修改呢
首先要修改最大连接数
s.Listen(10);//代表最大可提供10个连接队列
然后原本Socket temp = s.Accept();放入线程中处理
这时候就要建立一个线程函数
需要传入服务器的socket对象,循环接受对象的连接,暂时未处理读写部分
Thread listen = new Thread(Listen);
listen.Start(s);
主函数中创建线程与带server对象的启动线程
注释掉之前的代码
然后测试一下是否能多个进程连接到该服务器
四个单独的客户端都可以连接到服务器,由于服务器并没有做接收和发送处理,客户端都处于阻塞状态等待数据接收
接下来处理服务端的数据接收与发生
创建一个用于线程的函数,其中只是简单复制一下前面的发送接收的代码用于测试
在原来的监听线程上,创建一个数据的接收与发生线程用于跟客户端连接,实际上客户端可能需要一个长连接多次处理数据,如果都放入监听线程中处理会影响下一个客户端的连接
尝试对三个客户端同时连接,皆成功连接,但实际上我手动操作并不能体现客户端的高并发的效果,这事我们尝试修改客户端线程并发连接服务器看起是否能正常使用
在客户端主线程中
只加入一个循环的线程客户端创建
并且每创建一个阻塞10ms防止客户端过多导致内存溢出
线程函数里面复制原来的客户端代码,并加入异常处理防止出错程序退出
运行程序我们可以看到客户端与服务器快速连接
启动两个客户端并发操作会出现大量的服务器拒绝连接,因为同一时间服务器仅仅提供指定数量的客户端连接,所以大规模的并发会有部分客户端无法成功连接而抛出异常
对此我们完成了客户端与服务端的并发多线程处理,下面我们尝试客户端长连接服务器并提交请求
对于http协议而言有个http请求头
Connection: Keep-Alive
这个请求头决定服务器是否提供长连接还是返回数据后立马断开连接,对于请求多个静态文件的网页比较有效的减少连接次数,但是也会造成性能的降低,必须设定超时并在那段时间内消耗不必要的系统资源
接下来我们要做一个能够通过浏览器访问的服务器
首先我们来观察一次http请求浏览器到底给我们服务器发送了什么数据呢
通过fiddler观察到其数据的内容
有多个请求内容,由于我们只做一个简单可用的服务器,暂时忽略一些较为复杂的扩展处理功能,只针对get方法进行处理
string[] split = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
首先第一步对从客户端接收到的数据中获取http头请求信息,再通过\r\n分割信息
通过断点跟踪可以看到具体的内容,取get中的请求内容再切割
string[] get = split[0].Split(new Char[] { ' '});
可以拿到具体的网页请求字符串
这里头我们尝试假设了几个不同的静态文件,index首页文件,css文件,已经两种不同的响应头
然后在程序中
根据不同的请求,提供相应的返回内容,非正常请求返回404错误
然后进行超时处理
temp.ReceiveTimeout = 500;
可以对socket的接收设定超时值500ms,只要接收到数据到下一次接收之间时间差距不超过500ms就可以不断开连接,对于少静态文件的网页可能不明显,但是对于多静态文件的网站可以有效减少重复连接次数
超时会进入异常,关闭套接字,其他异常则捕抓输出
但是实际使用浏览器的过程中,浏览器依然是发起了三次连接,一个是请求首页文件,另一个的通过首页文件触发连接请求css文件,最后一个是chrome自身对网站图标的请求,该请求被忽略返回404
通过chrome我们可以看到请求情况
运行效果等
使用ie是只请求两次并关闭两次连接,在请求第二次前第一次的连接并没有关闭,对此有点疑惑,也许是我理解有所偏差
fiddler也能跟踪到数据的收发
可以证明该服务器可以正确相应简单的http请求已经多线程实现
最后修改客户端来模仿ie请求http
 
主要是将请求的字符串修改为标准的http头
这里只请求127.0.0.1:7777/的内容
也就是服务器的index文件
由于服务器并没有提供其他更为高级的功能这里就不写过多的请求头了
接收部分要重复多次接收才能完全读取所有的服务器返回数据
成功获取服务器的html文本
也可以并发获取文本信息
通过该实验我掌握了socket编程的一些更高级的用法,通过多线程来实现对客户端的请求处理,通过从客户端获取的请求字符串来反馈信息,同步处理信息等,在异步处理上仍有待学习,对于并发过程中出现一些错误有所不理解,仍有待研究,通过该实验对网络编程的理解有所提高。
来源:oschina
链接:https://my.oschina.net/u/1469400/blog/494667