
Socket Basic Concepts
首先介绍Socket的一些基本概念
Socket是操作系统提供的一系列网络编程接口。
网络模型分若干层,也有一些协议,比如TCP协议,UDP协议等,这些都是抽象的定义,在硬件以及操作系统级别上有一些对应的实现,Socket可以看做操作系统为开发人员提供的一系列网络编程接口,它封装了一些协议的细节,比如怎么组织数据包,怎么发送数据之类的。
Socket编程的几个基本概念
Endpoint
Endpoin指定要连接到哪里,Endpoint包括两部分内容,IP和Port,IP地址和端口组合起来才能唯一指定远程的通信端。
AddressFamily
怎么寻址,有了IP地址之后就是如何寻址的问题,常用的寻址方案是IP V4和IP V6两种类型,windows操作系统从VISTA和Windows 20008起默认支持IPV6。
Protocol
使用什么协议进行通信,比如TCP协议或者UDP协议,下面介绍Socket类型的时候还会涉及TCP和UDP等协议的介绍。
Socket类型
Socket有三种常用类型:Stream, Dgram, Raw
Stream流类型,支持可靠、双向、基于连接的字节流,使用TCP协议。
Dgram数据报类型,支持数据报,即最大长度固定的无连接、不可靠消息。消息可能会丢失或重复并可能在到达时不按顺序排列,使用UDP协议。
Raw类型支持对基础传输协议的访问,需要自己生成数据包。网上有一些RAW的例子,比如D.O.S***,ARP***,网络监控之类的。
本文只讨论Stream类型的Socket编程,RAW和Dgram不在讨论之列,也就是只讨论基于TCP协议的编程。
一些常见的概念问题
Socket和TCP/IP有什么关系?
Socket和TCP/IP不是一个层面的概念,Socket是操作系统提供的操作TCP数据的编程接口。
Sockets V4、Sockets V5有什么区别?
经常看到一些软件可以设置Sockets4/Sockets5代理,简单说他们是客户端与外网服务器之间通讯的协议,Sockets是位于应用层与传输层之间的中间层。 Sockets V4支持TCP, Sockets V5支持TCP/UDP,支持安全认证,支持IPV6。
Socket能够同时接受和发送数据吗?
TCP协议是双工的
Socket如何保证数据按顺序到达?
TCP协议来保证
Socket的基本通信模型模型
客户端:
Socket()
Connect
Send
Close
服务器端:
Socket()
Bind
Listen
Accept
Receive
Send
Close
客户端和服务器端模型是不一样的,两边是非对称的。
下面是.Net Socket编程最基本的几个类,位于命名空间System.Net.Sockets
Socket Socket接口类
TcpClient TCP客户端类
TcpListener TCP侦听类
NetworkStream 用于网络访问的基础数据流
其他经常用到的辅助类,位于命名空间System.Net
Dns 域名解析
EndPoint 标识网络地址
IPAddress IP地址。
NetworkCredential 基于密码的身份验证方案,不支持基于公钥的身份验证方法(比如ssl)
一个Socket的简单例子
输入网址,获得HTML页面的一段演示代码,只是演示Socket对象的几个主要功能,不具有实用价值。
基本步骤为:建立Socket对象,连接服务器,发送数据,然后接受数据,对应上一章介绍的Socket通信模型。
代码
01 | < span style = "font-size: 10pt;" >private string DownloadPage(string path) |
03 | Uri uri = new Uri(path); |
04 | Encoding encoding = Encoding.UTF8;// .GetEncoding("gb2312"); |
06 | string requestHeader = BuildRequestHeader(uri); |
07 | byte[] requestBytes = encoding.GetBytes(requestHeader); |
08 | byte[] receivedBytes = new byte[1024 * 100]; |
10 | Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); |
11 | socket.Connect(uri.Host,uri.Port); |
12 | socket.Send(requestBytes); |
14 | int receivedBytesLength = socket.Receive(receivedBytes); |
15 | socket.Shutdown(SocketShutdown.Both); |
18 | string html = string.Empty; |
19 | if (receivedBytesLength > 0) |
21 | html = encoding.GetString(receivedBytes, 0, receivedBytesLength); |
构造HTTP Header的代码如下,注意不要忘了Http头模板的最后一行
01 | < span style = "font-size: 10pt;" >private string BuildRequestHeader(Uri uri) |
03 | string httpHeaderTemplate = @"GET {url} HTTP/1.1 |
08 | return httpHeaderTemplate.Replace("{url}", uri.AbsolutePath) |
09 | .Replace("{host}", uri.Host); |
30秒思考题:这段简单代码有什么问题?
我们定义的用来接收数据的数组大小是固定的,如果要接受的数据超过数组大小怎么办? 可以定义一个缓冲区,每次接受固定大小的数据,直到接收完成为止。示例代码如下:
01 | < span style = "font-size: 10pt;" >MemoryStream ms = new MemoryStream(); |
04 | Console.WriteLine("Available :{0}", socket.Available); |
05 | int receivedBytesLength = socket.Receive(receivedBytes, 0, receivedBytes.Length, SocketFlags.None); |
06 | if (receivedBytesLength > 0) |
08 | ms.Write(receivedBytes, 0, receivedBytesLength); |
16 | string html = string.Empty; |
19 | html = encoding.GetString(ms.ToArray(), 0, (int)ms.Length); |
Socket的缓冲区
Socket接收数据时,操作系统先把数据接收到缓冲区,然后通知程序,socket.Available 是从已经从网络接收的、可供读取的数据的字节数,这个值是指缓冲区中已接收数据的字节数,不是实际的数据大小。而且如果网络有延迟,Send之后马上读取Available属性不一定能读到正确的值,所以不能利用socket.Available来判断总共要接受的字节数。
在上面的方法中,如果没有可读取的数据,则 Receive 方法将一直处于阻止状态,直到有数据可用,如果Server端也没有正确关闭连接,程序很容易死在这里,可以通过Socket.ReceiveTimeout来设置Socket对象接受数据的超时时间。
30秒思考题:为什么这样下载的页面有时候和浏览器下载的页面不一样?
>>gzip,chunked编码,重定向等
NetworkStream的例子
前面讲过基于TCP协议的Socket是Steam类型的,在操作系统中,为了简化编程,把设备、文件等都看作流对象,统一编程接口。NetworkStream类提供了在阻止模式下通过Socket套接字发送和接收数据的方法,.Net还提供了TcpClient和TcpListener类,用于简化同步阻止模式下通过TCP协议连接、发送和接收流数据。下面的例子是这几个对象的简单介绍,省略了一些细节,也不具有实用价值。
这个例子模拟计算机远程控制,先新起一个线程模拟服务进程,在这个线程中创建一个TcpListener对象,等待客户端连接。用户在客户端界面点了“连接”按钮后,UI线程创建TcpClient对象,等待用户输入dos命令,用户输入dos命令,按执行按钮,这时TcpClient对象把用户输入的命令发送给TcpListener对象,服务进程执行完命令后,将执行结果反馈给TcpClient对象。
部分代码。
01 | < span style = "font-size: 10pt;" >private void StartServer() |
03 | TcpListener server = new TcpListener(IPAddress.Any, 10000); |
05 | Debug.WriteLine("Server: start"); |
07 | TcpClient client = server.AcceptTcpClient(); |
08 | Debug.WriteLine("Server : connection accept"); |
09 | NetworkStream stream = client.GetStream(); |
11 | Process process = new Process(); |
12 | process.StartInfo.FileName = "cmd.exe"; |
13 | process.StartInfo.UseShellExecute = false; |
14 | process.StartInfo.RedirectStandardInput = true; |
15 | process.StartInfo.RedirectStandardOutput = true; |
16 | process.StartInfo.RedirectStandardError = true; |
17 | process.StartInfo.CreateNoWindow = false; |
20 | process.OutputDataReceived += (Object sender, DataReceivedEventArgs e) => |
22 | byte[] bytes = Encoding.GetEncoding("gb2312").GetBytes(e.Data + "\r\n"); |
23 | stream.Write(bytes, 0, bytes.Length); |
25 | process.BeginOutputReadLine(); |
29 | Byte[] buffer = new Byte[1024 * 10]; |
30 | int length = stream.Read(buffer, 0, buffer.Length); |
33 | Debug.WriteLine("Server: read 0 byte"); |
37 | string command = Encoding.GetEncoding("gb2312").GetString(buffer, 0, length); |
38 | Debug.WriteLine("Server: receive {0} ", command); |
40 | StreamWriter Writer = process.StandardInput; |
41 | Writer.WriteLine(command); |
46 | process.WaitForExit(1000); |
48 | Debug.WriteLine("Server: close"); |
52 | private void ConnectButton_Click(object sender, EventArgs e) |
54 | client = new TcpClient(); |
55 | client.Connect("localhost", 10000); |
56 | Console.WriteLine("Client: connect"); |
57 | StartButton.Enabled = true; |
60 | private void StartButton_Click(object sender, EventArgs e) |
62 | if (CommandTextBox.Text.Trim().Length == 0) |
67 | string request = CommandTextBox.Text.Trim(); |
68 | byte[] bytes = Encoding.GetEncoding("gb2312").GetBytes(request); |
69 | NetworkStream stream = client.GetStream(); |
70 | stream.Write(bytes, 0, bytes.Length); |
72 | Console.WriteLine("Client: request {0}", request); |
73 | MessageTextBox.AppendText("\r\nresponse from server:\r\n"); |
75 | byte[] buffer = new byte[1024]; |
77 | int receivedBytesLength = stream.Read(buffer, 0, buffer.Length); |
78 | if(receivedBytesLength > 0) |
80 | string text = Encoding.GetEncoding("gb2312").GetString(buffer, 0, receivedBytesLength); |
81 | MessageTextBox.AppendText(text); |
82 | MessageTextBox.AppendText("\r\n"); |
88 | }while(stream.DataAvailable); |
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>