基本操作

基本操作

 

服务端对端口进行监听

 

首先需要进行的,就是开启本地计算机上某一端口的监听.创建一个控制台应用程序,将项目命名为ServerConsole,表示服务端.如果想要与外界进行通信,第一件事就是开启对端口的监听,这就像是计算机打开了一个”门”,所有向这个”门”发送的请求(“敲门”)都会被系统接收.C#中可以通过下面几个步骤来完成,首先使用本机的IP地址和端口号创建一个System.Net.Sockers.TcpListener类型的实例,然后在该实例上调用Start()方法,从而开启对指定端口的监听:

http://jingyan.baidu.com/article/d3b74d64bea25e1f76e6095a.html

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//下面是两个必要的命名空间
using System.Net;
using System.Net.Sockets;
namespace ServerConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Server is running...");
            IPAddress ip = new IPAddress(new byte[] { 192,168,3,19});
            /*
            这里可能出现一点小问题,可能程序会报错,这个时候你可以使用cmd,用netstat -a命令
            查看一下那些端口未被占用.
            */
            TcpListener listener = new TcpListener(ip, 3419);
 
            //开始监听
            listener.Start();
            Console.WriteLine("Start Listening...");
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


上面的代码中,在创建TcpListener的实例时开启了对3419端口的监听,192.168.3.19是本地计算机的IP地址.运行上面的程序,打开cmd命令提示符,输入”netstat -a”,可以看到计算机中所有活动链接的状态.如果能看到3419端口应该是状态显示LISTENING表示开始了监听.

 

提示:防火墙可能会对端口监听进行阻挡,要在防火墙中添加对端口3419 的例外.

 

在打开了对端口的监听以后,服务端必须通过某种方式进行阻塞,比如Console.ReadKey(),使程序不会因为代码执行到末尾自动退出.否则就无法使用”netstat -a”看到端口的连接状态了,一句话,服务器的程序不能关闭.下面的每段代码都会有上面最后那个按”Q”退出.

 

客户端与服务端连接

 

1.单一客户端与服务端连接

 

服务器开始对端口监听之后,便可以创建客户端与它建立连接,这一步是通过在客户端创建一个TcpClient的类时实力完成.每创建一个新的TcpClient便相当于创建了一个新的套接字Socket与服务端通信,.NET会自动为这个套接字分配一个端口号.TcpClient类不过是对Socket进行了一个包装.在创建TcpClient类型实例时,可以在构造函数中指定远程服务器的地址和端口号.这样在创建的同时,就会向远程服务端发送一个连接请求(”握手”),一旦成功,则两者间的连接就建立起来了.也可以使用重载的无参数构造函数来创建对象,然后在调用Connect()方法,Connect()方法中传入远程服务器地址和端口号来与服务器建立连接.

 

这里需要注意的是,不管是使用有参数的构造函数还是通过Connect()方法与服务器建立连接,都是同步方法(或者说是阻塞的,block).意思是说,客户端在与服务端连接成功,方法返回,或者服务端不存在,抛出异常之前,是无法继续进行后续操作的.这里还有一个名为BeginConnect()的方法,用于异步的连接,这样程序不斛被阻塞,可以立即执行后面的操作.这是因为由于网络阻塞等问题,连接可能需要较长时间才能完成,因此在网络编程中几乎所有操作都提供了同步,异步两个版本.

 

再创建一个新的控制台应用程序,命名为ClientConsole,作为客户端,然后添加下面的代码,创建于服务器的连接:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running...");
            TcpClient client = new TcpClient();
            try
            {
                //与服务器建立连接
                client.Connect(IPAddress.Parse("192.168.3.19"),3419);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }
            //打印连接到的服务端信息
            Console.WriteLine("Server Connected ! Local:{0} -->Server:{1}",client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
 
            //按Q退出
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


在这里我多说一点,获得IPAddress对象的另外几个常用方法:

                IPAddress ip = IPAddress.Parse("92.168.3.19");
                IPAddress ip = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];


 

在上面的代码中,通过调用Connect()方法来与服务器端建立连接.随后,打印了这个连接的信息:本机IP地址和端口号,以及连接到的远程主机IP地址和端口号,应该能看出,客户端的端口号是.NET随机分配的.TcpClientClient属性返回了一个Socket对象,他的LocalEndPointRemoteEndPoint属性分别包含了本地和远程主机的地址信息.运行服务端,在运行客户端,可以看到两边的输出情况.再使用cmdnetstat -a命令查看一下客户端端口的情况,可以看出是ESTABLISHED.

 

这里我简单说一下,使用netstat -a命令查看得到的信息:


