一、TCP
1、TCP:有同步方式和异步工作方式。
(1)同步工作方式:利用TCP编写的程序执行发送、接收或监听语句时,未完成工作前不再继续往下执行,即处于阻塞状态,直到该语句完成相应的工作后才继续执行下一条语句;异步工作方式是指程序执行到发送、接收或监听语句时,不论工作是否完成,都会继续往下执行。
如在接收数据时,同步方式下接收方执行到接收语句后将处于阻塞方式,只有接收到对方发来的数据后才继续执行下一条语句;如果采用异步工作方式,则接收方在执行到接收语句后,无论是否接收方是否收到对方发来的数据,程序会继续执行下去。
2、客户端和服务端
客户端和服务端是相对的,同一台机器既可以单独运行服务器端,也可以单独运行客户端,也可以将服务器端和客户端同时写到一个程序中。
二、TcpListener类和TcpClient 类
这两个类时对Socket封装后的类,目的是简化网络编程的复杂度。TcpListener类用于监听客户端 的连接请求,TcpClient 类提供本地主机和远程主机的连接信息。
1、TcpListener类
用于监听和接收传入的连接请求。
2、TcpClient 类
用于在同步模式下通过网络连接、发送和接收流数据。
3、编程步骤:
服务器端:
步骤:
(1)创建一个TcpListener对象,然后调用该对象的Start方法在指定的端口进行监听。
(2)在单独的线程中,循环调用AccepTcpClinet方法接收客户端的连接请求,并根据该方法的返回结果得到与该客户端对应的TcpClient对象。
(3)每得到一个新的TcpClient对象,就创建一个与客户对应的线程,在线程中与对应的客户进行通信。
(4)根据传送信息的情况确定是否关闭和客户的连接。
class Program
{
static void Main()
{
int port = 888;//端口
TcpClient tcpClient;//创建TCP连接对象
IPAddress[] serverIP = Dns.GetHostAddresses("127.0.0.1");//定义IP地址 (要解析的IP地址)
IPAddress localAddress = serverIP[0];//IP地址
TcpListener tcpListener = new TcpListener(localAddress, port);//监听套接字
tcpListener.Start(); //开始监听
Console.WriteLine("服务器启动成功,等待用户接入…");//输出消息
while (true)
{
try
{
tcpClient = tcpListener.AcceptTcpClient();//每接收一个客户端则生成一个TcpClient
NetworkStream networkStream = tcpClient.GetStream();//获取网络数据流
BinaryReader reader = new BinaryReader(networkStream);//定义流数据读取对象
BinaryWriter writer = new BinaryWriter(networkStream);//定义流数据写入对象
while (true)
{
try
{
string strReader = reader.ReadString();//接收消息
string[] strReaders = strReader.Split(new char[] { ' ' });//截取客户端消息
Console.WriteLine("有客户端接入,客户IP:" + strReaders[0]);//输出接收的客户端IP地址
Console.WriteLine("来自客户端的消息:" + strReaders[1]);//输出接收的消息
string strWriter = "我是服务器,欢迎光临";//定义服务端要写入的消息
writer.Write(strWriter);//向对方发送消息
}
catch
{
break;
}
}
}
catch
{
break;
}
}
}
}
客户端:
步骤:
(1)利用TcpClient的构造函数创建一个TcpClient对象。
(2)使用Connect方法与服务器建立连接。
(3)利用TcpClient对象的GetStream方法得到网络流。然后利用该网络与服务器进行数据传输。
(4)创建一个线程监听指定的端口,循环接收并处理服务器发送过来的信息。
(5)完成工作后,向服务器端发送关闭信息,并关闭与服务的连接。
static void Main(string[] args)
{
TcpClient tcpClient = new TcpClient();//创建一个TcpClient对象,自动分配主机IP地址和端口号
tcpClient.Connect("127.0.0.1", 888);//连接服务器,其IP和端口号为127.0.0.1和888
if (tcpClient != null)//判断是否连接成功
{
Console.WriteLine("连接服务器成功");
NetworkStream networkStream = tcpClient.GetStream();//获取数据流
BinaryReader reader = new BinaryReader(networkStream);//定义流数据读取对象
BinaryWriter writer = new BinaryWriter(networkStream);//定义流数据写入对象
string localip="127.0.0.1";//存储本机IP,默认值为127.0.0.1
IPAddress[] ips = Dns.GetHostAddresses(Dns.GetHostName());//获取所有IP地址
foreach (IPAddress ip in ips)
{
if (!ip.IsIPv6SiteLocal)//如果不是IPV6地址
localip = ip.ToString();//获取本机IP地址
}
writer.Write(localip + " 你好服务器,我是客户端");//向服务器发送消息
while (true)
{
try
{
string strReader = reader.ReadString();//接收服务器发送的数据
if (strReader != null)
{
Console.WriteLine("来自服务器的消息:"+strReader);//输出接收的服务器消息
}
}
catch
{
break;//接收过程中如果出现异常,退出循环
}
}
}
Console.WriteLine("连接服务器失败");
}
}
三、TCP的无消息边界问题
TCP通信时,接收方能够按照发送方发送的顺序接收数据,但是在网络传输中,可能会出现发送方发送一次的消息与接收方接收一次的消息不一致的情况。称为TCP消息边界问题。
解决方法:
(1)发送固定长度的消息
TcpClient client =new TcpClient(ip,port);
NetWorkStream networkStream =client.GetStream();
BinaryWriter bw = new BinaryWriter(networkStream ,Encoding.UTF8);
bw.Write(35);
(2) 将消息的长度与消息一起发送
每次发送消息前面用四个字节表明本次消息的长度,然后将包含消息长度的消息发送到对方,对方接收到消息后,首先从消息的前面的四个自己获取消息长度,然后根据消息长度值接收发送方发送的数据。
(3)使用特殊标记分割消息