文章目录
一、基础概念
1-1 TCP面向连接
TCP了解基础:
1.面向连接的
(Connection-oriented)的,也就是两个远程主机(或者叫进程,远程通信实际上是进程之间的通信,进程是指运行中的程序)必须进行一个握手过程
。
2. TCP是全双工
的,就是进程A和进程B连接后,进程A可以向进程B传递信息,同时进程B也可以向进程A传递信息。
3. TCP有个特性,称为可靠的数据传输
,连接建立后,数据的发送一定能够到达,并且是有序的。
TCP还有个重要的概念是套接字
(Socket),在OSI(Open System Interconnect,开放式系统互联)网络七层协议,TCP属于传输层协议
。通过套接字,可以从应用程序中获取来自于传输层的数据。套接字像是传输层的一扇门,应用程序通过这扇门向远程发送数据,或者接收来自远程的数据。这个门后面的事情,比如消息是如何传递的、是如何保证可靠性的,开发者不需要关心,这属于网络底层的事情。下图为套接字的作用:
1-2 即时通信程序的三种模式
网络编程的最常见应用就是即时通信程序。
其中即时通信程序有三种模式:点对点
、广播服务器
、中央服务器
。
1-2-1 点对点
点对点:每台计算机即是客户端也是服务器,因为它需要端口的监听
。
这种模式的难点:各个主机之间如何知道其他主机的存在?通常做法是当某一主机上线,通过UDP协议进行一个IP广播(IP Broadcasting),使用这种方式通知其他主机自己已上线并说明位置,收到广播主机发回的一个应答,此时主机自己便知道其他主机的存在,然后便可以进行连接和通信。
1-2-2 广播服务器
广播服务器 由服务器专门进行广播
。服务器保持监听端口状态,当有主机上线时,首先连接至服务器,服务器在收到连接后,将该主机的IP地址和端口号广播给其他在线主机(图中用虚线表示)。这样其他主机即知道该主机已上线,并知道主机IP和端口号,可以与主机直接进行连接并进行通话,不用经过服务器(用实线表示)。因此,在使用这种模式,各个主机仍需要保持对端口的侦听。
其中广播服务器模式 解决了点对点模式的判断有多少主机在线的问题。
1-2-3 中央服务器
中央服务器模式最简单并且最实用
,其中与广播服务器模式有点类似。区别是,每台主机在上线时就与服务器建立了连接,主机之间并不建立连接。如果主机A向主机B发送消失时,通过的路径是 主机A–>服务器–>主机B,通过这种模式,各个主机不需要对端口进行侦听,只需要服务器对端口进行侦听
。
如果主机A想要传输的是一个比较大文件(例如图片、文件)给主机B,主机A可以与主机B之间可以搭建一个临时
的连接(用虚线表示),传输完成后,关闭该连接。
除此以外,由于消息都经过服务器,因此服务器还可以缓存主机间的对话,即当主机A发往主机B时,如果主机B已经离线,则服务器可以对消息进行缓存,当主机B再次连接服务器时,服务器自动将缓存的消息发给主机B。
二、基本操作
该例子采用第三种模式,中央服务器
模式。
2-1 服务端与一个客户端
2-1-1 服务端
首先开启对本地计算机上某一端口的侦听。创建一个控制台应用程序,命名为ServerConsole,代表服务端。
static void Main(string[] args)
{
Console.WriteLine("Server is running");
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
// 获得IPAdress 对象的另外几种常用方法:
// IPAddress ip = IPAddress.Parse("127.0.01");
// 监听服务器的IP 与端口
TcpListener listener = new TcpListener(ip, 8500);
// 开启监听
listener.Start();
Console.WriteLine("Start Listing...");
// 获取一个连接,中断方法
TcpClient remoteClient = listener.AcceptTcpClient();
Console.WriteLine("Client Connected! Local:{0}<--Client:{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
Console.WriteLine("\n\n 输入\"Q\"键退出。");
ConsoleKey key;
do
{
key = Console.ReadKey(true).Key;
} while (key != ConsoleKey.Q);
}
创建TcpListener实例开启了8500端口的侦听:
127.0.0.1是本地计算机IP地址。打开cmd命令提示符
,输入 Netstat -a
查看计算机所有活动连接的状态。
提示:
在打开对端口侦听以后,需要对程序进行阻塞操作,例如:Console.ReadKey(),使代码不会执行到末尾而结束,从而cmd命令提示符 捕捉不到8500端口号。
listener.AcceptTcpClient()
获取与一个客户端的连接,返回一个TcpClient类型实例这个方法是 同步方法
,调用它之后,会一直等待某个客户端连接,否则会一直等下去。
2-1-2 客户端
服务器开启侦听之后,便可以创建客户端与它连接。
创建一个控制台应用程序,命名为ClientConsole,代表客户端。
static void Main(string[] args)
{
Console.WriteLine("Client isRunning.....");
TcpClient client = new TcpClient();
try
{
// 与服务器建立链接 - 握手
client.Connect(IPAddress.Parse("127.0.0.1"), 8500);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
// 打印连接到的服务端信息
Console.WriteLine("Server Connected! Local:{0}-->Server:{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
Console.WriteLine("\n\n输入\"Q\"停止程序");
ConsoleKey key;
do
{
key = Console.ReadKey(true).Key;
} while (key != ConsoleKey.Q);
}
运行结果:
查看侦听端口:
可以看出几个重要信息:
- 端口8500和端口57077 建立连接。
- 端口8500与一个客户端建立一个连接后,仍然继续保持在侦听状态。说明,一个端口可以建立多个与远程端口的连接。
2-2 服务端与多个客户端
2-2-1 服务端
修改服务端代码,使侦听并输出连接多个客户端信息:
static void Main(string[] args)
{
Console.WriteLine("Server is running");
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
// 监听服务器的IP 与端口
TcpListener listener = new TcpListener(ip, 8500);
// 开启监听
listener.Start();
Console.WriteLine("Start Listing...");
while (true)
{
// 获取一个连接,中断方法
TcpClient remoteClient = listener.AcceptTcpClient();
Console.WriteLine("Client Connected! Local:{0}<--Client:{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
}
Console.WriteLine("\n\n 输入\"Q\"键退出。");
ConsoleKey key;
do
{
key = Console.ReadKey(true).Key;
} while (key != ConsoleKey.Q);
}
其中,While(true)看上去是一个死循环,但并不会使计算机的系统资源迅速耗尽,其中AcceptTcpClient()方法没有收到客户端的连接之前,是不会继续执行的,它的大部分时间都在等待。
2-2-2 客户端
static void Main(string[] args)
{
Console.WriteLine("Client isRunning.....");
TcpClient client;
for (int i = 0; i < 3; i++)
{
try
{
// 一个new实例化、有一个套接字,只能与远程服务器连接一次
client = new TcpClient();
// 与服务器建立链接 - 握手
client.Connect(IPAddress.Parse("127.0.0.1"), 8500);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
// 打印连接到的服务端信息
Console.WriteLine("Server Connected! Local:{0}-->Server:{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
}
Console.WriteLine("\n\n输入\"Q\"停止程序");
ConsoleKey key;
do
{
key = Console.ReadKey(true).Key;
} while (key != ConsoleKey.Q);
}
执行结果:
查看侦听端口: