socket简述

来源:互联网 发布:电脑容易被入侵的端口 编辑:程序博客网 时间:2024/06/05 07:51

socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,象一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

现象解释

  socket非常类似于电话插座。以一个国家级电话网为例。电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。

电话系统

  在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的过程,话音传输的过程以及整个电话系统的技术细节对他都是透明的,这也与socket机制非常相似。socket利用网间网通信设施实现进程通信,但它对通信设施的细节毫不关心,只要通信设施能提供足够的通信能力,它就满足了。

  至此,我们对socket进行了直观的描述。抽象出来,socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。在网间网内部,每一个socket用一个半相关描述:

  (协议,本地地址,本地端口)

  一个完整的socket有一个本地唯一的socket号,由操作系统分配。

  最重要的是,socket 是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的socket系统调用。客户随机申请一个socket (相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个socket号;服务器拥有全局公认的 socket ,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。

  socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器socket 半相关为全局所公认非常重要。读者不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的socket 固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。

什么是socket

  所谓socket通常也称作"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。以J2SDK-1.3为例,Socket和ServerSocket类库位于java .net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。

重要的Socket API

  重要的Socket API:java .net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。

  Accept方法用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象实例。"阻塞"是一个术语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,然后程序继续;通常"阻塞"是由循环产生的。

  getInputStream方法获得网络连接输入,同时返回一个InputStream对象实例。

  getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。注意:其中getInputStream和getOutputStream方法均可能会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。

SOCKET连接过程

  根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

  服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

  客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

  连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

如何开发一个Server-Client模型的程序

  开发原理:

  服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

  客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

  Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

常用的Socket类型

  有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。Socket为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:int socket(int domain,int type,int protocol);domain指明所使用的协议族,通常为PF_INET,(其与addrinfo 里的 AF_INET在现在看来是相同的。只是历史上人们曾构想将AF(地址家族address family)与PF(protocol family 协议家族)分开,但实际上这种区分并未真正推广,所以现在AF_INET和PF_INET具有相同的意义。其中AF_INET是基于IPv4而PF_INET基于IPv6)表示互联网协议族(TCP/IP协议族);type参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值0。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。socket在测量软件中的使用也很广泛。

 

C#语言中的SOCKET

所谓Socket通常也称作“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求。根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

  服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

  客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

  连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

  Socket通信以其传输速度快且稳定的优点在程序开发中应用非常的广泛,下面的范例就简要的介绍了Socket在C#中应用。

  public class XmlSocket

  {

  //异步socket诊听

  // Incoming data from client.从客户端传来的数据

  public static string data = null;

  // Thread signal.线程 用一个指示是否将初始状态设置为终止的布尔值初始化 ManualResetEvent 类的新实例。

  public static ManualResetEvent allDone = new ManualResetEvent(false);

  //static void Main(string[] args)

  //{

  // StartListening();

  //}

  public static void StartListening()

  {

  // Data buffer for incoming data. 传入数据缓冲

  byte[] bytes = new Byte[1024];

  // Establish the local endpoint for the socket. 建立本地端口

  // The DNS name of the computer

  // running the listener is “*******”.

  IPAddress ipAddress;

  String ipString = ConfigurationManager.AppSettings.Get(“SocketIP”);

  if (ipString==null || ipString ==String.Empty) { IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());

  ipAddress = ipHostInfo.AddressList[0];

  }

  else

  {

  ipAddress = IPAddress.Parse(ipString);

  }

  int port;

  String portString = ConfigurationManager.AppSettings.Get(“SocketPort”);

  if (portString==null || portString==String.Empty)

  {

  port = 11001;

  }

  else

  {

  port = int.Parse(portString);

  }

  IPEndPoint localEndPoint = new IPEndPoint(ipAddress,port);

  // Create a TCP/IP socket.

  Socket listener = new Socket(AddressFamily.InterNetwork,

  SocketType.Stream,ProtocolType.Tcp);

  // Bind the socket to the local endpoint and listen for incoming connections.绑定端口和数据

  try

  {

  listener.Bind(localEndPoint);

  listener.Listen(100);

  while (true)

  {

  // Set the event to nonsignaled state.设置无信号状态的事件

  allDone.Reset();

  // Start an asynchronous socket to listen for connections.重新 启动异步连接

  listener.BeginAccept(new AsyncCallback(AcceptCallback),listener);

  // Wait until a connection is made before continuing.等待连接创建后继续

  allDone.WaitOne();

  }

  }

  catch (Exception e)

  {

  //

  }

  }

  public static void AcceptCallback(IAsyncResult ar)

  {

  try

  {

  // Signal the main thread to continue.接受回调方法 该方法的此节向主应用程序线程发出信号,

  //让它继续处理并建立与客户端的连接

  allDone.Set();

  // Get the socket that handles the client request.获取客户端请求句柄

  Socket listener = (Socket)ar.AsyncState;

  Socket handler = listener.EndAccept(ar);

  // Create the state object.

  StateObject state = new StateObject();

  state.workSocket = handler;

  handler.BeginReceive(state.buffer,0,StateObject.BufferSize,0,

  new AsyncCallback(ReadCallback),state);

  }

  catch (Exception e)

  {

  //

  }

  }

  /// <summary>

  /// 与接受回调方法一样,读取回调方法也是一个 AsyncCallback 委托。

  /// 该方法将来自客户端套接字的一个或多个字节读入数据缓冲区,然后再次调用 BeginReceive 方法,直到客户端发送的数据完成为止。

  /// 从客户端读取整个消息后,在控制台上显示字符串,并关闭处理与客户端的连接的服务器套接字。

  /// </summary>

  /// <param name=”ar”>IAsyncResult 委托</param>

  public static void ReadCallback(IAsyncResult ar)

  {

  try

  {

  String content = String.Empty;

  // Retrieve the state object and the handler socket创建自定义的状态对象 from the asynchronous state object.

  StateObject state = (StateObject)ar.AsyncState;

  Socket handler = state.workSocket;//处理的句柄

  // Read data from the client socket. 读出

  int bytesRead = handler.EndReceive(ar);

  if (bytesRead > 0)

  {

  //业务代码

  String result = DoSomeThing(…);

  String len = Encoding.UTF8.GetBytes(result).Length.ToString().PadLeft(8,’0′);

  log.writeLine(len);

  Send(len + result,handler);

  }

  }

  catch (Exception e)

  {

  //

  }

  }

  private static void Send(String data,Socket handler)

  {

  try

  {

  // Convert the string data to byte data using UTF8 encoding.

  byte[] byteData = Encoding.UTF8.GetBytes(data);

  // Begin sending the data to the remote device.

  handler.BeginSend(byteData,0,byteData.Length,0,

  new AsyncCallback(SendCallback),handler);

  }

  catch (Exception e)

  {

  //

  }

  }

  /// <summary>

  /// 发送

  /// </summary>

  /// <param name=”ar”></param>

  private static void SendCallback(IAsyncResult ar)

  {

  try

  {

  // Retrieve the socket from the state object.

  Socket handler = (Socket)ar.AsyncState;

  // Complete sending the data to the remote device.向远端发送数据

  int bytesSent = handler.EndSend(ar);

  StateObject state = new StateObject();

  state.workSocket = handler;

  handler.BeginReceive(state.buffer,0,StateObject.BufferSize,0,new AsyncCallback(ReadCallback),state);

  handler.Shutdown(SocketShutdown.Both);

  handler.Close();

  }

  catch (Exception e)

  {

  //

  }

  }

  public static void StopListening()

  {

  allDone.Close();

  log.close();

  }

  /// <summary>

  /// 具体处理业务的方法

  /// </summary>

  /// <returns></returns>

  private static string DoSomething(int i)

  {

  //具体业务代码,返回需要返回的字符串信息

  }

  /// <summary>

  /// 写日志方法

  /// </summary>

  /// <param name=”strLog”>;写入内容</param>

  public static void WriteLog(string strLog)

  {

  //写入日志代码

  }

  }

  /// 线程执行体,转发消息

  /// </summary>

  /// <param name=”obj”>;传递给线程执行体的用户名,用以与用户通信 </param>

  private void ThreadFunc(object obj)

  {

  //通过转发表得到当前用户套接字

  Socket clientSkt = _transmit_tb[obj] as Socket;

  //主循环

  while (true)

  {

  try

  { //接受第一个数据包。

  //由于程序逻辑结构简单,所以在这里对客户机发送的第一个包内容作逐一判断,

  //这里的实现不够优雅,但不失为此简单模型的一个解决之道。

  byte[] packetBuff = new byte[_maxPacket];

  clientSkt.Receive(packetBuff);

  string _str = Encoding.Unicode.GetString(packetBuff).TrimEnd(‘\0′);

  //如果是发给不在线好友的信息

  if (_str.StartsWith(“cmd::FriendMessage”))

  {

  string UserName = _str.Substring(“cmd::FriendMessage”.Length,20).Trim();

  string MessageS = _str.Substring(“cmd::FriendMessage”.Length + 20,_str.Length - ”cmd::FriendMessage”.Length - 20);

  SaveMessage(obj as string,UserName,MessageS);

  continue;

  }

  //如果是离线请求

  if (_str.StartsWith(“cmd::RequestLogout”))

  {

  _transmit_tb.Remove(obj);

  UpdateFriendList((string)obj,false,”");

  // string svrlog = string.Format(“[系统消息]用户 {0} 在 {1} 已断开… 当前在线人数: {2}\r\n\r\n”,obj,DateTime.Now,_transmit_tb.Count);

  // Console.WriteLine(svrlog);

  //向所有客户机发送系统消息

  //foreach (DictionaryEntry de in _transmit_tb)

  //{

  // string _clientName = de.Key as string;

  // Socket _clientSkt = de.Value as Socket;

  // _clientSkt.Send(Encoding.Unicode.GetBytes(svrlog));

  //}

  Thread.CurrentThread.Abort();

  }

  //如果是请求好友列表

  if (_str.StartsWith(“cmd::RequestFriendList”))

  {

  SerializeFriendList(obj,clientSkt);

  // 将该用户不在线时的信息发送给用户

  DataTable TabMessage = ReadMessage(obj as string);

  if (TabMessage != null)

  {

  foreach (DataRow myrow in TabMessage.Rows)

  {

  if (myrow["SendUserName"].ToString() == ”System::Message”)

  {

  clientSkt.Send(Encoding.Unicode.GetBytes(myrow["Message"].ToString()));

  }

  else

  {

  clientSkt.Send(Encoding.Unicode.GetBytes(“cmd::FriendMessage” +myrow["SendUserName"].ToString().PadRight(20,’ ’) + myrow["Message"].ToString()));

  }

  }

  } //这里不需要再继续接受后继数据包了,跳出当前循环体。

  continue;

  }

  ////如果是请求好友列表

  //if (_str.StartsWith(“cmd::RequestOnLineList”))

  //{

  // byte[] onlineBuff = SerializeOnlineList();

  // //先发送响应信号,用户客户机的判断

  // clientSkt.Send(Encoding.Unicode.GetBytes(“cmd::RequestOnLineList”));

  // clientSkt.Send(onlineBuff);

  // //这里不需要再继续接受后继数据包了,跳出当前循环体。

  // continue;

  //} //查找用户

  if (_str.StartsWith(“Find::FindFriend”))

  {

  DataTable TabFind = TabUser.Clone();

  DataRow [] FindRow =null ;

  string UserName = _str.Substring(“Find::FindFriend”.Length,_str.Length - ”Find::FindFriend”.Length);

  if (UserName.Equals(“Find::WhoOnLine”))

  { //看谁在线

  FindRow = TabUser.Select(“ ZX = 1″);

  }

  else//精确查找

  {

  FindRow = TabUser.Select(“UserName = ‘” + UserName + ”‘”);

  }

  foreach (DataRow myrow in FindRow)

  {

  TabFind.ImportRow(myrow);

  }

  clientSkt.Send(Encoding.Unicode.GetBytes(“Find::FindFriend”));

  IFormatter format = new BinaryFormatter();

  MemoryStream stream = new MemoryStream();

  format.Serialize(stream,TabFind);

  stream.Position = 0;

  byte[] ret = new byte[_maxPacket];

  int count = 0;

  count = stream.Read(ret,0,_maxPacket);

  while (count >0)

  {

  clientSkt.Send(ret);

  count = stream.Read(ret,0,_maxPacket);

  }

  clientSkt.Send(Encoding.Unicode.GetBytes(“Find::FindFriendEnd”));

  stream.Close();

  TabFind = null;

  FindRow = null; //这里不需要再继续接受后继数据包了,跳出当前循环体。

  continue;

  } //请求添加好友

  if (_str.StartsWith(“Find::AddFriendAsk”))

  {

  string UserName = _str.Substring(“Find::AddFriendAsk”.Length,_str.Length - ”Find::AddFriendAsk”.Length);

  //通过转发表查找接收方的套接字

  if (_transmit_tb.Count != 0 && _transmit_tb.ContainsKey(UserName))

  {

  Socket receiverSkt = _transmit_tb[UserName] as Socket;

  receiverSkt.Send(Encoding.Unicode.GetBytes(“Find::AddFriendAsk” + obj as string));

  }

  //这里不需要再继续接受后继数据包了,跳出当前循环体。

  continue;

  }

  //回复答应添加好友

  if (_str.StartsWith(“Find::AddFriendYes”))

  {

  string UserName = _str.Substring(“Find::AddFriendYes”.Length,_str.Length - ”Find::AddFriendYes”.Length);

  //// 保存数据

  DataTable TabmyFriend = new DataTable() ;

  //保存该用户

  TabmyFriend.ReadXml(MyPath + ”\\UserFriend\\” + obj as string + ”.xml”);

  DataRow newRow = TabmyFriend.NewRow();

  newRow["UserName"] = UserName;

  TabmyFriend.Rows.Add(newRow);

  TabmyFriend.WriteXml(MyPath + ”\\UserFriend\\” + obj as string + ”.xml”,XmlWriteMode.WriteSchema,false);

  //保存其好友

  TabmyFriend = new DataTable();

  TabmyFriend.ReadXml(MyPath + ”\\UserFriend\\” + UserName + ”.xml”);

  DataRow newRow1 = TabmyFriend.NewRow();

  newRow1["UserName"] = obj as string;

  TabmyFriend.Rows.Add(newRow1);

  TabmyFriend.WriteXml(MyPath + ”\\UserFriend\\” + UserName + ”.xml”,XmlWriteMode.WriteSchema,false);

  TabmyFriend = null;

  SerializeFriendList(obj,clientSkt);

  //”开始”按钮事件

  private void button1_Click(object sender,System.EventArgs e) {

  //取得预保存的文件名

  string fileName=textBox3.Text.Trim();

  //远程主机

  string hostName=textBox1.Text.Trim();

  //端口

  int port=Int32.Parse(textBox2.Text.Trim());

  //得到主机信息

  IPHostEntry ipInfo=Dns.GetHostByName(hostName);

  //取得IPAddress[]

  IPAddress[] ipAddr=ipInfo.AddressList;

  //得到ip

  IPAddress ip=ipAddr[0];

  //组合出远程终结点

  IPEndPoint hostEP=new IPEndPoint(ip,port);

  //创建Socket 实例

  Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

  try

  {

  //尝试连接

  socket.Connect(hostEP);

  }

  catch(Exception se)

  {

  MessageBox.Show(“连接错误”+se.Message,”提示信息

  ,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);

  }

  //发送给远程主机的请求内容串

  string sendStr=”GET / HTTP/1.1\r\nHost: ” + hostName +

  “\r\nConnection: Close\r\n\r\n”;

  //创建bytes字节数组以转换发送串

  byte[] bytesSendStr=new byte[1024];

  //将发送内容字符串转换成字节byte数组

  bytesSendStr=Encoding.ASCII.GetBytes(sendStr);

  try

  {

  //向主机发送请求

  socket.Send(bytesSendStr,bytesSendStr.Length,0);

  }

  catch(Exception ce)

  {

  MessageBox.Show(“发送错误:”+ce.Message,”提示信息

  ,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);

  }

  //声明接收返回内容的字符串

  string recvStr=”";

  //声明字节数组,一次接收数据的长度为1024字节

  byte[] recvBytes=new byte[1024];

  //返回实际接收内容的字节数

  int bytes=0;

  //循环读取,直到接收完所有数据

  while(true)

  {

  bytes=socket.Receive(recvBytes,recvBytes.Length,0);

  //读取完成后退出循环

  if(bytes〈=0)

  break;

  //将读取的字节数转换为字符串

  recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes);

  }

  //将所读取的字符串转换为字节数组

  byte[] content=Encoding.ASCII.GetBytes(recvStr);

  try

  {

  //创建文件流对象实例

  FileStream fs=new FileStream(fileName,FileMode.OpenOrCreate,FileAccess.ReadWrite);

  //写入文件

  fs.Write(content,0,content.Length);

  }

  catch(Exception fe)

  {

  MessageBox.Show(“文件创建/写入错误:”+fe.Message,”提示信息”,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information);

  }

  //禁用Socket

  socket.Shutdown(SocketShutdown.Both);

  //关闭Socket

  socket.Close();

  }

  }