套接字类型:
套接字类型是指创建套接字的应用程序要使用的通信服务类型。linux系统支持多种套接字类型,最常用的有以下三种:
(1)SOCK_STREAM:流式套接字,提供面向连接、可靠的数据传输服务,数据按字节流、按顺序收发,保证在传输过程中无丢失、无冗余。TCP协议支持该套接字。
(2)SOCK_DGRAM:数据报套接字,提供面向无连接的服务,数据收发无序,不能保证数据的准确到达。UDP协议支持该套接字。
(3)SOCK_RAW:原始套接字。允许对低于传输层的协议或物理网络直接访问,例如可以接收和发送ICMP报文。常用于检测新的协议。
回调函数:
一直不太理解回调函数的作用,下面是找到的一些关于回调函数的作用的解答。
1.回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。
2.回调函数就相当于一个中断处理函数,由系统在符合你设定的条件时自动调用。为此,你需要做三件事:1,声明;2,定义;3,设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。
3.所谓回调函数就是按照一定的形式由你定义并编写实现内容,当发生某种事件时,而由系统或其它函数来调用的函数。使用回调函数实际上就是在调用某个函数时,将自己编写的一个函数的地址作为参数传递给那个函数。而那个函数在需要的时候,也就是某种事情发生的时候,利用传递的函数地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。回调函数只能是全局函数,或者是静态函数,因为这个函数只是在这个类中使用,所以为了维护类的完整性,我们用类的静态成员函数来做回调函数。
4.对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经熟知了函数指针。
(1)什么是回调函数?
简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
(2)为什么要使用回调函数?
因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。
如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它
提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。
回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer()API 使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。
另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。
不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。
c#socket编程基础
https://www.doczj.com/doc/bd1546678.html, Framework为应用程序访问Internet提供了分层的、可扩展的以及受管辖的网络服务,其名字空间https://www.doczj.com/doc/bd1546678.html,和https://www.doczj.com/doc/bd1546678.html,.Sockets包含丰富的类可以开发多种网络应用程序。.Net类采用的分层结构允许应用程序在不同的控制级别上访问网络,开发人员可以根据需要选择针对不同的级别编制程序,这些级别几乎囊括了Internet的所有需要--从socket 套接字到普通的请求/响应,更重要的是,这种分层是可以扩展的,能够适应Internet不断扩展的需要。
抛开ISO/OSI模型的7层构架,单从TCP/IP模型上的逻辑层面上看,.Net类可以视为包含3个层次:请求/响应层、应用协议层、传输层。WebReqeust和WebResponse代表了请求/响应层,支持Http、Tcp和Udp的类组成了应用协议层,而Socket类处于传输层。
传输层位于这个结构的最底层,当其上面的应用协议层和请求/响应层不能满足应用程序的特殊需要时,就需要使用这一层进行Socket套接字编程。
而在.Net中,https://www.doczj.com/doc/bd1546678.html,.Sockets命名空间为需要严密控制网络访问的开发人员提供了Windows Sockets(Winsock)接口的托管实现。https://www.doczj.com/doc/bd1546678.html,命名空间中的所有其他网络访问类都建立在该套接字Socket实现之上,如TCPClient、TCPListener和UDPClient类封装有关创建到Internet的TCP和UDP连接的详细信息;NetworkStream类则提供用于网络访问的基础数据流等,常见的许多Internet服务都可以见到Socket的踪影,如Telnet、Http、Email、Echo等,这些服务尽管通讯协议Protocol的定义不同,但是其基础的传输都是采用的Socket。
其实,Socket可以象流Stream一样被视为一个数据通道,这个通道架设在应用程序端(客
户端)和远程服务器端之间,而后,数据的读取(接收)和写入(发送)均针对这个通道来进行。
可见,在应用程序端或者服务器端创建了Socket对象之后,就可以使用Send/SentTo方法将数据发送到连接的Socket,或者使用Receive/ReceiveFrom方法接收来自连接Socket的数据;
针对Socket编程,.NET框架的Socket类是Winsock32API提供的套接字服务的托管代码版本。其中为实现网络编程提供了大量的方法,大多数情况下,Socket类方法只是将数据封送到它们的本机Win32副本中并处理任何必要的安全检查。如果你熟悉Winsock API函数,那么用Socket类编写网络程序会非常容易,当然,如果你不曾接触过,也不会太困难,跟随下面的解说,你会发觉使用Socket类开发windows网络应用程序原来有规可寻,它们在大多数情况下遵循大致相同的步骤。
在使用之前,你需要首先创建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。其间用到的方法/函数有:
Socket.Connect方法:建立到远程设备的连接
public void Connect(EndPoint remoteEP)(有重载方法)
Socket.Send方法:从数据中的指示位置开始将数据发送到连接的Socket。
public int Send(byte[],int,SocketFlags);(有重载方法)
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与一个本地终结点相关联:
public void Bind(EndPoint localEP);
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类形成到服务的连接点。
用到IPEndPoint类的时候就不可避免地涉及到计算机IP地址,.Net中有两种类可以得到IP 地址实例:
IPAddress类:IPAddress类包含计算机在IP网络上的地址。其Parse方法可将IP地址字符串转换为IPAddress实例。下面的语句创建一个IPAddress实例:
IPAddress myIP=IPAddress.Parse("192.168.1.2");
Dns类:向使用TCP/IP Internet服务的应用程序提供域名服务。其Resolve方法查询DNS服务器以将用户友好的域名(如"https://www.doczj.com/doc/bd1546678.html,")映射到数字形式的Internet地址(如192.168.1.1)。Resolve方法返回一个IPHostEnty实例,该实例包含所请求名称的地址和别名的列表。大多数情况下,可以使用AddressList数组中返回的第一个地址。下面的代码获取一个IPAddress实例,该实例包含服务器https://www.doczj.com/doc/bd1546678.html,的IP地址。
IPHostEntry ipHostInfo=Dns.Resolve("https://www.doczj.com/doc/bd1546678.html,");
IPAddress ipAddress=ipHostInfo.AddressList[0];
你也可以使用GetHostName方法得到IPHostEntry实例:
IPHosntEntry hostInfo=Dns.GetHostByName("https://www.doczj.com/doc/bd1546678.html,")
在使用以上方法时,你将可能需要处理以下几种异常:
SocketException异常:访问Socket时操作系统发生错误引发ArgumentNullException异常:参数为空引用引发
ObjectDisposedException异常:Socket已经关闭引发
在掌握上面得知识后,下面的代码将该服务器主机(https://www.doczj.com/doc/bd1546678.html,的IP地址与端口号组合,以便为连接创建远程终结点:
IPEndPoint ipe=new IPEndPoint(ipAddress,11000);
确定了远程设备的地址并选择了用于连接的端口后,应用程序可以尝试建立与远程设备的连接。下面的示例使用现有的IPEndPoint实例与远程设备连接,并捕获可能引发的异常:
try{
s.Connect(ipe);//尝试连接
}
//处理参数为空引用异常
catch(ArgumentNullException ae){
Console.WriteLine("ArgumentNullException:{0}",ae.ToString());
}
//处理操作系统异常
catch(SocketException se){
Console.WriteLine("SocketException:{0}",se.ToString());
}
catch(Exception e){
Console.WriteLine("Unexpected exception:{0}",e.ToString());
}
需要知道的是:Socket类支持两种基本模式:同步和异步。其区别在于:在同步模式中,对执行网络操作的函数(如Send和Receive)的调用一直等到操作完成后才将控制返回给调用程序。在异步模式中,这些调用立即返回。
另外,很多时候,Socket编程视情况不同需要在客户端和服务器端分别予以实现,在客户
端编制应用程序向服务端指定端口发送请求,同时编制服务端应用程序处理该请求,这个过程在上面的阐述中已经提及;当然,并非所有的Socket编程都需要你严格编写这两端程序;视应用情况不同,你可以在客户端构造出请求字符串,服务器相应端口捕获这个请求,交由其公用服务程序进行处理。以下事例语句中的字符串就向远程主机提出页面请求:
string Get="GET/HTTP/1.1\r\nHost:"+server+"\r\nConnection:Close\r\n\r\n";
远程主机指定端口接受到这一请求后,就可利用其公用服务程序进行处理而不需要另行编制服务器端应用程序。
综合运用以上阐述的使用Visual C#进行Socket网络程序开发的知识,下面的程序段完整地实现了Web页面下载功能。用户只需在窗体上输入远程主机名(Dns主机名或以点分隔的四部分表示法格式的IP地址)和预保存的本地文件名,并利用专门提供Http服务的80端口,就可以获取远程主机页面并保存在本地机指定文件中。如果保存格式是.htm格式,你就可以在Internet浏览器中打开该页面。适当添加代码,你甚至可以实现一个简单的浏览器程序。
实现此功能的主要源代码如下:
//"开始"按钮事件
private void button1_Click(object sender,System.EventArgs e){
//取得预保存的文件名
string fileName=textBox3.Text.Trim();
//远程主机
string hostName=textBox1.Text.Trim();
//端口
int port=Int32.Parse(textBox2.Text.Trim());
//得到主机信息
IPHostEntry ipInfo=Dns.GetHostByName(hostName);
//取得IPAddress[]
IPAddress[]ipAddr=ipInfo.AddressList;
//得到ip
IPAddress ip=ipAddr[0];
//组合出远程终结点
IPEndPoint hostEP=new IPEndPoint(ip,port);
//创建Socket实例
Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); try
{
//尝试连接
socket.Connect(hostEP);
}
catch(Exception se)
{
MessageBox.Show("连接错误"+se.Message,"提示信息
,MessageBoxButtons.RetryCancel,https://www.doczj.com/doc/bd1546678.html,rmation); }
//发送给远程主机的请求内容串
string sendStr="GET/HTTP/1.1\r\nHost:"+hostName+
"\r\nConnection:Close\r\n\r\n";
//创建bytes字节数组以转换发送串
byte[]bytesSendStr=new byte[1024];
//将发送内容字符串转换成字节byte数组
bytesSendStr=Encoding.ASCII.GetBytes(sendStr);
try
{
//向主机发送请求
socket.Send(bytesSendStr,bytesSendStr.Length,0);
}
catch(Exception ce)
{
MessageBox.Show("发送错误:"+ce.Message,"提示信息
,MessageBoxButtons.RetryCancel,https://www.doczj.com/doc/bd1546678.html,rmation); }
//声明接收返回内容的字符串
string recvStr="";
//声明字节数组,一次接收数据的长度为1024字节
byte[]recvBytes=new byte[1024];
//返回实际接收内容的字节数
int bytes=0;
//循环读取,直到接收完所有数据
while(true)
{
bytes=socket.Receive(recvBytes,recvBytes.Length,0);
//读取完成后退出循环
if(bytes〈=0)
break;
//将读取的字节数转换为字符串
recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes);
}
//将所读取的字符串转换为字节数组
byte[]content=Encoding.ASCII.GetBytes(recvStr);
try
{
//创建文件流对象实例
FileStream fs=new FileStream(fileName,FileMode.OpenOrCreate,FileAccess.ReadWrite);
//写入文件
fs.Write(content,0,content.Length);
}
catch(Exception fe)
{
MessageBox.Show("文件创建/写入错误:"+fe.Message,"提示信息",MessageBoxButtons.RetryCancel,https://www.doczj.com/doc/bd1546678.html,rmation);
}
//禁用Socket
socket.Shutdown(SocketShutdown.Both);
//关闭Socket
socket.Close();
}
}
程序在WindowsXP中文版、.Net Frameworkd中文正式版、Visual https://www.doczj.com/doc/bd1546678.html,中文正式版下调试通过
关于作者
宋华,96年毕业于承德石油学院电子与电气系计算机应用专业,同年进入中国石油天然气集团公司吐哈油田工作,一直从事网络规划与建设、网站架构与设计,现专门从事Internet 应用程序开发及Windows应用程序开发。
Visual C#.Net网络程序开发-Tcp篇
前一篇《Visual C#.Net网络程序开发-Socket篇》中说到:支持Http、Tcp和Udp的类组成了TCP/IP三层模型(请求响应层、应用协议层、传输层)的中间层-应用协议层,该层的类比位于最底层的Socket类提供了更高层次的抽象,它们封装TCP和UDP套接字的创建,不需要处理连接的细节,这使得我们在编写套接字级别的协议时,可以更多地尝试使用TCPClient、UDPClient和TcpListener,而不是直接向Socket中写。它们之间的这种层次关系示意如下:
可见,TcpClient类基于Socket类构建,这是它能够以更高的抽象程度提供TCP服务的基础。正因为这样,许多应用层上的通讯协议,比如FTP(File Transfers Protocol)文件传输协议、HTTP(Hypertext Transfers Protocol)超文本传输协议等都直接创建在TcpClient等类之上。
TCPClient类使用TCP从Internet资源请求数据。TCP协议建立与远程终结点的连接,然后使用此连接发送和接收数据包。TCP负责确保将数据包发送到终结点并在数据包到达时
以正确的顺序对其进行组合。
从名字上就可以看出,TcpClient类专为客户端设计,它为TCP网络服务提供客户端连接。TcpClient提供了通过网络连接、发送和接收数据的简单方法。
若要建立TCP连接,必须知道承载所需服务的网络设备的地址(IPAddress)以及该服务用于通讯的TCP端口(Port)。Internet分配号码机构(Internet Assigned Numbers Authority,IANA)定义公共服务的端口号(你可以访问https://www.doczj.com/doc/bd1546678.html,/assignments/port-numbers获得这方面更详细的资料)。IANA列表中所没有的服务可使用1,024到65,535这一范围中的端口号。要创建这种连接,你可以选用TcpClient类的三种构造函数之一:
1、public TcpClient()当使用这种不带任何参数的构造函数时,将使用本机默认的ip地址并将使用默认的通信端口号0。这样情况下,如果本机不止一个ip地址,将无法选择使用。以下语句示例了如何使用默认构造函数来创建新的TcpClient:
TcpClient tcpClientC=new TcpClient();
2、public TcpClient(IPEndPoint)使用本机IPEndPoint创建TcpClient的实例对象。上一篇介绍过了,IPEndPoint将网络端点表示为IP地址和端口号,在这里它用于指定在建立远程主机连接时所使用的本地网络接口(IP地址)和端口号,这个构造方法为使用本机IPAddress 和Port提供了选择余地。下面的语句示例了如何使用本地终结点创建TcpClient类的实例:
IPHostEntry ipInfo=Dns.GetHostByName("https://www.doczj.com/doc/bd1546678.html,");//主机信息
IPAddressList[]ipList=ipInfo.AddressList;//IP地址数组
IPAddress ip=ipList[0];//多IP地址时一般用第一个
IPEndPoint ipEP=new IPEndPoint(ip,4088);//得到网络终结点
try{
TcpClient tcpClientA=new TcpClient(ipLocalEndPoint);
}
catch(Exception e){
Console.WriteLine(e.ToString());
}
到这里,你可能会感到困惑,客户端要和服务端创建连接,所指定的IP地址及通信端口号应该是远程服务器的呀!事实上的确如此,使用以上两种构造函数,你所实现的只是TcpClient实例对象与IP地址和Port端口的绑定,要完成连接,你还需要显式指定与远程主机的连接,这可以通过TcpClient类的Connect方法来实现,Connet方法使用指定的主机名和端口号将客户端连接到远程主机:
1)、public void Connect(IPEndPoint);使用指定的远程网络终结点将客户端连接到远程TCP主机。
public void Connect(IPAddress,int);使用指定的IP地址和端口号将客户端连接到TCP主机。
public void Connect(string,int);将客户端连接到指定主机上的指定端口。
需要指出的是,Connect方法的所有重载形式中的参数IPEndPoint网络终
结点、IPAddress以及表现为string的Dns主机名和int指出的Port端口均指的是远程服务器。
以下示例语句使用主机默认IP和Port端口号0与远程主机建立连接:
TcpClient tcpClient=new TcpClient();//创建TcpClient对象实例
try{
tcpClient.Connect("https://www.doczj.com/doc/bd1546678.html,",11002);//建立连接
}
catch(Exception e){
Console.WriteLine(e.ToString());
}
3、public TcpClient(string,int);初始化TcpClient类的新实例并连接到指定主机上的指定端口。与前两个构造函数不一样,这个构造函数将自动建立连接,你不再需要额外调用Connect 方法,其中string类型的参数表示远程主机的Dns名,如:https://www.doczj.com/doc/bd1546678.html,。
以下示例语句调用这一方法实现与指定主机名和端口号的主机相连:
try{
TcpClient tcpClientB=new TcpClient("https://www.doczj.com/doc/bd1546678.html,",4088);
}
catch(Exception e){
Console.WriteLine(e.ToString());
}
前面我们说,TcpClient类创建在Socket之上,在Tcp服务方面提供了更高层次的抽象,体现在网络数据的发送和接受方面,是TcpClient使用标准的Stream流处理技术,使得它读写数据更加方便直观,同时,.Net框架负责提供更丰富的结构来处理流,贯穿于整个.Net框架中的流具有更广泛的兼容性,构建在更一般化的流操作上的通用方法使我们不再需要困惑于文件的实际内容(HTML、XML或其他任何内容),应用程序都将使用一致的方法(Stream.Write、Stream.Read)发送和接收数据。另外,流在数据从Internet下载的过程中提供对数据的即时访问,可以在部分数据到达时立即开始处理,而不需要等待应用程序下载完整个数据集。.Net 中通过NetworkStream类实现了这些处理技术。
NetworkStream类包含在.Net框架的https://www.doczj.com/doc/bd1546678.html,.Sockets命名空间里,该类专门提供用于网
络访问的基础数据流。NetworkStream实现通过网络套接字发送和接收数据的标准.Net框架流机制。NetworkStream支持对网络数据流的同步和异步访问。NetworkStream从Stream继承,后者提供了一组丰富的用于方便网络通讯的方法和属性。
同其它继承自抽象基类Stream的所有流一样,NetworkStream网络流也可以被视为一个数据通道,架设在数据来源端(客户Client)和接收端(服务Server)之间,而后的数据读取及写入均针对这个通道来进行。
.Net框架中,NetworkStream流支持两方面的操作:
1、写入流。写入是从数据结构到流的数据传输。
2、读取流。读取是从流到数据结构(如字节数组)的数据传输。
与普通流Stream不同的是,网络流没有当前位置的统一概念,因此不支持查找和对数据流的随机访问。相应属性CanSeek始终返回false,而Seek和Position方法也将引发NotSupportedException。
基于Socket上的应用协议方面,你可以通过以下两种方式获取NetworkStream网络数据流:1、使用NetworkStream构造函数:public NetworkStream(Socket,FileAccess,bool);(有重载
方法),它用指定的访问权限和指定的Socket所属权为指定的Socket创建NetworkStream类的新实例,使用前你需要创建Socket对象实例,并通过Socket.Connect方法建立与远程服务端的连接,而后才可以使用该方法得到网络传输流。示例如下:
Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//创建客户端Socket对象实例
try{
s.Connect("https://www.doczj.com/doc/bd1546678.html,",4088);//建立与远程主机的连接
}
catch(Exception e){
MessageBox.show("连接错误:"+e.Message);
}
try{
NetworkStream stream=new NetworkStream(s,FileAccess.ReadWrite,false);//取得网络传输流}
2、通过TcpClient.GetStream方法:public NetworkStream etStream();它返回用于发送和接收数据的基础网络流NetworkStream。GetStream通过将基础Socket用作它的构造函数参数来创建NetworkStream类的实例。使用前你需要先创TcpClient对象实例并建立与远程主机的连接,示例如下:
TcpClient tcpClient=new TcpClient();//创建TcpClient对象实例
Try{
tcpClient.Connect("https://www.doczj.com/doc/bd1546678.html,",4088);//尝试与远程主机相连
}
catch(Exception e){
MessageBox.Show("连接错误:"+e.Message);
}
try{
NetworkStream stream=tcpClient.GetStream();//获取网络传输流
}
catch(Exception e)
{
MessageBox.Show("TcpClient错误:"+e.Message);
}
通过以上方法得到NetworkStream网络流之后,你就可以使用标准流读写方法Write和Read 来发送和接受数据了。
以上是.Net下使用TcpClient类实现客户端编程的技术资料,为了向客户端提供这些服务,我们还需要编制相应的服务端程序,前一篇《Visual C#.Net网络程序开发-Socket篇》上曾经提到,Socket作为其他网络协议的基础,既可以面向客户端开发,也可以面向服务端开发,在传输层面上使用较多,而在应用协议层面上,客户端我们采用构建于Socket类之上的TcpClient取代Socket;相应地,构建于Socket之上的TcpListener提供了更高理念级别的TCP服务,使得我们能更方便地编写服务端应用程序。正是因为这样的原因,像FTP和HTTP这样的应用层协议都是在TcpListener类的基础上建立的。
.Net中的TCPListener用于监视TCP端口上的传入请求,通过绑定本机IP地址和相应端口(这两者应与客户端的请求一致)创建TcpListener对象实例,并由Start方法启动侦听;当TcpListener侦听到用户端的连接后,视客户端的不同请求方式,通过AcceptTcpClient方法接受传入的连接请求并创建TcpClient以处理请求,或者通过AcceptSocket方法接受传入的连接请求并创建Socket以处理请求。最后,你需要使用Stop关闭用于侦听传入连接的Socket,你必须也关闭从AcceptSocket或AcceptTcpClient返回的任何实例。这个过程详细解说如下:
首先,创建TcpListener对象实例,这通过TcpListener类的构造方法来实现:
public TcpListener(port);//指定本机端口
public TcpListener(IPEndPoint)//指定本机终结点
public TcpListener(IPAddress,port)//指定本机IP地址及端口
以上方法中的参数在前面多次提到,这里不再细述,唯一需要提醒的是,这些参数均针对服务端主机。下面的示例演示创建TcpListener类的实例:
IPHostEntry ipInfo=Dns.Resolve("127.0.0.1");//主机信息
IPAddressList[]ipList=ipInfo.IPAddressList;//IP数组
IPAddress ip=ipList[0];//IP
try{
TcpListener tcpListener=new TcpListener(ipAddress,4088);//创建TcpListener对象实例以侦听用户端连接
}
catch(Exception e){
MessageBox.Show("TcpListener错误:"+e.Message);
}
随后,你需要调用Start方法启动侦听:
public void Start();
其次,当侦听到有用户端连接时,需要接受挂起的连接请求,这通过调用以下两方法之一来完成连接:
public Socket AcceptSocket();
public TcpClient AcceptTcpClient();
前一个方法返回代表客户端的Socket对象,随后可以通过Socket类的Send和Receive方法与远程计算机通讯;后一个方法返回代表客户端的TcpClient对象,随后使用上面介绍的TcpClient.GetStream方法获取TcpClient的基础网络流NetworkStream,并使用流读写Read/Write方法与远程计算机通讯。
最后,请记住关闭侦听器:public void Stop();
同时关闭其他连接实例:public void Close();
下面的示例完整体现了上面的过程:
bool done=false;
TcpListener listener=new TcpListener(13);//创建TcpListener对象实例(13号端口提供时间服务)
listener.Start();//启动侦听
while(!done){//进入无限循环以侦听用户连接
TcpClient client=listener.AcceptTcpClient();//侦听到连接后创建客户端连接TcpClient NetworkStream ns=client.GetStream();//得到网络传输流
byte[]byteTime=Encoding.ASCII.GetBytes(DateTime.Now.ToString());//预发送的内容(此为服务端时间)转换为字节数组以便写入流
ns.Write(byteTime,0,byteTime.Length);//写入流
ns.Close();//关闭流
client.Close();//关闭客户端连接
}
catch(Exception e){
MessageBox.Show("流错误:"+e.Message)
}
综合运用上面的知识,下面的实例实现了简单的网络通讯-双机互连,针对客户端和服务端分别编制了应用程序。客户端创建到服务端的连接,向远程主机发送连接请求连接信号,并发送交谈内容;远程主机端接收来自客户的连接,向客户端发回确认连接的信号,同时接收并显示客户端的交谈内容。在这个基础上,发挥你的创造力,你完全可以开发出一个基于程序语言(C#)级的聊天室!
客户端主要源代码:
public void SendMeg()//发送信息
{
try
{
int port=Int32.Parse(textBox3.Text.ToString());//远程主机端口
try
{
tcpClient=new TcpClient(textBox1.Text,port);//创建TcpClient对象实例}
catch(Exception le)
{
MessageBox.Show("TcpClient Error:"+le.Message);
}
string strDateLine=DateTime.Now.ToShortDateString()+""+DateTime.Now.ToLongTimeString() ;//得到发送时客户端时间
netStream=tcpClient.GetStream();//得到网络流
sw=new StreamWriter(netStream);//创建TextWriter,向流中写字符
string words=textBox4.Text;//待发送的话
string content=strDateLine+words;//待发送内容
sw.Write(content);//写入流
sw.Close();//关闭流写入器
netStream.Close();//关闭网络流
tcpClient.Close();//关闭客户端连接
}
catch(Exception ex)
{
MessageBox.Show("Sending Message Failed!"+ex.Message);
textBox4.Text="";//清空
}
服务器端主要源代码:
public void StartListen()//侦听特定端口的用户请求
{
//ReceiveMeg();
isLinked=false;//连接标志
try
{
int port=Int32.Parse(textBox1.Text.ToString());//本地待侦听端口serverListener=new TcpListener(port);//创建TcpListener对象实例serverListener.Start();//启动侦听
}
catch(Exception ex)
{
MessageBox.Show("Can‘t Start Server"+ex.Message);
return;
}
isLinked=true;
while(true)//进入无限循环等待用户端连接
{
try
{
tcpClient=serverListener.AcceptTcpClient();//创建客户端连接对象netStream=tcpClient.GetStream();//得到网络流
sr=new StreamReader(netStream);//流读写器
}
catch(Exception re)
{
MessageBox.Show(re.Message);
}
string buffer="";
string received="";
received+=sr.ReadLine();//读流中一行
while(received.Length!=0)
{
buffer+=received;
buffer+="\r\n";
//received="";
received=sr.ReadLine();
listBox1.Items.Add(buffer);//显示
//关闭
sr.Close();
netStream.Close();
tcpClient.Close();
}
}
MJPEG概述
MJPEG全名为"Motion Joint Photographic Experts Group",是一种视频编码格式,中文名称翻译为“技术即运动静止图像(或逐帧)压缩技术”。MJPEG广泛应用于非线性编辑领域可精确到帧编辑和多层图像处理,把运动的视频序列作为连续的静止图像来处理,这种压缩方式单独完整地压缩每一帧,在编辑过程中可随机存储每一帧,可进行精确到帧的编辑,此外M-JPEG的压缩和解压缩是对称的,可由相同的硬件和软件实现。但M-JPEG只对帧内的空间冗余进行压缩。不对帧间的时间冗余进行压缩,故压缩效率不高。采用M-JPEG数字压缩格式,当压缩比7:1时,可提供相当于Betecam SP质量图像的节目。
Motion JPEG技术常用于闭合电路的电视摄像机的模拟视频信号“翻译”成视频流,并存储在硬盘上。典型的应用如数字视频记录器等。MJPEG不像MPEG,不使用帧间编码,因此用一个非线性编辑器就很容易编辑。MJPEG的压缩算法与MPEG一脉相承,功能很强大,能发送高质图片,生成完全动画视频等。但相应地,MJPEG对带宽的要求也很高,相当于T-1,MJPEG信息是存储在数字媒体中的庞然大物,需要大量的存储空间以满足如今多数用户的需求。因此从另一个角度说,在某些条件下,MJPEG也许是效率最低的编码/解码器之一。
MJPEG是24-bit的"true-color"影像标准,MJPEG的工作是将RGB格式的影像转换成YCrCB格式,目的是为了减少档案大小,一般约可减少1/3~1/2左右。
MJPEG的特点
M-JPEG是一种基于静态图像压缩技术JPEG发展起来的动态图像压缩技术,可以生成序列化的运动图像。其主要特点是基本不考虑视频流中不同帧之间的变化,只单独对某一帧进行压缩,其压缩倍数为20~80倍,适合静态画面的压缩,分辨率可从352×288到704×576。以往的JPEG压缩技术是直接处理整个画面,所以要等到整个压缩档案传输完成才开始进行解压缩成影像画面,而这样的方式造成传输一个高解析画面时须耗时数十秒甚至数分钟。而新一代的M-JPEG是采取渐层式技术,先传输低解析的图档,然后再补送细部之资料,使画面品质改善。M-JPEG压缩技术可以获取清晰度很高的视频图像,而且可以灵活设置每路的视频清晰度和压缩帧数。因期压缩后之格式可读单一画面,所以可以任意剪接。M-JPEG因采用帧内压缩方式也适于视频编辑。
M-JPEG的主要缺点是压缩效率低,MJPEG算法是根据每一帧图像的内容进行压缩,而不是根据相邻帧图像之间的差异来进行压缩,因此造成了大量冗余信息被重复存储,存储占用的空间大到每帧8~15K字节,最好也只能做到每帧3K字节,但如果因此而采用高压缩比则视频质量会严重降低。
IP地址的分类——a,b,c类是如何划分的
现在的IP网络使用32位地址,以点分十进制表示,如172.16.0.0。地址格式为:IP地址=网络地址+主机地址或IP地址=主机地址+子网地址+主机地址。
IP地址类型
最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址。1.A类IP地址
一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”,地址范围从1.0.0.0到126.0.0.0。可用的A类网络有126个,每个网络能容纳1亿多个主机。
2.B类IP地址
一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机。
3.C类IP地址
一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类网络可达209万余个,每个网络能容纳254个主机。
4.D类地址用于多点广播(Multicast)。
D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。
5.E类IP地址
以“llll0”开始,为将来使用保留。
全零(“0.0.0.0”)地址对应于当前主机。全“1”的IP地址(“255.255.255.255”)是当前子网的广播地址。
在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下:
A类地址:10.0.0.0~10.255.255.255
B类地址:172.16.0.0~172.31.255.255
C类地址:192.168.0.0~192.168.255.255
A类地址的第一组数字为1~126。注意,数字0和127不作为A类地址,数字127保留给内部回送函数,而数字0则表示该地址是本地宿主机,不能传送。
B类地址的第一组数字为128~191。
C类地址的第一组数字为192~223。
1.A类地址
A类地址的表示范围为:0.0.0.0~126.255.255.255,默认网络掩码为:255.0.0.0;A类地址分配给规模特别大的网络使用。A类网络用第一组数字表示网络本身的地址,后面三组数字作为连接于网络上的主机的地址。分配给具有大量主机(直接个人用户)而局域网络个数较少的大型网络。例如IBM公司的网络。
2.B类地址
B类地址的表示范围为:128.0.0.0~191.255.255.255,默认网络掩码为:255.255.0.0;B类地址分配给一般的中型网络。B类网络用第一、二组数字表示网络的地址,后面两组数字代表网络上的主机地址。
3.C类地址
C类地址的表示范围为:192.0.0.0~223.255.255.255,默认网络掩码为:255.255.255.0;C 类地址分配给小型网络,如一般的局域网和校园网,它可连接的主机数量是最少的,采用把所属的用户分为若干的网段进行管理。C类网络用前三组数字表示网络的地址,最后一组数字作为网络上的主机地址。
实际上,还存在着D类地址和E类地址。但这两类地址用途比较特殊,在这里只是简单介绍一下:D类地址称为广播地址,供特殊协议向选定的节点发送信息时用。E类地址保留给将来使用。
C#引用类型作为函数参数时
在探讨本文的主题之前,先来介绍下C#中的值类型和引用类型
众所周知C#中有值类型和引用类型,值类型有基础数据类型(诸如int,double,bool等)、结构体、枚举,引用类型有接口、类、委托。
值类型全部在操作系统的栈空间中申请,而引用类型则在操作系统的堆空间中建立对象,然后在栈空间中申请一个指针指向这个对象的地址。
因此C#的引用类型其实就如同C++的指针类型。
下面我再来看看函数传参的问题。
早在C时代就有函数参数传值和传地址的概念,请记住在C#中函数参数默认都是传值。对于值类型,函数是将实参变量的值在栈空间复制一份然后传给形参变量。所以在函数中对形参变量的更改不会对实参变量造成任何影响,因为函数的形参只是实参的副本。
而对于引用类型,由于实参变量和形参变量都是引用类型,它们都指向内存堆中的某一对象的地址,函数是将实参变量指向的地址值复制了一份给形参变量,由于形参变量和实参变量指向堆中同一地址,所以在函数中使用形参变量对所指向对象所做的更改也会在实参变量中反映出来。
所以不管是值类型还是引用类型在作为参数传进函数时,其实都是传的值,只不过引用类型传的是对象在堆中的的地址罢了。
而且从上面的定义可以看出C#中引用类型的变量用C++来说就相当于是该引用类型的指针,比如有类(引用类型)RefClass:
RefClass rc就相当于是C++上的RefClass*rc
在C#中使用rc.IntValue++;时,相当于C++的rc->IntValue++;
因为引用类型在函数传参时是传地址的,所以我脑袋里就形成了一种惯性思维,认为只要传进函数的是引用类型,那么在函数中做的任何更改都会反映到实参上。但是我发现并不完全是这样,下面给出个例子(注释内容为对应等效的C++代码):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RefWarn
{
class RefClass
{
public int IntValue
{
get;
set;
}
}
class Program
{
static void AddValue(RefClass prc)//RefClass*prc,prc和传进来的rc指向同一个RefClass 对象的地址
{
prc.IntValue++;//prc->IntValue++;
}
static void AddValue(ref RefClass prc)//RefClass**prc,prc指向传进来的rc的地址
{
prc.IntValue++;//(*prc)->IntValue++;
}
static void ChangeRef(RefClass prc)//RefClass*prc,prc和传进来的rc指向同一个RefClass 对象的地址
{
prc=new RefClass(){IntValue=1000};
//prc=new RefClass(){IntValue=1000};请注意new关键字在C++中创建的是指向对象的指针不是对象
}
static void ChangeRef(ref RefClass prc)//RefClass**prc,prc指向传进来的rc的地址
{
prc=new RefClass(){IntValue=1000};
//*prc=new RefClass(){IntValue=1000};请注意new关键字在C++中创建的是指向对象的指针不是对象
}
static void Main(string[]args)
{
RefClass rc=new RefClass(){IntValue=1};//RefClass*rc=new RefClass(){IntValue= 1};请注意new关键字在C++中创建的是指向对象的指针不是对象
AddValue(rc);//rc,传递指向RefClass对象的指针
Console.WriteLine("调用AddValue(rc)后IntValue为:"+rc.IntValue);
rc.IntValue=1;
AddValue(ref rc);//&rc,传递指向RefClass对象指针的指针
Console.WriteLine("调用AddValue(ref rc)后IntValue为:"+rc.IntValue);
rc.IntValue=1;
ChangeRef(rc);//rc,传递指向RefClass对象的指针
Console.WriteLine("调用ChangeRef(rc)后IntValue为:"+rc.IntValue);
简单的四则运算计算器程序
注:1、报告内的项目或内容设置,可根据实际情况加以调整和补充。 2、教师批改学生实验报告时间应在学生提交实验报告时间后10日内。
附件:程序源代码 // sizheyunsuan.cpp : Defines the entry point for the console application. #include
吉首大学课程设计 课程设计名称:简单的计算器 专业班级: 10 网络工程 学生姓名:胡申城唐茂林赖佳 学号:20104045049 20104045006 201040450 指导教师:鲁荣波老师 课程序设计时间:2011.12.18-2011.12.25
目录 一内容 (1) 二.设计要求 (2) 三.计算器的算法 (2) 四..流程图 (5) 五.源程序 (7) 六.执行结果 (8) 七.课程设计总结 (3) 八.参考书目 (3)
一、内容 编写一个功能基本完善的计算器程序,支持四则运算、乘方开方、指数对数等运算,支持带括号的表达式。 二、设计要求 1) 程序采用字符界面 2) 显示功能菜单,用户指定键即可进入相应功能 3) 每次计算完成后输出一个提示符,要计算的数字或表达式由用户在提示后面输入 4) 用户输入完按回车立即显示计算结果 三、计算器的算法 我们现在用的计算器多是一个数一个运算符的输入输出的。一个比较具有现实意义的计算器,应该具有如下功能: 1.能够实现整数的四则运算:如:1+6-9*4+4/3 2.能够实现小数的四则运算:如:0.2+7-4 这样的计算器基本可以使用在我们生活中。如果我们按照栈的使用来描述一下表达式的进栈出栈问题: 表达式例如:1+9*4/4-5。 假设有栈D和数组S;S中放运算数,D栈放入运算符,优先级高的现运算,四则运算中,*和/优先级最高。 第一步:扫描到‘1’,是运算数,放入S; 第二步:扫描到‘+‘进入D
第三步:扫描到9;进S 第四步:扫描‘*’,因为乘比加优先级高,所以9出栈实现:9*4,把结果入S, 第五步:扫描到/,同样计算S的栈顶元素出栈,实现36/4,把结果如S 第六步:扫描到—,进入D栈; 第七步:扫描到‘5’,进S 第八步:扫描到‘\0’,为结束符;把S栈顶元素栈S1,D顶元素出栈,然后S栈顶元素在出栈,实现减操作,接着S栈顶元素出栈,实现加操作。 具体进出栈如图: Next 为下一个字符或数字,run表示运行,空为不操作S[] D S[] D S[] D S[] D S[] D S[] top t top 9 top 36 top 4 - -1 top base 1 + 1 + 1 + 1 + 1 + 0 top top next + 9 * 4 / 4 - 5 = run 9*4 36/4 4-5 -1+1 表1-1 运算数运算符进栈、出栈
基于单片机实现的四则运算计算器 姓名 学号: 班级: 专业名称:测控技术与仪器 指导教师: 东北大学 2016年1月
课程设计(论文)任务书课程设计(论文)题目:基于单片机实现的四则运算计算器 基本内容和设计要求: 1、主机的矩阵键盘输入数和运算符号,从机显示运算结果。 2、主从单片机串行通信系统,并在LCD上显示运算内容及结果。 3、计算结果超过十位数字则显示错误。 4、运算除法时,结果只取整数,不是四舍五入。 5、有清零功能。
目录 课程设计(论文)任务书................................................................................................ i i 摘要 (1) 第1章绪论 (2) 1.1计算器简介 (2) 1.2设计主要内容 (2) 第2章系统硬件设计 (4) 2.1硬件组成 (4) 2.2输入模块 (4) 2.3输出模块 (5) 2.4运算模块 (5) 第3章系统软件设计 (7) 3.1 主程序 (7) 3.1.1主程序框图及初始化 (7) 3.1.2LCD程序框图及初始化 (8) 3.1.3键盘程序框图及初始化 (9) 3.1.4运算程序框图 (10) 第4章调试测试与实验分析 (11) 4.1 计算器调试 (11) 参考文献 (12) 心得体会 (13) 附录硬件原理图及PCB图 (14) 附录程序清单 (15) 附录实物照片 (28)
摘要 单片机的出现是计算机制造技术高速发展的产物,它是嵌入式控制系统的核心,如今,它已广泛的应用到我们生活的各个领域,电子、科技、通信、汽车、工业等。本设计是基于89C52RC单片机来进行的四则运算计算器系统设计,可以完成计算器的键盘输入,进行加、减、乘、除的基本四则运算,并在LCD1602液晶显示屏上显示相应的结果。本电路采用89C52RC单片机为主要控制电路,利用4*4矩阵键盘作为计算器以及运算符的输入。显示采用字符LCD静态显示。软件用C语言编程,并用开发板进行演示。 关键词:计算器,89C52RC单片机,LCD,矩阵键盘
HTML5+JavaScript: