阻塞、非阻塞;同步、异步之间的区别

本文详细介绍了如何使用 C# 中的 Socket 进行服务器端的监听、接收及响应消息等操作,并解释了五种 I/O 模型,包括阻塞 I/O、非阻塞 I/O、I/O 复用、信号驱动 I/O 和异步 I/O 的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C#中使用Socket向服务器端发送并接收回应消息的代码

[STAThread]
  static void Main()
  {
   Application.Run(new Form1());
  }

  private void StartListening()
  {   
   TcpListener listener = new TcpListener(listenport); 
   listener.Start();
   label1.Text = "listening....";
   while (true)
   {
    try
    {
   
     Socket s = listener.AcceptSocket();
     clientsocket = s;
     clientservice = new Thread(new ThreadStart(ServiceClient));
     clientservice.Start();
    }
    catch(Exception ex)
    {
     MessageBox.Show("listening Error: "+ex.Message);
    }
   }  
  }
  private void ServiceClient()
  {
   Socket client = clientsocket;
   bool keepalive = true;


   while (keepalive)
   {
    Byte[] buffer = new Byte[1024];
    int bufLen = 0;
    try
    {
     bufLen = client.Available ;
    
     client.Receive(buffer,0,bufLen,SocketFlags.None);
     if(bufLen==0)
      continue;   
    }
    catch(Exception ex)
    {
     MessageBox.Show("Receive Error:"+ex.Message);
     return;
    }
   
    string clientcommand = System.Text.Encoding.ASCII.GetString(buffer).Substring(0,bufLen);

    string[] tokens = clientcommand.Split(new Char[]{'|'});
    Console.WriteLine(clientcommand);

    if (tokens[0] == "CONN")
    {
     for(int n=0; n<clients.Count;n++)
     {
      Client cl = (Client)clients[n];
      SendToClient(cl, "JOIN|" + tokens[1]);
     }
     EndPoint ep = client.RemoteEndPoint;
     Client c = new Client(tokens[1], ep, clientservice, client);
    
     string message = "LIST|" + GetChatterList() +"/r/n";
     SendToClient(c, message);

     clients.Add(c);


     lbClients.Items.Add(c);


    }
    if (tokens[0] == "CHAT")
    {
     for(int n=0; n<clients.Count;n++)
     {
      Client cl = (Client)clients[n];
      SendToClient(cl, clientcommand);
     }
    }
    if (tokens[0] == "PRIV")
    {
     string destclient = tokens[3];
     for(int n=0; n<clients.Count;n++)
     {
      Client cl = (Client)clients[n];
      if(cl.Name.CompareTo(tokens[3]) == 0)
       SendToClient(cl, clientcommand);
      if(cl.Name.CompareTo(tokens[1]) == 0)
       SendToClient(cl, clientcommand);
     }
    }
    if (tokens[0] == "GONE")
    {
     int remove = 0;
     bool found = false;
     int c = clients.Count;
     for(int n=0; n<clients.Count;n++)
     {
      Client cl = (Client)clients[n];
      SendToClient(cl, clientcommand);
      if(cl.Name.CompareTo(tokens[1]) == 0)
      {
       remove = n;
       found = true;
       lbClients.Items.Remove(cl);
      }
     }
     if(found)
      clients.RemoveAt(remove);
     client.Close();
     keepalive = false;
    }
   }
  }

  private string GetChatterList()
  {
   string result = "";

   for(int i=0;i<clients.Count;i++)
   {
    result += ((Client)clients[i]).Name+"|";
   }
   return result;

  }

  private void SendToClient(Client cl,string clientCommand)
  {
   Byte[] message = System.Text.Encoding.ASCII.GetBytes(clientCommand);
   Socket s = cl.Sock;
   if(s.Connected)
   {
    s.Send(message,message.Length,0);
   }
  }

  private void Form1_Load(object sender, System.EventArgs e)
  {
   clients = new ArrayList();
  }

  private void button1_Click(object sender, System.EventArgs e)
  {
   threadListen = new Thread(new ThreadStart(StartListening));
   threadListen.Start(); 
  }

private void ReceiveChat()
  {
   bool keepalive = true;
   while (keepalive)
   {
    try
    {
     Byte[] buffer = new Byte[1024];  // 2048???
     ns.Read(buffer,0,buffer.Length);
     string chatter = System.Text.Encoding.ASCII.GetString(buffer);
     string[] tokens = chatter.Split(new Char[]{'|'});

     if (tokens[0] == "CHAT")
     {
      rtbChatIn.AppendText(tokens[1]);
      //      if(logging)
      //       logwriter.WriteLine(tokens[1]);
     }
     if (tokens[0] == "PRIV")
     {
      rtbChatIn.AppendText("Private from ");
      rtbChatIn.AppendText(tokens[1].Trim() );
      rtbChatIn.AppendText(tokens[2] + "/r/n");
      //      if(logging)
      //      {
      //       logwriter.Write("Private from ");
      //       logwriter.Write(tokens[1].Trim() );
      //       logwriter.WriteLine(tokens[2] + "/r/n");
      //      }
     }
     if (tokens[0] == "JOIN")
     {
      rtbChatIn.AppendText(tokens[1].Trim() );
      rtbChatIn.AppendText(" has joined the Chat/r/n");
      //      if(logging)
      //      {
      //       logwriter.WriteLine(tokens[1]+" has joined the Chat");
      //      }
      string newguy = tokens[1].Trim(new char[]{'/r','/n'});
      lbChatters.Items.Add(newguy);
     }
     if (tokens[0] == "GONE")
     {
      rtbChatIn.AppendText(tokens[1].Trim() );
      rtbChatIn.AppendText(" has left the Chat/r/n");
      //      if(logging)
      //      {
      //       logwriter.WriteLine(tokens[1]+" has left the Chat");
      //      }
      lbChatters.Items.Remove(tokens[1].Trim(new char[]{'/r','/n'}));
     }
     if (tokens[0] == "QUIT")
     {
      ns.Close();
      clientsocket.Close();
      keepalive = false;
      statusBar1.Text = "服务器端已停止";
      connected= false;
      btnSend.Enabled = false;
      btnDisconnect.Enabled = false;
     }
    }
    catch(Exception){}
   }
  }

  private void QuitChat()
  {
   if(connected)
   {
    try
    {
     string command = "GONE|" + clientname;
     Byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(command.ToCharArray());
     ns.Write(outbytes,0,outbytes.Length);
     clientsocket.Close();
    }
    catch(Exception ex)
    {
     MessageBox.Show(ex.Message);
    }
   }
   //   if(logging)
   //    logwriter.Close();
   if(receive != null && receive.IsAlive)
    receive.Abort();
   this.Text = "客户端";
  
   connected = false;

  }

  private void btnSend_Click(object sender, System.EventArgs e)
  {
   if(connected)
   {
    try
    {
     string command = "CHAT|" + clientname+": "+ChatOut.Text+"/r/n";
     Byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(command.ToCharArray());
     ns.Write(outbytes,0,outbytes.Length);
     //clientsocket.Close();
    }
    catch(Exception ex)
    {
     MessageBox.Show(ex.Message);
    }
   }
  }

  private void btnConnect_Click(object sender, System.EventArgs e)
  {
   EstablishConnection();
   RegisterWithServer();
   if(connected)
   {  
    receive = new Thread(new ThreadStart(ReceiveChat));
    receive.Start();
   }
  }

  private void btnDisconnect_Click(object sender, System.EventArgs e)
  {
   QuitChat();
  }

 

五个I/O模型  
   
  阻塞I/O  
  非阻塞I/O  
  I/O复用(select和poll)  
  信号驱动I/O(SIGIO)  
  异步I/O  
   
   
  阻塞   I/O模型  
   
  进程调用recvfrom,此系统调用直到数据报到达且拷贝到应用缓冲区或是出错才返回。最常见的错误是系统调用被信号中断,进程阻塞的整段时间是指从调用recvfrom开始到它返回的这段时间,当进程返回成功指示时,应用进程开始处理数据报。  
   
  非阻塞方式    
   
  当请求的I/O操作不能完成时,不让进程睡眠,而应返回一个错误。   前三次调用recvfrom时仍无数据返回,因此内核立即返回一个错误。第四次调用recvfrom时,数据报已准备好,被拷贝到应用缓冲区,   recvfrom返回成功指示,接着处理数据。   此过程称为轮询(polling)。这对CPU时间是极大的浪费。  
   
  I/O复用模型    
   
  调用select或poll,在这两个系统调用中的某一个上阻塞,而不是阻塞于真正I/O系统调用。   阻塞于select调用,等待数据报套接口可读。当select返回套接口可读条件时,调用recevfrom将数据报拷贝到应用缓冲区中。  
   
  信号驱动I/O模型    
   
  套接口启动信号驱动I/O,   并通过系统调用sigaction安装一个信号处理程序。此系统调用立即返回,进程继续工作,它是非阻塞的。  
  当数据报准备好被读时,就为该进程生成一个SIGIO信号。  
  随即可以在信号处理程序中调用recvfrom来读数据报,井通知主循环数据已准备好被处理中。也可以通知主循环,让它来读数据报。  
   
  异步I/O模型  
   
  让内核启动操作,并在整个操作完成后(包括将数据从内核拷贝到用户自己的缓冲区)通知用户。  
  信号驱动I/O:由内核通知我们何时可以启动一个I/O操作,  
  异步I/O模型:由内核通知我们I/O操作何时完成。  
   
   
  --------------------------------------------------------------------------------  
   
  select   函数  
  允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程。  
  作为一个例子,我们可以调用函数select并通知内核仅在下列情况发生时才返回  
  集合{1,4,5}中的任何报述字准备好读  
  集合{2,7}的任何描述字准备好写  
  集合{1,4}中的任何描述字有异常条件待处理  
  已经过了10.2秒  
  通知内核我们对哪些描述字感兴趣(读、写或异常条件)以及等待多长时间。  
   
  描述字不受限于套接口:任何描述字(例如文件描述字)都可用select来测试。  
   
  select   定义  
   
  int   select(int   maxfdp1,fd_set   *readset,fd_set   *writeset,fd_set   *exceptset,const   struct   timeval   *timeout);  
   
  maxfdp1   :   描述字最大值  
  readset   :   读描述字集  
  writeset   :   写描述字集  
  exceptset   :   异常条件的描述字集  
  timeout   :   等待时间  
   
  readset,   writeset和exceptset  
   
  让内核测试读、写和异常条件所需的描述字。  
   
  为这三个参数的每一个指定一个或多个描述字值  
   
  描述字集,是一个整数数组,每个数中的每一位对应一个描述字。  
   
  数组的第一个元素对应于描述字0-31,  
   
  数组户的第二个元素对应于描述字32—63。  
   
  例子:  
   
  fd_set   rset;   //定义描述字集数据类型  
   
  FD_ZERO   (&rset);   //对描述字集初始化  
   
  FD_SET(1,   &rset);   //打开描述字的第1位  
   
  FD_SET(4,   &rset)   //   //打开描述字的第4位  
   
  ......  
   
  FD_ISSET(4,   &rest)   //测试描述字的第4位  
   
  FD_CLR(4,   &rset)   //   //关闭描述字的第4位  
   
  readset   套接口准备好读  
   
  套接口接收缓冲区中的数据字节数>=  
   
  套接口接收缓冲区低潮限度的当前值  
   
  连接的读这一半关闭(接收了FIN的TCP连接)  
   
  套接口是一个监听套接口旦已完成的连接数为非0。  
   
  有一个套接口错误待处理。  
   
  writeset   套接口准备好写  
   
  套接口发送缓冲区中的可用空间字节数大干等于套接口发送缓冲区低潮限度的  
   
  当前值,且或者(i)套接口已连接,或者(i)套接口不要求连接  
   
  连接的写这一半关闭。对这样的套接口的写操作将产生信SIGPIPE。  
   
  有一个套接口错误待处理。对这样的套接口的写操作将不阻塞且返回一个错误(一1)  
   
  exceptset异常条件待处理  
   
  如果一个套接口存在带外数据或者仍处于带外标记,那它有异常条件待处理。  
   
  带外数据(out—of—band   data),有时也称为加速数据(expedited   data),  
   
  是指连接双方中的一方发生重要事情,想要迅速地通知对方。  
   
  这种通知在已经排队等待发送的任何“普通”(有时称为“带内”)数据之前发送。  
   
  带外数据设计为比普通数据有更高的优先级。  
   
  带外数据是映射到现有的连接中的,而不是在客户机和服务器间再用一个连接。  
   
  最大描述字   maxfdp1  
   
  当select刚开始设计时,操作系统常对每个进程可用的最大描述字数上限作出  
   
  限制(4.2BSD的限制为31),select也就用相同的限制值。  
   
  unix版本对每个进程的描述字数根本不作限制   (仅受限于内存量和管理性限制),  
   
  #include   <sys/types.h>  
   
  #DEFINE   FD_SETSIZE   256  
   
   
   
  --------------------------------------------------------------------------------  
   
  str_cli   函数的修订版  
  服务器进程一终止客户就能马上得到通知  
   
  早期版本的问题就在于当套接口上发生了某些事件时,客户可能阻塞于fgets调用,  
   
  新版本则阻塞于select调用:等待标准输入,等待套接口可读。  
   
   
  对套接口的处理  
   
  对方TCP发送数据,套接口就变为可读且read返回大于0  
   
  对方TCP发送一个FIN(对方进程终止),套接口就变为可读且read返回0(文件结束)。  
   
  对方TCP发送一个RST   (对方主机崩溃并重新启动),套接口变为可读且返回-1  
   
   
  #include   "unp.h"  
  void   str_cli(FILE   *fp,   int   sockfd)  
  {   int   maxfdp1;   //最大描述字  
  fd_set   rset;   //描述字集    
  char   sendline[MAXLINE],   recvline[MAXLINE];  
  FD_ZERO(&rset);   //描述字集清零(空集)  
  for   (   ;   ;   )   {  
  FD_SET(fileno(fp),   &rset);   //打开文件描述字的测试  
  FD_SET(sockfd,   &rset);   //   //打开套接口描述字的测试  
  maxfdp1   =   max(fileno(fp),   sockfd)   +   1;   //获得最大描述字  
  Select(maxfdp1,   &rset,   NULL,   NULL,   NULL);   //对是否可读进行测试  
  if   (FD_ISSET(sockfd,   &rset))   {   //如果套接口可读    
  if   (Readline(sockfd,   recvline,   MAXLINE)   =   =   0)   //读入一行  
  err_quit(“str_cli:   server   termi.   premat..”);   //对方终止时退出  
  Fputs(recvline,   stdout);   //写到标准输出  
  }  
  if   (FD_ISSET(fileno(fp),   &rset))   {   //如果标准输入可读    
  if   (Fgets(sendline,   MAXLINE,   fp)   ==   NULL)   //读入一行  
  return;   //   遇到^D时退出子程序    
  Writen(sockfd,   sendline,   strlen(sendline));   //写入套接口  
  }    
  }  
  }    
   
   
  --------------------------------------------------------------------------------  
   
   
  shutdown   函数  
  close有两个限制可由函数shutdown来避免:  
   
  close将描述字的访问计数减1,仅在此计数为0时才关闭套接口  
  shutdown可激发TCP的正常连接终止序列,   而不管访问计数。  
   
  close终止了数据传送的两个方向:读和写。  
  shutdown终止的数据传送的两个方向:读和写,   或其中任一方向:读或写  
   
  定义:  
  int   shutdown(   int   sockfd,   int   howto)   ;  
   
  howto选项:  
  SHUT_RD   关闭连接的读一半  
  SHUT_WR   关闭连接的写这一半  
  SHUT_RDWR   关闭连接读读和写  
   
   
  --------------------------------------------------------------------------------  
   
  str_cli函数(再修订)  
  void   str_cli(FILE   *fp,   int   sockfd)  
  {   int   maxfdp1,   stdineof   ;   fd_set   rset;    
  char   sendline[MAXLINE],   recvline[MAXLINE];    
  stdineof=0;   FD_ZERO(&rset);  
  for   (   ;   ;   )   {    
  if   (stdineof   ==   0)   FD_SET(fileno(fp),   &rset);  
  FD_SET(sockfd,   &rset);  
  maxfdp1   =   max(fileno(fp),   sockfd)   +   1;  
  Select(maxfdp1,   &rset,   NULL,   NULL,   NULL);  
  if   (FD_ISSET(sockfd,   &rset))   {   /*   socket   is   readable   */  
  if   (Readline(sockfd,   recvline,   MAXLINE)   ==   0)   {  
  if   (stdineof   ==   1)   return;   /*   normal   termination   */  
  else   err_quit(“str_cli:   server   ERR.   ");   }  
  Fputs(recvline,   stdout);  
  }  
  if   (FD_ISSET(fileno(fp),   &rset))   {   /*   input   is   readable   */  
  if   (Fgets(sendline,   MAXLINE,   fp)   ==   NULL)   {  
  stdineof   =   1;  
  Shutdown(sockfd,   SHUT_WR);   /*   send   FIN   */  
  FD_CLR(fileno(fp),   &rset);  
  continue;   }  
  Writen(sockfd,   sendline,   strlen(sendline));  
  }   }   }  
   
   
   
  --------------------------------------------------------------------------------  
   
   
  TCP回射服务器程序(修订版)  
  使用select来处理任意数目的客户的单进程程序  
   
  不为每个客户派生一个子进程,避免了创建一个新进程的所有开销。  
   
  监听套接口  
   
  服务器只维护一个读描述字集  
   
  描述字0、1和2分别被设置为标准输入、标准输出和标准错误输出  
   
  监听套接口的第一个可用的描述字是3。  
   
  与第一个客户建立连接  
   
  监听描述字变为可读,于是服务器调用accept。  
   
  由accept返回的新的已连接描述字将是4。  
   
  第一个客户终止与服务器的连接  
   
  客户TCP发送一个FIN,这使得服务器中的描述字4变为可读。  
   
  当服务器读此已连接套接口时,readline返回0。  
   
  关闭此套接口并相应地更新数据结构,数组元素client[0]]的值置为一1,  
   
  描述字集中的描述字4被置为0,maxfd的值没有改变。   
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值