(1)LISTENING状态
FTP服务启动后首先处于侦听(LISTENING)状态。

(2)、ESTABLISHED状态
ESTABLISHED的意思是建立连接。表示两台机器正在通信。

(3)、CLOSE_WAIT

对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE_WAIT 此时我方要调用close()来使得连接正确关闭

(4)、TIME_WAIT

我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAITTCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分 段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情 况下,尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。

    目前有一种避免TIME_WAIT资源浪费的方法,就是关闭socketLINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作可能会带来错误。

(5)、SYN_SENT状态

   SYN_SENT状态表示请求连接,当你要访问其它的计算机的服务时首先要发个同步信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为 ESTABLISHED,此时SYN_SENT状态非常短暂。但如果发现SYN_SENT非常多且在向不同的机器发出,那你的机器可能中了冲击波或震荡波 之类的病毒了。这类病毒为了感染别的计算机,它就要扫描别的计算机,在扫描的过程中对每个要扫描的计算机都要发出了同步请求,这也是出现许多 SYN_SENT的原因。


 这是使用netstat -a命令的效果,当然了,这里我把没用的部分删除了.

  TCP    192.168.3.19:3419      Syx--PC:0              LISTENING

  TCP    192.168.3.19:3419      Syx--PC:11293          ESTABLISHED

  TCP    192.168.3.19:11293     Syx--PC:3419           ESTABLISHED

就算瞎子用屁眼也能想出来这是啥意思吧?

 

端口3419和端口11293建立了连接,端口11923是客户端用来与服务端进行通信的端口.

 

服务端的3419端口在与客户端建立一个连接后,仍然继续保持监听状态,也就是说,一个端口可以建立多个与远程端口的连接.这是肯定的啊,HTTP的默认端口是80,但是一个Web服务器要通过这个端口与不计其数的客户端浏览器进行通信.作为测试,我们再次开启一个新的客户端,会发现依然连接成功.

 

2.多个客户端与服务端连接

 

既然一个服务器端口可以应对多个客户端连接,那么接下来看一下,如何让多个客户端与服务端连接.如同上面所说,一个TcpClient对应着一个Socket,因此只要创建多个TcpClient,然后调用Conect()方法就可以了.:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running...");
            TcpClient client;
            for (int i = 0; i < 2; i++)
            {
                try
                {
                    client = new TcpClient();
                    //与服务器建立连接
                    client.Connect(IPAddress.Parse("192.168.3.19"), 3419);
 
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return;
                }
                //打印连接到的服务端信息
                Console.WriteLine("Server Connected ! Local:{0} -->Server:{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
            }
            //按Q退出
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


上面代码中最重要的就是client=new TcpClient()这句,如果将这句声明放到for的外面,在循环执行第二次的时候就会发生异常,原因很显然:一个TcpClient对象对应一个Socket,一个Socket对应着一个端口,如果不使用new操作符重新创建对象,那么就相当于使用一个已经与服务端建立了连接的端口再次与统一远程服务建立连接.

 

我建议你可以再次使用netstat -a命令看看这几个客户端端口和服务器端口的状态.

 

服务器获取客户端连接

 

1.获取单一客户端的连接

 

前面服务端,客户端的代码已经建立起了连接,通过使用”netstat -a”命令,从端口的状态ESTABLISHED(已建立)可以看出来,但这里的端口状态是操作系统告诉我们的.注意到服务器端只会显示”Start Listening...”,现在需要解决的一个问题就是:如何在服务端获得客户端发起的连接信息?也就是说,在服务器端显示已连接的远程客户端IP地址和端口号.

 

服务器端在开始监听后,可以在TcpListener实例上调用AcceptTcpClient()方法来获取与一个客户端的连接,它返回一个TcpClient类型实例,此时它所包装的是由服务端去往客户端的Socket.而在客户端创建的TcpClient类型实例则是由客户端发往服务端的.这个方法是一个同步方法(或者叫阻塞方法,block method),在程序调用它以后,他会一直等待某个客户端连接,然后才会返回,否则一直等待下去.这样,在调用它以后,除非得到一个客户端连接,不然不会执行接下来的代码.一个很好的类比就是ConsoleReadLine()方法,它读取输入到控制台的一行字符串,如果有输入,就获取字符串,并且继续执行下面的代码;如果没有输入,就会一直等待下去.接下来,修改服务端代码,获得来自客户端的连接信息:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//下面是两个必要的命名空间
using System.Net;
using System.Net.Sockets;
namespace ServerConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Server is running...");
            IPAddress ip = new IPAddress(new byte[] { 192,168,3,19});
            /*
            这里可能出现一点小问题,可能程序会报错,这个时候你可以使用cmd,用netstat -a命令
            查看一下那些端口未被占用.
            */
            TcpListener listener = new TcpListener(ip, 3419);
 
            //开始监听
            listener.Start();
            //获取一个连接,中断方法
            TcpClient remoteClient = listener.AcceptTcpClient();
            //打印连接到的客户端信息
            Console.WriteLine("Client Connected ! Local:{0} <-- Client:{1}",remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);
            Console.WriteLine("Start Listening...");
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}

