您的位置:首页 > 编程语言

揭开Socket编程的面纱 (一)

2011-07-25 11:49 411 查看
声明一下。这个系列是博客园一个高手的我转载来学了。不过对他的代码加了点小注释。嘿嘿

对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

1. 什么是TCP/IP、UDP?
2. Socket在哪里呢?
3. Socket是什么呢?
4. 你会使用它们吗?

什么是TCP/IPUDP

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
这里有一张图,表明了这些协议的关系。



图1

TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IP与UDP的关系了吧。
Socket在哪里呢?在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。



图2

原来Socket在这里。
Socket是什么呢? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
你会使用它们吗?前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。



图3

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
在这里我就举个简单的例子,我们走的是TCP协议这条路(见图2)。例子用MFC编写,运行的界面如下:

(Microsoft Foundation Classes),是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows的API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。



图4



图5

在客户端输入服务器端的IP地址和发送的数据,然后按发送按钮,服务器端接收到数据,然后回应客户端。客户端读取回应的数据,显示在界面上。
客户端就一个函数完成了一次通信。在这里IP地址为何用127.0.0.1呢?使用这个IP地址,服务器端和客户端就能运行在同一台机器上,这样调试方便多了。当然你可以在你朋友的机器上运行Server程序(本人在局域网中测试过),在自己的机器上运行Client程序,当然输入的IP地址就该是你朋友机器的IP地址了。
简单的理论和实践都说了,现在Socket编程不神秘了吧?希望对你有些帮助。

附C#简单测试源码:/Files/lmjob/socket通信.rar

这个代码的解析:

服务端

int portNum = Convert.ToInt32(this.textBox1.Text);

//得到端口号码

TcpListener listener = new TcpListener(portNum);

//建立TCP监听端口

//TcpListener 类提供一些简单方法,用于在阻止同步模式下侦听和接受传入连接请求。可使用 TcpClientSocket 来连接 TcpListener。可使用 IPEndPoint、本地 IP 地址及端口号或者仅使用端口号,来创建 TcpListener。可以将本地 IP 地址指定为 Any,将本地端口号指定为 0(如果希望基础服务提供程序为您分配这些值)。如果您选择这样做,可在连接套接字后使用 LocalEndpoint 属性来标识已指定的信息。

Start 方法用来开始侦听传入的连接请求。Start 将对传入连接进行排队,直至您调用 Stop 方法或它已经完成 MaxConnections 排队为止。可使用 AcceptSocketAcceptTcpClient 从传入连接请求队列提取连接。这两种方法将阻止。如果要避免阻止,可首先使用 Pending 方法来确定队列中是否有可用的连接请求。

listener.Start();

this.richTextBox1.AppendText("Waiting for connection..." + "\r");

TcpClient client = listener.AcceptTcpClient();

//得到客户端的请求连接

//为使 TcpClient 连接并交换数据,使用 TCP ProtocolType 创建的 TcpListener 或 Socket 必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接到该侦听器:

·                          创建一个 TcpClient,并调用三个可用的 Connect 方法之一。

·                          使用远程主机的主机名和端口号创建 TcpClient。此构造函数将自动尝试一个连接。


this.richTextBox1.AppendText("Connection accepted." + "\r");

//System.Net.Sockets.tc

NetworkStream ns = client.GetStream();

//返回用于发送和接收数据的 NetworkStream

//提供用于网络访问的基础数据流。

NetworkStream 类提供在阻止模式下通过 Stream 套接字发送和接收数据的方法。

若要创建 NetworkStream,必须提供连接的 Socket。

将 Write 和 Read 方法用于简单的单线程同步阻止 I/O。若要使用不同的线程来处理 I/O,则请考虑使用 BeginWrite 和 EndWrite 方法,或 BeginRead 和 EndRead 方法进行通信。

NetworkStream 不支持对网络数据流的随机访问


byte[] byteTime = Encoding.UTF8.GetBytes(this.textBox2.Text);

//得到要发送给客户端的数据。转换成BYTE类型数据

try

{

ns.Write(byteTime, 0, byteTime.Length);

//buffer

类型:array<System..::.Byte>[]()[]

类型 Byte 的数组,该数组包含要写入 NetworkStream 的数据。

offset

类型:System..::.Int32

buffer 中开始写入数据的位置。

size

类型:System..::.Int32

要写入 NetworkStream 的字节数。


ns.Close();

//传输完毕,就立即关闭NS

//这时等待客户端关闭client,客户端关闭完了,服务端才关闭

client.Close();

//只传输一次,就关闭了。对应的客户端的代码也是这样连接的。

//Close 方法将该实例标记为已释放并关闭 TCP 连接。调用此方法将关闭 Socket,并且还将关闭用于发送和接收数据的关联 NetworkStream(如果创建有一个这样的流)。

}

catch (Exception ee)

{

MessageBox.Show(ee.ToString());

}

listener.Stop();

//关闭监听端口

//

Stop 方法不会关闭任何已接受的连接。需要用户负责分别关闭这些连接。

}

客户端

private void button1_Click(object sender, EventArgs e)

{

try

{

int portNum = Convert.ToInt32(this.textBox2.Text);

//服务器端口,可以随意修改

string hostName = this.textBox1.Text; //服务器地址,127.0.0.1指本机

this.richTextBox1.AppendText("请求连接" + hostName + ":" + portNum.ToString() + "\r");

TcpClient client = new TcpClient(hostName, portNum);

//创建 TCP对象 TcpClient 构造函数 (String, Int32) 初始化 TcpClient 类的新实例并连接到指定主机上的指定端口。

hostname

类型:System..::.String

要连接到的远程主机的 DNS 名。

port

类型:System..::.Int32

要连接到的远程主机的端口号。


NetworkStream ns = client.GetStream();

// client.GetStream();

// 返回用于发送和接收数据的 NetworkStream

byte[] bytes = new byte[1024]; //长度

int bytesRead = ns.Read(bytes, 0, bytes.Length);//返回字节长度

// buffer

类型:array<System..::.Byte>[]()[]

类型 Byte 的数组,它是内存中用于存储从 NetworkStream 读取的数据的位置。

offset

类型:System..::.Int32

buffer 中开始将数据存储到的位置。

size

类型:System..::.Int32

要从 NetworkStream 中读取的字节数。

返回值
类型:System..::.Int32

从 NetworkStream 中读取的字节数。


this.richTextBox1.AppendText(Encoding.UTF8.GetString(bytes, 0, bytesRead) + "\r");

//定义在1024个字符范围内去读取数据,然后得到真实数据长度。读出传输字节长度范围内所有数据。

//Rdstrm = new StreamReader(邮件服务流对象, System.Text.Encoding.GetEncoding("gb2312"));

//服务端当它收到TCP请求连接时就把数据NS直接写出,写完了就关闭NS。

client.Close();

//读完数据后,直接关闭TCP连接。

//服务端受到连接关闭后,也关闭它的连接

}

catch (Exception ee)

{

MessageBox.Show(ee.ToString());

this.richTextBox1.AppendText(ee.ToString() + "\r");

}

}

附图







内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: