九、Socket之TCP编程

时间:2023-03-10 07:26:11
九、Socket之TCP编程

TCP简介

  TCP是Transmission Control Protocol(传输控制协议)的简称,是TCP/IP体系中面向连接的运输层协议,在网络中提供全双工的和可靠的服务。

  TCP最主要的特点:

  (1)是面向连接的传输层协议;

  (2)每个TCP连接只能有两个端点,而且只能一对一通信,不能一点对多点直接通信。

  (3)通过TCP连接传送的数据,能保证数据无差错、不丢失、不重复地准确到达接收方,并且保证各数据到达的顺序与数据发出的顺序相同。

  (4)数据以字节流的方式传输。

  (5)传输的数据无消息边界。

  利用TCP开发应用程序时,.NET框架提供两种工作方式.

  (1)同步工作方式 指利用TCP编写的程序执行到发送、接收或监听语句时,在未完成工作前不再继续往下执行,

    即处于阻塞状态,直到该语句完成相应的工作后才继续执行下一条语句;

  (2)异步工作方式 异步工作方式是指程序执行到发送、接收或监听语句时,不论工作是否完成,都会继续往下执行。

    例如:同步接收数据时,接收方执行到接收语句后将处于阻塞方式,只有接收到对方发来的数据后才继续执行下一条语句;

    而如果采用异步工作方式,则接收方在执行到接收语句后,无论是否接收到对方发来的数据,程序都继续往下执行。

TcpListener类与TcpClient类

  TcpListener类与TcpClient类两个类均封装了底层的套接字,并分别提供了对套接字进一步封装后的同步和异步操作的方法,降低了TCP应用编程的难度。

  TcpListener类用于侦听和接受传入的连接请求。

  TcpClient类用于提供本地主机和远程主机的连接信息。

  注意,TcpListener和TcpClient只支持标准协议编程。如果希望编写非标准协议的程序,只能使用套接字来实现。

  编写服务器端程序的一般步骤为:

  使用对套接字封装后的类,编写基于TCP的服务器端程序的 一般步骤为:

  (1)创建一个TcpListener对象,然后调用该对象的Start方法在指定的端口进行监听。

  (2)在单独的线程中,循环调用AcceptTcpClient方法接受客户端的连接请求,并根据该方法的返回的结果得到与该客户端对应的TcpClient对象。

  (3)每得到一个新的TcpClient对象,就创建一个与该客户对应的线程,在线程中与对应的客户进行通信。

  (4)根据传送信息的情况确定是否关闭与客户的连接。

  编写客户端程序的一般步骤为:

  使用对套接字封装后的类,编写基于TCP的客户端程序的一 般步骤如下:

  (1)利用TcpClient的构造函数创建一个TcpClient对象。

  (2)使用Connect方法与服务器建立连接。

  (3)利用TcpClient对象的GetStream方法得到网络流,然后利用该网络流与服务器进行数据传输。

  (4)创建一个线程监听指定的端口,循环接收并处理服务器发送过来的信息。

  (5)完成工作后,向服务器发送关闭信息,并关闭与服务器的连接。

  在网络传输中,可能会出现发送方一次发送的消息与接收方一次接收的消息不一致的现象。

  这主要是因为TCP协议是字节流形式的、无消息边界的协议,由于受网络传输中的不确定因素的影响,因此不能保证单个Send方法发送的数据被单个Receive方法读取。

  实际应用中,解决TCP协议消息边界问题的方法有三种

  1、发送固定长度的消息,该方法适用于消息长度固定的场合。

  2、将消息长度与消息一起发送,这种方法适用于任何场合。

  3、使用特殊标记分隔消息,这种方法主要用于消息中不包含特殊标记的场合。

  第一种通过网络流来解决消息边界问题:

 //客户端
private BinaryReader br;
private BinaryWriter bw;
//客户端获取网络流
NetworkStream networkStream = client.GetStream(); /// <summary>向服务器端发送信息</summary>
private void SendMessage(string message)
{
try
{
//将字符串写入网络流,此方法会自动附加字符串长度前缀
bw.Write(message);
bw.Flush();
}
catch
{
throw new Exception("发送失败!");
}
} //服务器端
//读取网络流
string receiveString = br.ReadString();

  第二种定义消息开始和结尾,字节流的形式传输

/// <summary>
/// 受保护将要传送数据转成二进制数组方法
/// </summary>
/// <param name="obj">要转换的对象</param>
/// <returns>被转换成二进制数</returns>
internal virtual byte[] FromObjectToBytes(object obj)
{
lock (this)
{
byte byteType = ;
byte[] bytesContent = null;
byte[] bytesResult = null;
byte[] bytesLength = null; if (obj is string)
{
byteType = (byte)EnumDataMark.Text;
bytesContent = Encoding.UTF8.GetBytes(obj.ToString());
bytesLength = BitConverter.GetBytes(bytesContent.Length);
}
else if (obj is object)
{
byteType = (byte)EnumDataMark.Obj; try
{
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Seek(, SeekOrigin.Begin); bytesLength = BitConverter.GetBytes((int)ms.Length);
bytesContent = new byte[ms.Length];
ms.Read(bytesContent, , (int)ms.Length);
ms.Close();
}
catch (SerializationException se)
{
throw se;
}
} bytesResult = new byte[bytesContent.Length + ];
bytesResult[] = (byte)EnumDataMark.Start;//开始
bytesResult[] = byteType;
Array.Copy(bytesLength, , bytesResult, , bytesLength.Length);
Array.Copy(bytesContent, , bytesResult, , bytesContent.Length);
bytesResult[bytesResult.Length - ] = (byte)EnumDataMark.end;//结尾 return bytesResult;
}
}