运行上面的这段代码,会发现服务端执行到listener.AcceptTcpClient()时便停止的了,并不会执行下面的Console.WriteLine()方法.为了让此方法继续执行下去,必须有一个客户端连接它,现在执行客户端.简单起见,只在客户端开启一个端口与服务端连接:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running...");
            TcpClient client;
 
            try
            {
                client = new TcpClient();
                //与服务器建立连接
                client.Connect(IPAddress.Parse("192.168.3.19"), 3419);
 
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }
            //打印连接到的服务端信息
            Console.WriteLine("Server Connected ! Local:{0} -->Server:{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
 
            //按Q退出
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}
 


可以观察一下客户端和服务端的输出.

 

2.获取多个客户端连接

 

到现在为止,接着想,如果有多个客户端发起对服务端的连接会咋样?对客户端的代码进行修改,将发起连接的代码放到一个for循环中,建立三个连接:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running...");
            TcpClient client;
            for (int i = 0; i <= 2; i++)
            {
                try
                {
                    client = new TcpClient();
                    //与服务器建立连接
                    client.Connect(IPAddress.Parse("192.168.3.19"), 3419);
 
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return;
                }
                //打印连接到的服务端信息
                Console.WriteLine("Server Connected ! Local:{0} -->Server:{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
            }
            //按Q退出
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


如果服务端代码不变,先运行服务端,再运行客户端,那么接下来的输出情况可能会是这样:

//服务端:
Server is running...
Client Connected ! Local:192.168.3.19:3419 <-- Client:192.168.3.19:12862
Start Listening...
 
 
输入"Q"键退出.
 
//客户端
Client is running...
Server Connected ! Local:192.168.3.19:12862 -->Server:192.168.3.19:3419
Server Connected ! Local:192.168.3.19:12863 -->Server:192.168.3.19:3419
Server Connected ! Local:192.168.3.19:12864 -->Server:192.168.3.19:3419


 

 

 

现在又出现了前面说过的那个问题:尽管有三个客户端连接到了服务端,但是服务端程序只会接收到一个.这是因为服务端只调用了一次listener.AcceptTcpClient(),而它只对应一个去往客户端的Socket.尽管操作系统知道连接已经建立了,但在程序中却没有进行处理.此时如果再次在”cmd命令提示”中输入”netstat -a”,仍然会看到三个连接都已经成功建立.

 

为了收到三个客户端的建立,只需要对服务端稍微一点的修改就行了,AcceptTcpClient方法放入一个while循环中就行了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client is running...");
            TcpClient client;
            for (int i = 0; i <= 2; i++)
            {
                try
                {
                    client = new TcpClient();
                    //与服务器建立连接
                    client.Connect(IPAddress.Parse("192.168.3.19"), 3419);
 
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return;
                }
                //打印连接到的服务端信息
                Console.WriteLine("Server Connected ! Local:{0} -->Server:{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
            }
            //按Q退出
            Console.WriteLine("\n\n输入\"Q\"键退出. ");
            ConsoleKey key;
            do
            {
                key = Console.ReadKey(true).Key;
            } while (key != ConsoleKey.Q);
        }
    }
}


这段代码看上去是一个死循环,但是并不会让计算机的系统资源耗尽.前面已经说过了,AcceptTcpClient()方法在没有收到客户端连接之前,是不会继续执行的,它的大部分时间都在等待.另外,服务端几乎总是保持运行,所以这样做无可厚非.其实这里就可以省略”按Q退出”这段代码了.此时运行服务端客户端,就能看到想要的效果了.

 

现在服务器端已经可以接收来自多个客户端的连接,接下来 的步骤就是在服务器端与客户端之间收发数据了.

 

在于服务端建立了连接之后,就可以通过此连接收发数据了.端口和端口之间以流(Stream)的形式传输数据,因为任何可以表示为二进制的数据都可以保存到流中,所以实际上可以在客户端与服务端之间传输任何类型的数据.对客户端来说,向流中写入数据,即为向服务端发送数据;从流中读取数据,即为从服务端接收数据.对服务端来说,情况也是一样的.

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值