您的位置:首页 > 理论基础 > 计算机网络

网络编程

2014-04-10 21:14 134 查看
只有需要的时候去学习才是进步最快的时候,最近要去面试需要了解一下网络编程的知识,研究的不深,权当扫盲了.
主要讲述了基于套接字(Socket)进行网络编程的基本概念,其中包括TCP协议、套接字、聊天程序的三种开发模式,以及两个基本操作:侦听端口、连接远程服务端;
面向连接的传输协议:TCP
关于TCP协议我觉得作为开发人员,只需要掌握与程序相关的概念就可以了,不需要做太艰深的研究。
首先知道TCP是面向连接
意思是说两个远程主机(或者叫进程,因为实际上远程通信是进程之间的通信,而进程则是运行中的程序),必须首先进行一个握手过程,确认连接成功,之后才能传输实际的数据。
比如说进程A想将字符串“I'am
hungry”发给进程B,它首先要建立连接。在这一过程中,它首先需要知道进程B的位置(主机地址和端口号)。随后发送一个不包含实际数据的请求报文,我们可以将这个报文称之为“hello”。如果进程B接收到了这个“hello”,就向进程A回复一个“hello”,进程A随后才发送实际的数据“I'am hungry”。
其次就是它是全双工
意思是说如果两个主机上的进程(比如进程A、进程B),一旦建立好连接,那么数据就既可以由A流向B,也可以由B流向A。除此以外,它还是点对点的,意思是说一个TCP连接总是两者之间的,在发送中,通过一个连接将数据发给多个接收方是不可能的。
最后TCP可靠的数据传输
意思是连接建立后,数据的发送一定能够到达,并且是有序的,就是说发的时候你发了ABC,那么收的一方收到的也一定是ABC,而不会是BCA或者别的什么。
套接字
编程中与TCP相关的最重要的一个概念就是套接字。我们应该知道网络七层协议,如果我们将上面的应用程、表示层、会话层笼统地算作一层,那么我们编写的网络应用程序就位于应用层,而大家知道TCP是属于传输层的协议,那么我们在应用层如何使用传输层的服务呢(消息发送或者文件上传下载)?大家知道在应用程序中我们用接口来分离实现,在应用层和传输层之间,则是使用套接字来进行分离。它就像是传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,我们是不知道也不需要知道的,我们也不会关心它如何传输,这属于网络其它层次的工作。
举个例子,如果你想写封邮件发给远方的朋友,那么你如何写信、将信打包,属于应用层,信怎么写,怎么打包完全由我们做主;而当我们将信投入邮筒时,邮筒的那个口就是套接字,在进入套接字之后,就是传输层、网络层等(邮局、公路交管或者航线等)其它层次的工作了。我们从来不会去关心信是如何从廊坊发往北京的,我们只知道写好了投入邮筒就OK了。
在.NET中,尽管我们可以直接对套接字编程,但是.NET提供了两个类将对套接字的编程进行了一个封装,使我们的使用能够更加方便,这两个类是TcpClient和TcpListener.
TcpListener用于接受连接请求,而TcpClient则用于接收和发送流数据。TcpListener持续地保持对端口的侦听,一旦收到一个连接请求后,就可以获得一个TcpClient对象,而对于数据的发送和接收都有TcpClient去完成。此时,TcpListener并没有停止工作,它始终持续地保持对端口的侦听状态。
基本操作
1.服务端对端口进行侦听
接下来开始编写一些实际的代码,第一步就是开启对本地机器上某一端口的侦听。首先创建一个控制台应用程序,将项目名称命名为ServerConsole,它代表我们的服务端。如果想要与外界进行通信,第一件要做的事情就是开启对端口的侦听,这就像为计算机打开了一个“门”,所有向这个“门”发送的请求(“敲门”)都会被系统接收到。在C#中可以通过下面几个步骤完成,首先使用本机Ip地址和端口号创建一个System.Net.Sockets.TcpListener类型的实例,然后在该实例上调用Start()方法,从而开启对指定端口的侦听。

usingSystem.Net;  //需要引入的命名空间
usingSystem.Net.Sockets;//需要引入的命名空间
 
namespaceServerConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Server is running ... ");
            IPAddress ip = new IPAddress(newbyte[] { 127, 0, 0, 1 });
  // 获得IPAddress对象的另外几种常用方法:
  //IPAddress ip = IPAddress.Parse("127.0.0.1");
  //IPAddress ip = Dns.GetHostEntry("localhost").AddressList[0]; 
 
            TcpListener listener = newTcpListener(ip, 8500);
            listener.Start();           //开始侦听
            Console.WriteLine("StartListening ...");
            Console.WriteLine("输入Q键退出。");
            ConsoleKey key;
            do
            {
                key =Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


上面的代码中,我们开启了对8500端口的侦听。在运行了上面的程序之后,然后打开“命令提示符”,输入“netstat-a”,可以看到计算机器中所有打开的端口的状态。可以从中找到8500端口,看到它的状态是LISTENING,这说明它已经开始了侦听:



在打开了对端口的侦听以后,服务端必须通过某种方式进行阻塞(比如Console.ReadKey()),使得程序不能够因为运行结束而退出。否则就无法使用“netstat-na”看到端口的连接状态,因为程序已经退出,连接会自然中断,再运行“netstat-a”当然就不会显示端口了。所以程序最后按“Q”退出那段代码是必要的。
2.客户端与服务端连接
客户端与服务端连接
当服务器开始对端口侦听之后,便可以创建客户端与它建立连接。这一步是通过在客户端创建一个TcpClient的类型实例完成。每创建一个新的TcpClient便相当于创建了一个新的套接字Socket去与服务端通信,.Net会自动为这个套接字分配一个端口号,上面说过,TcpClient类不过是对Socket进行了一个包装。创建TcpClient类型实例时,可以在构造函数中指定远程服务器的地址和端口号。这样在创建的同时,就会向远程服务端发送一个连接请求(“握手”),一旦成功,则两者间的连接就建立起来了。也可以使用重载的无参数构造函数创建对象,然后再调用Connect()方法,在Connect()方法中传入远程服务器地址和端口号,来与服务器建立连接。
这里需要注意的是,不管是使用有参数的构造函数与服务器连接,或者是通过Connect()方法与服务器建立连接,都是同步方法(或者说是阻塞的,英文叫block)。它的意思是说,客户端在与服务端连接成功、从而方法返回,或者是服务端不存、从而抛出异常之前,是无法继续进行后继操作的。这里还有一个名为BeginConnect()的方法,用于实施异步的连接,这样程序不会被阻塞,可以立即执行后面的操作,这是因为可能由于网络拥塞等问题,连接需要较长时间才能完成。网络编程中有非常多的异步操作,凡事都是由简入难,关于异步操作,我们后面再讨论,现在只看同步操作。
创建一个新的控制台应用程序项目,命名为ClientConsole,它是我们的客户端,然后添加下面的代码,创建与服务器的连接:

usingSystem.Net;
usingSystem.Net.Sockets;

namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
 
            Console.WriteLine("ClientRunning ...");
            TcpClient client = new TcpClient();
            try
            {
               client.Connect("localhost",8500);      // 与服务器连接
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }
            // 打印连接到的服务端信息
           Console.WriteLine("Server Connected!{0}--> {1}",
                client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
 
            // 按Q退出
               Console.WriteLine("输入Q键退出。");
            ConsoleKey key;
            do
            {
                key =Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


上面带代码中,我们通过调用Connect()方法来与服务端连接。随后,我们打印了这个连接消息:本机的Ip地址和端口号,以及连接到的远程Ip地址和端口号。TcpClient的Client属性返回了一个Socket对象,它的LocalEndPoint和RemoteEndPoint属性分别包含了本地和远程的地址信息。先运行服务端,再运行这段代码。可以看到两边的输出情况如下:



我们看到客户端使用的端口号为4761,上面已经说过,这个端口号是由.NET随机选取的,并不需要我们来设置,并且每次运行时,这个端口号都不同。再次打开“命令提示符”,输入“netstat-a”,可以看到下面的输出:




从这里我们可以得出几个重要信息:1、端口8500和端口4184建立了连接,这个4184端口便是客户端用来与服务端进行通信的端口;2、8500端口在与客户端建立起一个连接后,仍然继续保持在监听状态。这也就是说一个端口可以与多个远程端口建立通信,这是显然的,大家众所周之的HTTP使用的默认端口为80,但是一个Web服务器要通过这个端口与多少个浏览器通信啊。
3.服务端获取客户端连接
获取客户端连接

上 面服务端、客户端的代码已经建立起了连接,这通过使用“netstat -a”命令,从端口的状态可以看出来,但这是操作系统告诉我们的。那么我们现在需要知道的就是:服务端的程序如何知道已经与一个客户端建立起了连接?
服务器端开始侦听以后,可以在TcpListener实例上调用AcceptTcpClient()来获取与一个客户端的连接,它返回一个TcpClient类型实例。此时它所包装的是由服务端去往客户端的Socket,而我们在客户端创建的TcpClient则是由客户端去往服务端的。这个方法是一个同步方法(或者叫阻断方法,blockmethod),意思就是说,当程序调用它以后,它会一直等待某个客户端连接,然后才会返回,否则就会一直等下去。这样的话,在调用它以后,除非得到一个客户端连接,不然不会执行接下来的代码。一个很好的类比就是Console.ReadLine()方法,它读取输入在控制台中的一行字符串,如果有输入,就继续执行下面代码;如果没有输入,就会一直等待下去。

usingSystem.Net;  //需要引入的命名空间
usingSystem.Net.Sockets;//需要引入的命名空间
 
namespaceServerConsole
{
    class Program
    {
        static void Main(string[] args)
        {
           Console.WriteLine("Server is running ... ");
            IPAddress ip = new IPAddress(newbyte[] { 127, 0, 0, 1 });
            TcpListener listener = newTcpListener(ip, 8500);
 
           listener.Start();           //开始侦听
            Console.WriteLine("StartListening ...");
 
            // 获取一个连接,中断方法
            TcpClient remoteClient =listener.AcceptTcpClient();
 
            // 打印连接到的客户端信息
           Console.WriteLine("Client Connected!{0}<-- {1}",
              remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
 
            // 按Q退出
           Console.WriteLine("输入Q键退出。");
            ConsoleKey key;
            do
            {
                key =Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}
运行这段代码,会发现服务端运行到listener.AcceptTcpClient()时便停止了,并不会执行下面的Console.WriteLine()方法。为了让它继续执行下去,必须有一个客户端连接到它,所以我们现在运行客户端,与它进行连接。简单起见,我们只在客户端开启一个端口与之连接:

using System.Text;
usingSystem.Net.Sockets;
 
 
namespaceClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
 
            Console.WriteLine("ClientRunning ...");
            TcpClient client = new TcpClient();
            try
            {
               client.Connect("localhost",8500);      // 与服务器连接
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }
            // 打印连接到的服务端信息
           Console.WriteLine("Server Connected!{0}--> {1}",
                client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
 
            // 按Q退出
           Console.WriteLine("输入Q键退出。");
            ConsoleKey key;
            do
            {
                key =Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


此时,服务端、客户端的输出分别为:



文章很简单,权当扫盲,按需学习才能更快的了解你想了解的东西.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: