服务端获取客户端连接
1. 获取单一客户端连接
上面服务端、客户端的代码已经建立起了连接,这通过使用“netstat -a
”命令,从端口的状态可以看出来,但这是 操作系统
告诉我们的。 那么我们现在需要知道的就是:服务端的程序如何知道已经与一个客户端建立起了连接
?
服务器端开始侦听以后,可以在 TcpListener
实例上调用 AcceptTcpClient()
来获取与一个客户端的连接,它返回一个 TcpClient
类型实 例。此时它所包装的是由服务端去往客户端的Socket
,而我们在客户端创建的TcpClient则是由客户端去往服务端的。这 个方法是一 个 同步方法
(或者叫阻断方法,block method
),意思就是说,当程序调用它以后,它会一直等待某个客户端连接,然后才会返回,否 则就会一直等下去。这样的话,在调用它以后,除非得到一个 客户端连接,不然不会执行接下来的代码。一个很好的类比就是 Console.ReadLine()
方法,它读取输入在控制台中的一行字符串,如果有输入, 就继续执行下面代码;如果没有输入,就会一直等待 下去。
class Server {
static void Main(string[] args) {
Console.WriteLine("Server is running ... ");
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
TcpListener listener = new TcpListener(ip, 8500); listener.Start(); // 开始侦听
Console.WriteLine("Start Listening ...");
// 获取一个连接,中断方法
TcpClient remoteClient = listener.AcceptTcpClient();
// 打印连接到的客户端信息
Console.WriteLine("Client Connected!{0} <-- {1}",
remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
// 按 Q 退出
}
}
运行这段代码,会发现服务端运行到 listener.AcceptTcpClient()
时便停止了,并不会执行下面的 Console.WriteLine()
方法。为了让它继续执行下去,必须有一个客户端连接到它,所以我们现在运行客户端,与它进行连接。简单起见,我们 只在客户端开启一个端口与之连接:
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 退出
}
}
此时,服务端、客户端的输出分别为:
// 服务端
Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:5188
// 客户端
Client Running ...
Server Connected!127.0.0.1:5188 --> 127.0.0.1:8500
2. 获取多个客户端连接
现在我们再接着考虑,如果有多个客户端发动对服务器端的连接会怎么样,为了避免你将浏览器向上滚动,来查看上面的代码,我将它拷贝了下来,我们先看下客户端的关键代码:
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);
}
如果服务端代码不变,我们先运行服务端,再运行客户端,那么接下来会看到这样的输出:
// 服务端
Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:5226
// 客户端
Client Running ...
Server Connected!127.0.0.1:5226 --> 127.0.0.1:8500 Server Connected!127.0.0.1:5227 --> 127.0.0.1:8500 Server Connected!127.0.0.1:5228 --> 127.0.0.1:8500
就又回到了本章第2.2小节“多个客户端与服务端连接”中的处境:尽管有三个客户端连接到了服务端,但是服务端程序只接收到了一个。这是因为服务端只调用了一次 listener.AcceptTcpClient(),而它只对应一个连往客户端的Socket
。但是操作系统是知道连接已经建 立了的,只是我们程序中没有处理到,所以我们当我们输入“netstat -a
”时,仍然会看到3对连接都已经建立成功。
为了能够接收到三个客户端的连接,我们只要对服务端稍稍进行一下修改,将AcceptTcpClient
方法放入一个do/while
循环中就可以了:
Console.WriteLine("Start Listening ...");
while (true) {
// 获取一个连接,同步方法
TcpClient remoteClient = listener.AcceptTcpClient();
// 打印连接到的客户端信息
Console.WriteLine("Client Connected!{0} <-- {1}",
remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
}
这样看上去是一个死循环,但是并不会让你的机器系统资源迅速耗尽。因为前面已经说过了,AcceptTcpClient()
再没有收到客户端的连 接 之前,是不会继续执行的,它的大部分时间都在等待。另外,服务端几乎总是要保持在运行状态,所以这样做并无不可,还可以省去“按Q退出”那段代码。此时再 运行代码,会看到服务端可以收到3个客户端的连接了。
Server is running ...
Start Listening ...
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:5305
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:5306
Client Connected!127.0.0.1:8500 <-- 127.0.0.1:5307
本篇文章到此就结束了,接下来一篇我们来看看如何在服务端与客户端之间收发数据。