【转自:https://www.cnblogs.com/IPrograming/archive/2012/10/15/CSharp_Socket_4.html】 

经过前面基础知识作为背景,现在对Socket编程进行进一步的学习。在 System.Net.Socket 命名空间提供了Socket类,利用该类我们可以直接编写Socket的客户端和服务的的程序。但是直接使用Socket类编写Socket程序会比较麻烦、而且容易出错,所以.NET为我们提供了进一步封装好的TcpListener类、TCPClient类和UdpClient类。同时,当我们希望通过网络传输数据时,首先应该将数据转换为数据流

1.Socket的类型

  Socket的中文释义称为套接字,是支撑TCP/IP通信最基本的操作单元。可以将Socket看做不同主机之间的进程进行双向通信的端点,在一个双方都可以通信的Socket实例中,既保存了对方的IP地址和端口,也保持了双方通信采用的协议等信息。Socket有三种不同的类型:

  • ①.流套接字:实现面向连接的TCP通信
  • ②.数据报套接字:实现无连接的UDP通信
  • ③.原始套接字:实现IP数据包的通信(这里不做讨论)

三种类型的套接字的对象均可使用Socket类来构造:

/// <summary>
/// Socket 构造函数
/// </summary>
/// <param name="addressFamily">网络类型</param>
/// <param name="socketType">Socket类型</param>
/// <param name="protocolType">Socket使用的协议</param>
public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType)

当我们编写基于TCP和UDP的应用程序时,既可以使用对套接字进行进一步封装的TcpListener类、TCPClient类和UdpClient类,也可以直接使用Socket类来实现,如果没有特殊需求应该使用进一步封装过的类,由于Socket类是他们实现的基础,所有在这里我们先从学习Socket类入手。

2.第一个Socket程序

  在C# Socket编程(1)基本的术语和概念这篇博客中我们知道:IP协议层之上是传输层(transport layer),它提供了两种可选的协议:TCP协议和UDP协议,它们分别是面向连接和无连接的两种协议。在面向连接的Socket中,使用TCP来建立两个地址端点的会话。一旦建立这种连接,就可以在设备之间进行可靠的数据传输。在进行跟深入的学习前我们先巩固一个简单的例子(TCP)来对Socket编程建立一个直观的印象。  

  TCP Socket连接的过程可以简单的分为:①.服务端监听 ②.客户端请求 ③.建立连接。在使用面向连接的Socket进行通信之前,两个应用程序之间首先要建立一个TCP连接,这涉及两台相互通信的主机的TCP部件间完成的握手消息(handshake message)的交换。下面我们通过直接使用Socket类来构建一个简单的Socket应用程序(这里先从同步Socket入手,实际项目要比这复杂,有许多需要考虑的问题:如消息边界问题、端口号是否冲突、消息命令的解析等等)。在这里我们为了和每一个客户端进行通信建立两个线程:一个是接受客户端连接的线程,一个是接受客户端数据的线程,下面是分别是示例程序的服务端和客户端的代码:

2.1 服务器端代码:

private static byte[] m_DataBuffer = new byte[];
//设置端口号
private static int m_Port = ;
static Socket serverSocket;
static void Main(string[] args)
{
//为了方便在本机上同时运行Client和server,使用回环地址为服务的监听地址
IPAddress ip = IPAddress.Loopback;
//实例化一个Socket对象,确定网络类型、Socket类型、协议类型
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//Socket对象绑定IP和端口号
serverSocket.Bind(new IPEndPoint(ip, m_Port));
//挂起连接队列的最大长度为15,启动监听
serverSocket.Listen(); Console.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());
//一个客户端连接服务器时创建一个新的线程
Thread myThread = new Thread(ListenClientConnect);
myThread.Start();
} /// <summary>
/// 接收连接
/// </summary>
private static void ListenClientConnect()
{
while (true)
{
//运行到Accept()方法是会阻塞程序(同步Socket),
//收到客户端请求创建一个新的Socket Client对象继续执行
Socket clientSocket = serverSocket.Accept();
clientSocket.Send(Encoding.UTF8.GetBytes("Server说:Client 你好!"));
//创建一个接受客户端发送消息的线程
Thread reciveThread = new Thread(ReciveMessage);
reciveThread.Start(clientSocket);
}
} /// <summary>
/// 接收信息
/// </summary>
/// <param name="clientSocket">包含客户端信息的套接字</param>
private static void ReciveMessage(Object clientSocket)
{
if (clientSocket != null)
{
Socket m_ClientSocket = clientSocket as Socket;
while (true)
{
try
{
//通过clientSocket接收数据
int reciverNumber = m_ClientSocket.Receive(m_DataBuffer);
Console.WriteLine("接收客户端:{0}消息:{1}", m_ClientSocket.RemoteEndPoint.ToString(), Encoding.UTF8.GetString(m_DataBuffer, , reciverNumber));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
m_ClientSocket.Shutdown(SocketShutdown.Both);
m_ClientSocket.Close();
break;
}
}
}
}
}

2.2 客户端代码:

