套接字编程
套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。套接字存在于通信域中,Windows Sockets只支持一个通信域:网际域(AF-INET),这个域被使用网际协议族通信的进程所使用。
套接字有两种不同的类型:流套接字和数据报套接字。
TCP/IP的Socket则提供3种类型的套接字。
- 1.流式套接字(SOCK_STREAM)
- 提供面向连接、可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传输协议(FTP)即使用流式套接字。
- 2.数据报式套接字(SOCK_DGRAM)
- 提供无连接服务。数据包以独立包形式发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
- 3.原始套接字(SOCK_RAW)
- 该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
通常,在希望通信的两个进程间相互作用的主要模式是客户机/服务器模式(client/server)。客户机/服务器模式工作时要求有一套为客户机和服务器所公认的协议来保证服务能被提供(或接受)。根据不同的情况,协议可以是非对称的也可以是对称的。在对称的协议中,每一方都有可能扮演主从角色;在非对称协议中,一方被不可改变的认为是主机,而另一方则是从机。
服务器端与客户端
服务器软件既包括遵循OSI或其他网络结构的网络软件,又包括由该服务器提供给网络上的应用程序或服务软件。 在服务器上执行的计算通常被称为后端处理。服务器端程序通常在一个众所周知的地址监听对服务的请求。服务器一般首先启动,之后处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“唤醒”并且为客户提供服务。
与服务器端相对应,客户机执行的计算通常被称为前端处理。客户机端软件一般由网络接口软件、支持用户需求的应用程序以及实现某些网络功能的实用程序(如电子邮件等)组成。应用程序软件执行具体的任务,如字处理,电子表格和数据库查询等。实用程序软件通常执行几乎所有网络用户都要求的标准任务。网络接口软件提供各种数据传输服务。
套接字编程原理
服务器端程序执行步骤:
- 打开一个通信通道并告知本地主机,它愿意在某一地址和端口上接收客户请求。
- 等待客户请求到达该端口。
- 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,激活一个新的进程(或线程)来处理这个客户请求。新进程(或线程)处理此客户请求,并不需要对其他请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
- 返回第二步,等待另一客户请求。
- 关闭服务器。
客户端程序执行步骤:
- 打开一个通信通道,并连接到服务器所在主机的特定端口。
- 向服务器发服务请求报文,等待并接收应答;继续提出请求。
- 请求结束后关闭通信通道并终止。
套接字工作原理
套接字可以像Stream流一样被视为一个数据通道,这个通道架设在客户端应用程序和服务器端程序之间,数据的读取(接收)和写入(发送)均针对这个通道来进行。因此要通过网络进行通信,就至少需要一对套接字,其中一个运行于客户端,称之为客户端套接字(ClientSocket),另一个运行于服务器端,称之为服务器端套接字(ServerSocket)。当创建了这两个套接字对象之后,将这两个套接字连接起来就可以实现数据传送了。
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
(1)服务器监听
服务器监听时服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
(2)客户端请求
客户端请求是指由客户端的套接字发出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后再向服务器端套接字提出连接请求。
(3)连接确认
连接确认是指当服务器端套接字监听到(或接收到)客户端套接字的连接请求时,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
.NET中的Socket编程
Socket类用于实现Berkeley套接字接口。
常用方法
(1)Bind(EndPoint address)
在服务器端,当一个套接字被创建后,需要将它绑定到系统的一个特定地址。可以使用Bind( )方法来完成,其参数为一个IPEndPoint实例(包含IP地址和端口信息)。
(2)Listen(int con_num)
服务器端的套接字完成了与地址的绑定后,就使用Listen( )方法监听客户发送的连接请求。其参数con_num为一整型值,该值表示服务器可以接受的最大连接数目。超过这个数目的连接都会被拒绝。con_num数值的设定会影响到服务器的运行,因为每个接受的连接都要使用TCP缓冲区,如果连接的数目过大,收发数据的缓存将减少。
(3)Accept( )
在服务器进入监听状态时,如有从客户端发来的连接请求,服务器将使用Accept( )方法来接受连接请求。Accept( )返回一个新的套接字,该套接字包含所建立的连接的信息并负责处理本连接的所有通信。而服务器刚开始创建的套接字仍然负责监听,并在需要时调用Accept( )接受新的连接请求。
(4)Send( )
当服务器接受了来自客户端的连接请求后,服务器和客户端双方就可以利用Send( )方法来发送数据。
(5)Receive( )
当服务器接受了来自客户端的连接请求后,服务器和客户端双方就可以利用Receive( )方法来接受数据。
(6)Connect(EndPoint remoteEP)
同服务器端一样,客户端的套接字建立后也必须与一个地址绑定。在客户端使用Connect( )方法实现绑定,remoteEP参数为所要连接的服务器端的IPEndPoint实例。调用Connect( )方法后,它将一直阻塞到连接建立,如果连接不成功,将返回一个异常。
一个简单的例子:
服务器端程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace Socket编程实例
{
class Program
{
static void Main(string[] args)
{
int port = 6060;
string host = "127.0.0.1";
IPEndPoint ipe = new IPEndPoint( IPAddress.Parse(host), port);//IPEndPoint实例
Socket service = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
service.Bind(ipe);
service.Listen(1);
Console.WriteLine("等待客户端连接");
Socket temp = service.Accept();//为新建连接创建新的socket
Console.WriteLine("建立连接");
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//从客户端接受信息
recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
Console.WriteLine("我是服务器:{0}", recvStr);//把客户端传来的信息显示出来
string sendStr = "当然在啊!";
byte[] bs = Encoding.UTF8.GetBytes(sendStr);
temp.Send(bs, bs.Length, 0);//返回信息给客户端
temp.Close();
service.Close();
Console.ReadKey();
}
}
}
客户端程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
namespace Socket编程实例客户端
{
class Program
{
static void Main(string[] args)
{
try
{
int port = 6060;
string host = "127.0.0.1";
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(host), port);//IPEndpoint实例
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建Socket
Console.WriteLine("正在连接...");
client.Connect(ipEndPoint);//连接到服务器
string sendStr = "你好,在吗?";
byte[] bs = Encoding.UTF8.GetBytes(sendStr);
Console.WriteLine("正在发送信息...");
client.Send(bs, bs.Length, 0);//发送信息
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = client.Receive(recvBytes, recvBytes.Length, 0);//从服务器端接受返回信息
//recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
Console.WriteLine("我是客户端:{0}", recvStr);//显示服务器返回信息
client.Close();
}
catch (ArgumentNullException ex)
{
Console.WriteLine("argumentNullException: {0}", ex);
}
catch (SocketException ex)
{
Console.WriteLine("SocketException:{0}", ex);
}
Console.WriteLine("请按任意键退出");
Console.ReadKey();
}
}
}
截图: