科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件Unix和Windows跨系统通讯编程

Unix和Windows跨系统通讯编程

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文介绍了套接字(Socket)的基本概念及编程技术,并结合实例说明在Unix和Windows下如何用套接字实现客户/服务器方式的通讯编程...

作者:张勤 李华春 来源:天极论坛 2007年10月22日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
四、 跨系统通讯编程实例

  下面通过一个实例来具体说明 Socket 在Unix 和Windows 跨系统通讯编程中的应用。

  Internet 上可以提供一种叫 IRC(Internet Relay Chatting,Internet 在线聊天系统)的服务。使用者通过客户端的程序登录到 IRC 服务器上,就可以与登录在同一 IRC 服务器上的客户进行交谈,这也就是平常所说的聊天室。在这里,给出了一个在运行 TCP/IP 协议的网络上实现 IRC 服务的程序。其中,服务器运行在 SCO Open Server 5.0.5上,客户端运行在Windows98 或 Windows NT 上。

  在一台计算机上运行服务端程序,同一网络的其他计算机上运行客户端程序,登录到服务器上,各个客户之间就可以聊天了。

  1.服务端

  服务端用名为 client 的整型数组记录每个客户的已连接套接口描述字,此数组中的所有元素都初始化为-1。同时服务器既要处理监听套接口,又要处理所有已连接套接口,因此需要用到 I/O 复用。通过 select 函数内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定时间后才唤醒进程。维护一个读描述字集 rset ,当有客户到达时,在数组 client中的第一个可用条目(即值为-1的第一个条目)记录其已连接套接口的描述字。同时把这个已连接描述字加到读描述字集rset 中,变量maxi 是当前使用的数组 client 的最大下标,而变量 maxfd+1 是函数 select第一个参数的当前值。如下图:(假设有两个客户与服务器建立连接)

图3 第二客户建立连接后的TCP服务器

图4 第二客户建立连接后的数据结构

  服务器在指定的端口上监听,每当一个套接口接收到信息,都将会把接收到的信息发送给每一个Client 。其主要源程序如下:

  int main(int argc,char **argv)
  {
  int   i,maxi,maxfd,listenfd,connfd,sockfd;
  int   nready,client;
  ssize_t n;
  fd_set rset,allset;
  char line[MAXLNE];
  socklen_t clilen;
  struct sockaddr_in cliaddr,servaddr;
  const int on=1;
  listenfd=Socket(AF_INET,SOCK_STREAM,0);
  if(setsockopt (listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))=-1);    err_ret(″setsockopt error″);
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family =AF_INET;
  servaddr.sin_addr.s_addr =htonl(INADDR_ANY);
  servaddr.sin_port =htons(SERV_PORT);
  Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
  Listen(listenfd,LISTENQ);
  maxfd =listenfd; //初始化
  maxi = -1; //最大下标
  for(i=0;i<FD_SETSIZE;i++)
    client[i]=-1; //-1表示可用
  FD_ZERO(&allset);
  FD_SET(listenfd,&allset);
  for(;;){
   rset=allset; //结构赋值
    nready=Select(maxfd+1,&rset,NULL,NULL,NULL);
   if (FD_ISSET(listenfd,&rset)){ //有用户连接
    clilen=sizeof(cliaddr);
    connfd=Accept(listenfd,(SA*)&cliaddr,&clilen);
    for(i=0;i<FD_SETSIZE;i++)
      if(client[i]< 0 {
       client[i]=connfd;//保存套接字
       break;
      }
    if(i=FD_SETSIZE);
     err_quit(″too many clients″);
    FD_SET (connfd,&allset); //增加套接字到读描述字集
    if(connfd>maxfd);
      maxfd=connfd;
    if(i>maxi);
      maxi=i;
    if(--nready<=0)
     continue;
  }
  for (i=0;i<=maxi;i++){ //检查所有用户连接
    if((sockfd=client[i])<0)
     continue;
    if(FD_ISSET(sockfd,&rset))
{
     if((n=Readline(sockfd,line,MAXLINE))=0)
{               //用户关闭连接
       Close(sockfd);
       FD_CLR(sockfd,&allset);
       client[i]=-1
      }else
        printf(″%s″,line);
       Broadcast(client,maxi,line,n);
       if (--nready<=0)
         break;
        }
      }
    }
  }
  //将聊天内容发送到所有已连接的用户
  Broadcast(int client,int maxi,char*str,size_t n)
  {
      int i;
      int sockfd;
      for(i=0;i<maxi,i++){
       if((sockfd=client[i])<0)
       continue;
      writen(sockfd,str,n);
    }
  }

  2.客户端

  为了实现非阻塞通信,利用异步选择函数 WSAAsynSelect() 将网络事件与 WinSock 消息联系起来,由该函数注册一些用户感兴趣的网络事件(如接收缓冲区满,允许发送数据,请求连接等)。当这些被注册的网络事件发生时,应用程序的相应函数将接收到有关消息。

  应用程序在使用Windows Sockets DLL 之前必须先调用启动函数WSAStartup() ,该函数的功能有两点:一是由应用程序指定所要求Windows Sockets DLL 版本;二是获得系统 Windows Sockets DLL的一些技术细节。每一个WSAStartup()函数必须和一个WSACleanup()函数对应,当应用程序终止时,必须调用 WSACleanup()将自己从OLL 中注销。

  客户端程序用 VC++6.0在Windows98 操作系统下设计,程序主框架由 AppWizard 生成,客户端核心代码在 CTalkDialog类中。只有一个 socket 变量m_hSocket 与服务端进行连接。连接建立好后,通过此 SOCKET发送和接收信息。

  手工加入CTalkDialog::OnSockConnect() ,完成基本套接字编程,为客户程序申请一个套接字,并将该套接字与指定服务器绑定,然后向服务器发出连接请求,启动异步选择函数等待服务器的响应。

  void CTalkDialog:″OnSockConnect()
{
   struct sockaddr_in servaddr;
   WSADATA wsaData;
   if(WSAStartup(WINSOCK_VERSION,&wsaData));
   {
    MessageBox(″Could not load Windows Sockets DLL,″,NULL,MB_Ok);
    return;
  }
m_hSocket=socket(AF_INET,SOCK_STREAM,0);
   memset(&servaddr,0,sizeof(servaddr));
   servaddr.sin_family=AF_INET;
   //m_iPort,m_csIP 为通过注册对话框返回的端口号和IP地址
  servaddr.sin_port=htons(m_iport);
  servaddr.sin_addr.S_un.S_addr=inet_addr(m_csIP);
  if (connect(m_hSocket,(SA*)& servaddr,sizeof (servaddr))!=0){
     AfxMessageBox(″连接服务器失败!″);
   GetDlgItem(IDC_BUTTON_OUT)->EnableWindow(FALSE);
     GetDlgItem(IDC_BUTTON_IN)->EnableWindowTRUE
     GetDlgItem(IDC_EDIT-SEND)->EnableWindow(FALSE);
     UpdateData(FALSE);
  }
  else{
     GetDlgItem(IDC_BUTTON_IN)->EnableWindow(FALSE);
   GetDlgItem(IDC_BUTTON_OUT)->EnableWindow(TRUE);
   GetDlgItem(IDC_EDIT-SEND)->EnableWindow(TRUE);
     int iErrorCode=WSAAsyncSelect(m_hSocket,m_hWnd,WM_SOCKET_READ,   FD_READ);
     if(iErrorCode=SOCKET_ERROR)
      MessageBox(″WSAAsyncSelect failed on socket″,NULL,MB_OK);
    }
  }  手工加入CTalkDialog::OnSockRead(), 响应WinSock 发来的消息
  LRESULT CTalkDialog::OnSockRead(WPARAM wParamLPARAM 1Param)
  {
    int iRead;
    int iBufferLength;
    int iEnd;
    int iSpaceRemain;
    char chIncomingData[100];
    iBufferLength=iSpaceRemain=sizeof (chIncomingData);
   iEnd=0;
   iSpaceRemain-=iEnd;
   iRead=recv(m_hSocket,(LPSTR)(chIncomingData+iEnd),iSpaceRemain,0);
   iEnd+=iRead;
   if (iRead=SOCKET_ERROR)
    AfxMessageBox(″接收数据错误!″);
   chIncomingData[iEnd]=‘\0’;
   if(lstrlen(chIncomingData)!=0)
{
  m_csRecv=m_csRecv+chIncomingData;
  GetDlgItem(IDC_EDIT_RECV)->SetWindowText((LPCSTR)m_csRecv);
  CEdit*pEdit;
  pEdit=(CEdit*)GetDlgItem(IDC_EDIT_RECV);
  int i=pEdit->GetLineCount();
     pEdit->LineScroll(i,0);
    }
    return(OL);
  }
  手工加入CTalkDialog::OnSend(),将指定缓冲区中的数据发送出去。
  void CTalkDialog::OnSend()
   {
    UpdateData();
    m_csSend.TrimLeft();
    m_csSend.TrimRight();
    if(!m_csSend.IsEmpty())
   {
m_csSend=m_csName+″:″+m_csSend+″\r\n″;
   int nCharSend=send(m_hSocket,m_csSend,m_csSend.GetLength(),0);
   if(nCharSend=SOCKET_ERROR)
    MessageBox(″ 发送数据错误!″,NULL,MB_OK);
   }
   m_csSend=″″;
   UpdateData(FALSE);
   CWnd *pEdit=GetDlgItem(IDC_EDIT_SEND);
   pEdit->SetFocus();
   return;
  }
  通过ClassWizard 增加 virtual function PreTranslateMessage(), 控制当按回车键时调用OnSend(),而不是执行缺省按钮的动作。
  BOOL CTalkDialog::PreTranslateMessage(MSG*pMsg)
  {
if(pMsg-> message==WM_KEYDOWN && pMsg-> wParam=VK_RETURN){
     OnSend();
    }
    return CDialog::PreTranslateMessage(pMsg);
  }

  为了简化设计,用户名在客户端控制,服务端只进行简单的接收信息和“广播”此信息,不进行名字校验,也就是说,可以有同名客户登录到服务端。这个程序设计虽然简单,但是已经具备了聊天室的最基本的功能。服务端程序在SCO OpenServer 5.0.5 下编译通过,客户端程序在VC++6.0 下编译通过,在使用TCP/IP 协议的局域网上运行良好。

  上述实例仅用于说明通过 Socket 编程接口能方便地实现跨系统通讯,而这种跨系统通讯在金融行业中有着越来越广泛的应用,利用它可以实现各种多媒体查询,数据传输,网络通讯等功能。因此对跨系统通讯的探讨是非常必要和有意义的,希望上述探讨对大家能有所启发。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章