//创建一个数据缓冲区
private static byte[] m_DataBuffer = new byte[];
static void Main(string[] args)
{
IPAddress ip = IPAddress.Loopback;
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try
{
clientSocket.Connect(new IPEndPoint(ip, ));
Console.WriteLine("连接服务器成功");
}
catch (Exception ex)
{
Console.WriteLine("连接服务器失败,按回车键退出");
Console.WriteLine(ex.Message);
return;
}
//通过clientSocket接收数据
int receiveLength = clientSocket.Receive(m_DataBuffer);
Console.WriteLine("接受服务器消息:{0}", Encoding.UTF8.GetString(m_DataBuffer, , receiveLength));
//通过clientSocket发送数据
for (int i = ; i < ; i++)
{
try
{
Thread.Sleep();
string sendMessage = string.Format("{0} {1}", "Server 你好!", DateTime.Now.ToString());
clientSocket.Send(Encoding.UTF8.GetBytes(sendMessage));
Console.WriteLine("向服务器发送消息:{0}", sendMessage);
}
catch
{
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
break;
}
}
Console.WriteLine("发送完毕,按回车键退出");
Console.Read();
}
}

2.3 运行示例程序

首先运行服务端程序:

  

接着运行客户端程序,向服务端发送消息后

  

这时候我们可以看到服务端已经收到了客户端发送的消息

  

3.网络流和内存流

  通过网络传输数据,或者对文件数据进行操作的时候都需要先将数据转换为数据流。典型的数据流是和某个外部数据源相关,数据源可以是文件、外部设备、内存、网络套接字等。.NET提供多个从Stream类派生的子类来对不同的数据源提供支持,每个类都代表了一种具体的数据流类型。例如和磁盘文件相关的文件流FileStream和Socket相关的NetworkStream,和内存相关的MemoryStream等,在Socket编程中我们只需了解NetworkStream和MemoryStream(具体文件IO可以参考博文:.NET I/O 学习笔记:文件的读和写),一个用来网络数据的传输,另一个用作数据缓冲区。

3.1 网络流(NetworkStream)

  数据在网络的各个位置之间是以连续的字节形式传输的,我们使用NetworkStream类来发送和接收网络数据。和其他的的流类型不同NetworkStream 类是在System.Net.Sockets 命名空间中的,该类实现专门用于网络资源的 Stream 类。NetworkStream 选件类和其他流之间的主要差异在于NetworkStream 没有当前位置的概念,因此不支持查找功能,并且NetworkStream仅支持面向连接(TCP)的Socket。

  对于NetworkStream来说,写入操作是指将数据源内存缓冲区到网络上的数据传输;读取操作是从网络上到接收端内存缓冲区的数据传输。

创建NetworkStream对象

  我们可以通过TcpClient对象的GetStream()方法获取该对象发送和接收数据的 NetworkStream 对象:

TcpClient client = new TcpClient();
client.Connect("www.baidu.com", );
NetworkStream nStream = client.GetStream();

  也可以通过使用Socket来获取 NetworkStream 对象:

NetworkStream myNetworkStream = new NetworkStream(mySocket);   

通过NetworkStream对象获取数据

  接收数据端通过调用Read方法将数据从接收缓冲区中读取到进程缓冲区中,完成读取操作。可以通过调用DataAvailable属性来确定是否还有数据可供读取,如下:

TcpClient client = new TcpClient();
client.Connect("www.baidu.com", );
NetworkStream nStream = client.GetStream();
//是否有数据可读
if (nStream.CanRead)
{
//接受数据的缓冲区
byte[] myReadBuffer = new byte[];
StringBuilder completeMessage = new StringBuilder();
int numberOfBytesRead = ;
//准备接收的信息也有可能大于1024所以使用循环
do
{
numberOfBytesRead = nStream.Read(myReadBuffer,,myReadBuffer.Length);
completeMessage.AppendFormat("{0}",Encoding.UTF8.GetString(myReadBuffer,,numberOfBytesRead);
}while(nStream.DataAvailable);
Console.WriteLine("接受的信息为:"+completeMessage);
}
else
{
Console.WriteLine("当前没有可供读取的数据。");
}

3.2 内存流(MemoryStream)

  MemoryStream表示保存在内存中的数据流,有该类封装的数据可以直接在内存中访问。内存流一般用于暂时缓存数据以降低应用程序对临时缓冲区和临时文件的需要。内存流相对于字节数组容量可以自动增长,并且在需要对数据进行加密以及对数据长度不定的数据进行缓存时,使用内存流比较方便。MemoryStream支持对数据流的查找和随机访问,当该类对象的CanSeek属性值为True时,程序可以通过范围Position属性获取内存流当前的位置。下面我们通过一个简单的小示例学习如何具体使用内存流:

static void Main(string[] args)
{
//构造MemoryStream实例
MemoryStream m_Stream = new MemoryStream();
Console.WriteLine("初始化分配容量:{0}", m_Stream.Capacity);
Console.WriteLine("初始使用量:{0}", m_Stream.Length); //将待写入数据从字符串转换为字节数组
UnicodeEncoding encoder = new UnicodeEncoding();
byte[] bytes = encoder.GetBytes("新增数据"); //向内存流中写入数据
for (int i = ; i < ; i++)
{
Console.WriteLine("第{0}写入新数据", i);
m_Stream.Write(bytes, , bytes.Length);
} //写入数据后MemoryStream实例的容量和使用量的大小
Console.WriteLine("当前分配容量:{0}", m_Stream.Capacity);
Console.WriteLine("当前使用量:{0}", m_Stream.Length); Console.Read();
}
 
 

【转】C# Socket编程(4)初识Socket和数据流的更多相关文章

  1. C# Socket编程(4)初识Socket和数据流

    经过前面基础知识作为背景,现在对Socket编程进行进一步的学习.在System.Net.Socket命名空间提供了Socket类,利用该类我们可以直接编写Socket的客户端和服务的的程序.但是直接 ...

  2. 2018.12.02 Socket编程之初识Socket

    Socket编程主要分为TCP/UDP/SCTP三种,每一种都有各自的优点,所以会根据实际情况决定选用何种Socket,今天开始我将会逐步学习Socket编程,并将学习过程记录于此. 今天学习的是TC ...

  3. Socket编程实践(2) Socket API 与 简单例程

    在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...

  4. socket编程 —— 非阻塞socket (转)---例子已上传至文件中

    在上一篇文章 <socket编程——一个简单的例子> http://blog.csdn.net/wind19/archive/2011/01/21/6156339.aspx 中写了一个简单 ...

  5. Socket编程实践(3) --Socket API

    socket函数 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, ...

  6. C# Socket编程 笔记,Socket 详解,入门简单

    目录 一,网络基础 二,Socket 对象 三,Bind() 绑定与 Connect() 连接 四,Listen() 监听请求连接 和 Accept() 接收连接请求 五,Receive() 与 Se ...

  7. 【Socket编程】通过Socket实现TCP编程

    通过Socket实现TCP编程 Socket通信 : 1.TCP协议是面向对象连接.可靠的.有序的,以字节流的方式发送数据. 2.基于TCP协议实现网络通信的类: 客户端----Socket类 服务器 ...

  8. 【Socket编程】通过Socket实现UDP编程

    通过Socket实现UDP编程 UDP通信: 1.UDP协议(用户数据报协议)是无连接.不可靠.无序的. 2.UDP协议以数据报作为数据传输的载体. 3.使用UDP进行数据传输时,首先需要将要传输的数 ...

  9. Socket编程实践(2) --Socket编程导引

    什么是Socket? Socket可以看成是用户进程与内核网络协议栈的接口(编程接口, 如下图所示), 其不仅可以用于本机进程间通信,可以用于网络上不同主机的进程间通信, 甚至还可以用于异构系统之间的 ...

随机推荐

  1. JavaScript消息机制入门篇

    JavaScript这个语言本身就是建立在一种消息机制上的,所以它很容易处理异步回调和各种事件.这个概念与普通的编程语言基础是不同的,所以让很多刚接触JavaScript的人摸不着头脑.JavaScr ...

  2. C++类中成员变量的初始化总结

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  3. shell 中的 eval 及 crontab 命令

    eval eval会对后面的命令进行两遍扫描,如果第一遍扫描后,命令是个普通命令,则执行此命令:如果命令中含有变量的间接引用,则保证间接引用的语义.也就是说,eval命令将会首先扫描命令行进行所有的置 ...

  4. 黑苹果Yosemite 10.10.1 修改wowpc.iso文件免选择直接启动Mac系统

    安装教程见: http://www.cnblogs.com/zouzf/p/4356641.html 网上很多教程都是OK的,但每个人的具体情况不同就可能有一些细节问题搞死你1.本文所指的 wowpc ...

  5. 编译lineageos

    lineageos 2 -- 编译rom包 fu*k小米,手机老是1年左右出现充不进去电.前段时间我的红米note4x突然充不进去电了,只好新买了个手机(买手机先看lineageos支持列表 ^_^) ...

  6. 织梦DedeCMS实现 三级栏目_二级栏目_一级栏目_网站名称 的效果代码

    1.将官方原来的排列方式反过来,找到include/typelink.class.php第164行 $this->valuePositionName = $tinfos['typename']. ...

  7. EF Code-First 学习之旅 多对多的关系

    public class Student { public Student() { this.Courses = new HashSet<Course>(); } public int S ...

  8. Android--第三方控件--okHttp

    Android中有很多的第三方控件,其中OkHttp是一个很强大的用于网络加载的第三方框架,当然了,它的内部也是使用原生的代码封装好的.今天我们就来看一下OkHttp的简单用法: 说到网络请求,肯定就 ...

  9. HDFS集群启动的常见问题

    hdfs集群启动的常见问题 1.用浏览器访问namenode的50070端口,不正常,需要诊断问题出在哪里: a.在服务器的终端命令行使用jps查看相关进程 观察节点是否存活 b.如果已经知道了启动失 ...

  10. pandas 数据处理

    1. 查看数值数据的整体分布情况 datafram.describe() 输出: agecount 1463.000000mean 22.948052std 8.385384min 13.000000 ...