对socket的理解
要想理解socket,就得先熟悉TCP/IP协议族,TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准。
从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中。
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,如下图所示:
那我们要找的socket在哪里呢?
Socket
我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
Socket通信流程:
在此,Socket可以像流Stream一样视为一个数据通道,这个通道假设在应用程序端(客户端)和远程服务器端之间,而后,数据的读取(接收)和写入(发送)均针对这个通道来进行。在应用程序段或者服务器端创建了Socket对象之后,就可以使用Send/SentTo方法将数据发送到连接的Socket上,或者使用Receive/ReceiveFrom方法接收来自连接Socket的数据。
在使用Socket之前,首先要创建Socket对象实例,这可以通过Socket类的构造函数方法来实现:
public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType);
其中,addressFamily参数指定Socket使用的寻址方案,socketType参数指定Socket的类型,protocolType参数指定Socket使用的协议。
创建一个Socket,可用于基于TCP/IP的网络(如Internet)上通讯
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
若要使用UDP而不是TCP,需要更改协议类型:
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
一旦创建Socket成功,在客户端,你九可以通过Connect方法连接到指定的服务器,并通过Send/SendTo方法向远程服务器发送数据,而后可以通过Receive/ReceiveFrom方法从服务器端接收数据;而在服务器端,你需要使用Bind方法绑定所指定的接口使Socket与一个本地端点相联,并通过Listen方法监听该接口上的请求,当监听到用户端的连接时,调用Accept完成连接的操作,创建新的Socket以处理传入的连接请求。使用完Socket后,记住使用ShutDown方法禁用Socket,并使用Close方法关闭Socket。其间用到的方法/函数有:
Scoket.Connect();建立到远程设备的连接;
public void Connect(EndPoint remoteEP)(有重载方法)
Socket.Send();从数据中的指示位置开始讲数据发送到连接的Socket;
public int Send(byte[],EndPoint);(有重载方法)
Socket.SendTo();将数据发送到特定终结点;
public int SendTo(byte[],EndPoint);(有重载方法)
Socket.Receive();将数据从连接的Socket接收到接收缓冲区的特定位置;
public int Receive(byte[],int,SocketFlags);
Socket.ReceiveFrom();接收数据缓冲区中特定位置的数据并存储终结点;
public int ReceiveFrom(byte[],int,SocketFlags,ref EndPoint);
Socket.Bind();使Socket与一个bending终结点相关联;
public void Bind(EndPoint LoaclEP);
Socket.Listen();将Socket置于侦听状态。
public void Listen(int backlog);
Socket.Accept();创建新的Socket以处理传入的连接请求;
public Socket.Accept();
Socket.ShutDown();禁用某Socket上的发送和接收;
public void Shutdown(SocketShutdown how);
Socket.Close();强制Socket连接关闭;
public void Close();
以上许多方法包含EndPoint类型的参数,在Internet中,TCP/IP使用一个网络地址和一个服务端口号来唯一标识设备。网络地址标识网络上的特定设备;端口号标识要连接到该设备上的特定服务。网络地址和服务端口的组合成为终结点。在.net框架中正是由EndPoint类表示这个终结点,它提供表示网络资源或服务的抽象,用以标志网络地址等信息。.NET同时也为每个受支持的地址族定义了EndPoint的子代;对于IP地址族,该类为IPEndPoint。IPEndPoint类包含应用程序连接到主机上的服务所需的主机和端口信息,通过组合服务的主机IP地址和端口号,IPEndPoint类形成到服务的连接点。
用到EndPoint类的时候就不可避免的设计到计算机IP地址,.NET中有两类可以得到IP地址实例:
IPAddress类:IPAddress类包含计算机在IP网络上的地址,其Parse方法可将IP地址字符串转换为IPAddress实例。下面的语句创建一个IPAddress实例:
IPAddress myIP = IPAddress.Parse("192.168.1.2");
DNS类:向使用TCP/IP Internet服务的应用程序提供域名服务。其Resolve方法查询DNS服务器以将用户友好的域名(如:”host.contoso.com“)映射到数字形式的Internet地址(如:192.168.1.1)。Resolve方法返回一个IPHostEnty实例,该实例包含所请求名称的地址和别名的列表。大多数情况下,可以使用AddressList数组中返回的第一个地址。下面的代码获取一个IPAddress实例,该实例包含服务器host.contoso.com的IP地址。
IPHostEntry ipHostInfo = Dns.Resolve("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
你也可以使用GetHostName方法得到IPHostEntry实例:
IPHosntEntry hostInfo=Dns.GetHostByName("host.contoso.com")
在使用以上方法时,你将可能需要处理以下几种异常:
- SocketException异常:访问Socket时操作系统发生错误引发
- ArgumentNullException异常:参数为空引用引发
- ObjectDisposedException异常:Socket已经关闭引发
在掌握上面得知识后,下面的代码将该服务器主机( host.contoso.com的 IP 地址与端口号组合,以便为连接创建远程终结点:
IPEndPoint ipe = new IPEndPoint(ipAddress,11000);
确定了远程设备的地址并选择了用于连接的端口后,应用程序可以尝试建立与远程设备的连接。下面的示例使用现有的 IPEndPoint 实例与远程设备连接,并捕获可能引发的异常:
1 try { 2 s.Connect(ipe);//尝试连接 3 } 4 //处理参数为空引用异常 5 catch(ArgumentNullException ae) { 6 Console.WriteLine("ArgumentNullException : {0}", ae.ToString()); 7 } 8 //处理操作系统异常 9 catch(SocketException se) { 10 Console.WriteLine("SocketException : {0}", se.ToString()); 11 } 12 catch(Exception e) { 13 Console.WriteLine("Unexpected exception : {0}", e.ToString()); 14 }
需要知道的是:Socket 类支持两种基本模式:同步和异步。其区别在于:在同步模式中,对执行网络操作的函数(如 Send 和 Receive)的调用一直等到操作完成后才将控制返回给调用程序。在异步模式中,这些调用立即返回。
另外,很多时候,Socket编程视情况不同需要在客户端和服务器端分别予以实现,在客户端编制应用程序向服务端指定端口发送请求,同时编制服务端应用程序处理该请求,这个过程在上面的阐述中已经提及;当然,并非所有的Socket编程都需要你严格编写这两端程序;视应用情况不同,你可以在客户端构造出请求字符串,服务器相应端口捕获这个请求,交由其公用服务程序进行处理。以下事例语句中的字符串就向远程主机提出页面请求:
string Get = "GET / HTTP/1.1rnHost: " + server + "rnConnection: Closernrn";
远程主机指定端口接受到这一请求后,就可利用其公用服务程序进行处理而不需要另行编制服务器端应用程序。
综合运用以上阐述的使用Visual C#进行Socket网络程序开发的知识,下面的程序段完整地实现了Web页面下载功能。用户只需在窗体上输入远程主机名(Dns 主机名或以点分隔的四部分表示法格式的 IP 地址)和预保存的本地文件名,并利用专门提供Http服务的80端口,就可以获取远程主机页面并保存在本地机指定文件中。如果保存格式是.htm格式,你就可以在Internet浏览器中打开该页面。适当添加代码,你甚至可以实现一个简单的浏览器程序。
实现此功能的主要源代码如下:
1 //"开始"按钮事件 2 private void button1_Click(object sender, System.EventArgs e) { 3 //取得预保存的文件名 4 string fileName=textBox3.Text.Trim(); 5 //远程主机 6 string hostName=textBox1.Text.Trim(); 7 //端口 8 int port=Int32.Parse(textBox2.Text.Trim()); 9 //得到主机信息 10 IPHostEntry ipInfo=Dns.GetHostByName(hostName); 11 //取得IPAddress[] 12 IPAddress[] ipAddr=ipInfo.AddressList; 13 //得到ip 14 IPAddress ip=ipAddr[0]; 15 //组合出远程终结点 16 IPEndPoint hostEP=new IPEndPoint(ip,port); 17 //创建Socket 实例 18 Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); 19 try 20 { 21 //尝试连接 22 socket.Connect(hostEP); 23 } 24 catch(Exception se) 25 { 26 MessageBox.Show("连接错误"+se.Message,"提示信息 27 ,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information); 28 } 29 //发送给远程主机的请求内容串 30 string sendStr="GET / HTTP/1.1rnHost: " + hostName + 31 "rnConnection: Closernrn"; 32 //创建bytes字节数组以转换发送串 33 byte[] bytesSendStr=new byte[1024]; 34 //将发送内容字符串转换成字节byte数组 35 bytesSendStr=Encoding.ASCII.GetBytes(sendStr); 36 try 37 { 38 //向主机发送请求 39 socket.Send(bytesSendStr,bytesSendStr.Length,0); 40 } 41 catch(Exception ce) 42 { 43 MessageBox.Show("发送错误:"+ce.Message,"提示信息 44 ,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information); 45 } 46 //声明接收返回内容的字符串 47 string recvStr=""; 48 //声明字节数组,一次接收数据的长度为1024字节 49 byte[] recvBytes=new byte[1024]; 50 //返回实际接收内容的字节数 51 int bytes=0; 52 //循环读取,直到接收完所有数据 53 while(true) 54 { 55 bytes=socket.Receive(recvBytes,recvBytes.Length,0); 56 //读取完成后退出循环 57 if(bytes〈=0) 58 break; 59 //将读取的字节数转换为字符串 60 recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes); 61 } 62 //将所读取的字符串转换为字节数组 63 byte[] content=Encoding.ASCII.GetBytes(recvStr); 64 try 65 { 66 //创建文件流对象实例 67 FileStream fs=new FileStream(fileName,FileMode.OpenOrCreate,FileAccess.ReadWrite); 68 //写入文件 69 fs.Write(content,0,content.Length); 70 } 71 catch(Exception fe) 72 { 73 MessageBox.Show("文件创建/写入错误:"+fe.Message,"提示信息",MessageBoxButtons.RetryCancel,MessageBoxIcon.Information); 74 } 75 //禁用Socket 76 socket.Shutdown(SocketShutdown.Both); 77 //关闭Socket 78 socket.Close(); 79 } 80 }
参考:http://blog.sina.com.cn/s/blog_7315615d0101wvom.html
http://goodcandle.cnblogs.com/archive/2005/12/10/294652.aspx
http://www.cnblogs.com/dolphinX/p/3460545.html