2.客户端与服务端连接


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

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

这里需要注意的是,不管是使用有参数的构造函数与服务器连接,或者是通过Connect()方法与服务器建立连接,都是同步方法(或者 说是阻塞的,英文叫block)。 它的意思是说,客户端在与服务端连接成功、从而方法返回,或者是服务端不存、从而抛出异常之前, 是无法继续进行后继操作的。这里还有一个名为 BeginConnect()的方法,用于实施异步的连接,这样程序不会被阻塞,可以立即执行 后面的操作,这是因为可能由于网络拥塞等问题,连接需要较长 时间才能完成。网络编程中有非常多的异步操作,凡事都是由简入难,关于异步操作,我们后面再讨论,现在只看同步操作。

创建一个新的控制台应用程序项目,命名为ClientConsole,它是我们的客户端,然后添加下面的代码,创建与服务器的连接:

class Client {
	static void Main(string[] args) {
		Console.WriteLine("Client Running ...");
		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 退出
	}
}

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

// 服务端:
Server is running ...
Start Listening ...
// 客户端:
Client Running ...
Server Connected!127.0.0.1:4761 --> 127.0.0.1:8500

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

TCP    jimmy:8500             0.0.0.0:0              LISTENING 
TCP    jimmy:8500             localhost:4761         ESTABLISHED 
TCP    jimmy:4761             localhost:8500         ESTABLISHED

从这里我们可以得出几个重要信息:

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

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

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

class Client {
	static void Main(string[] args) {
		Console.WriteLine("Client Running ...");
		TcpClient client;
		for (int i = 0; i <= 2; i++) {
			try {
				client = new TcpClient(); 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 退出
	}
}

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

此时,如果在“命令提示符”运行“netstat -a”,则会看到类似下面的的输出:

TCP    jimmy:8500             0.0.0.0:0               LISTENING 
TCP    jimmy:8500             localhost:10282        ESTABLISHED 
TCP    jimmy:8500             localhost:10283        ESTABLISHED 
TCP    jimmy:8500             localhost:10284        ESTABLISHED 
TCP    jimmy:10282            localhost:8500         ESTABLISHED 
TCP    jimmy:10283            localhost:8500         ESTABLISHED 
TCP    jimmy:10284            localhost:8500         ESTABLISHED

可以看到创建了三个连接对,并且 8500 端口持续保持侦听状态,从这里以及上面我们可以推断出 TcpListener  的 Start() 方法是一个异 步方法。

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
YES开发框架
评论列表

发表评论

评论内容
昵称:
关联文章