WINSOCK的VC++实现聊天室程序设计

作者在 2008-11-22 16:15:10 发布以下内容

一般在使用中,面向连接协议的SOCKET编程模型应用最为广泛,因 
为面向连接协议提供了一系列的数据纠错功能,可以保证在网络上传输 
的数据及时、无误地到达对方。 
总的来说,使用SOCKET接口(面向连接或无连接)进行网络通信时, 
必须按下面简单的四步进行处理: 
1、程序必须建立一个 SOCKET。 
2、程序必须按要求配置此SOCKET。也就是说,程序要么将此 
SOCKET连接到远方的主机上,要么给此SOCKET指定一个 
本地协议端口。 
3、程序必须按要求通过此SOCKET发送和接收数据。 
4、程序必须关闭此SOCKET。 



用WINSOCK实现聊天室的VC++程序设计 
罗 杰 
(贵州大学计算机软件与理论研究所) 

摘要:WINSOCK 是在Windows进行网络通信编程的API接口,也是 
Windows网络编程的事实标准。在网络编程中最常用的方案便是客户 
机/服务器模型。本文提出了在客户机/服务器模型下用WINSOCK实现 
Internet中常见的聊天室软件的方案。 

关键词:套接字,WINSOCK,客户机/服务器,网络编程 
一、SOCKET简介 
80年代初,美国政府的高级研究工程机构(ARPA)给加利福尼亚大 
学Berkeley分校提供了资金,让他们在UNIX操作系统下实现TCP/IP协 
议。在这个项目中,研究人员为TCP/IP网络通信开发了一个API(应用 
程序接口)。这个API称为Socket接口(套接字)。今天,SOCKET接口 
是TCP/IP网络最为通用的API,也是在INTERNET上进行应用开发最为通 
用的API。 
90年代初,由Microsoft联合了其他几家公司共同 
制定了一套WINDOWS下的网络编程接口,即WindowsSockets规范。它是 
BerkeleySockets的重要扩充,主要是增加了一些异步函数,并增加了 
符合Windows消息驱动特性的网络事件异步选择机制。WINDOWSSOCKETS 
规范是一套开放的、支持多种协议的Windows下的网络编程接口。从 
1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、 
Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成 
为Windows网络编程的事实上的标准。目前,在实际应用中的 
WINDOWSSOKCETS规范主要有1.1版和2.0版。两者的最重要区别是1.1版 
只支持TCP/IP协议,而2.0版可以支持多协议。2.0版有良好的向后兼容 
性,任何使用1.1版的源代码,二进制文件,应用程序都可以不加修改地 
在2.0规范下使用。 
SOCKET实际在计算机中提供了一个通信端口,可以通过这个端口 
与任何一个具有SOCKET接口的计算机通信。应用程序在网络上传输,接 
收的信息都通过这个SOCKET接口来实现。在应用开发中就像使用文件句 
柄一样,可以对SOCKET句柄进行读,写操作。

 

二、基于WINDOWS SOCKET的应用开发介绍。 
在WINDOWS95/98,WINDOWSNT进行WINSOCK开发使用的编程语言有很多, 
VC++,JAVA,DELPHI,VB等。其中VC时使用最普遍,和WINSOCK结合最 
紧密的。并且VC++对原来的WindowsSockets库函数进行了一系列封装, 
继而产生了CAsynSocket、CSocket、CSocketFile等类,它们封装着 
有关Socket的各种功能,是编程变得更加简单。但如果你是一个 
WINSOCK编程的初学者,那么建议你在一开始还是学习WINSOCK最基本的 
API函数进行编程,这样可以大大加深对WINSOCK的了解,对将来很有好 
处。 
在VC中进行WINSOCK的API编程开发,需要使用到下面三个文件: 
1、WINSOCK.H: 这是WINSOCK API的头文件。 
2、WSOCK32.LIB: WINSOCK API连接库文件。在使用中,一点要把它作为项目 
的非缺省的连接库包含到项目文件中去。 
3、WINSOCK.DLL: WINSOCK的动态连接库,位于WINDOWS的安装目录下。 
WINSOCK接口在WINDOWS编程环境中的位置如下图所示: 



可以看到,WINSOCK。DLL位于TCP/IP协议栈和应用程序之间。也就 
是说,WINSOCK管理与TCP/IP协议的接口。在一开始WINSOCK的应有开 
发时,你不必对TCP/IP协议有很深刻的了解。但是,如果想成为一个为 
网络编程的高手,就一定要对下层了解得十分清楚。 
在网络编程中最常用的方案便是客户机/服务器模型。在这种方案中 
客户应用程序向服务器程序请求服务。一个服务程序通常在一个众所周 
知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态, 
直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程 
序被“惊醒”并且为客户提供服务-对客户的请求作出适当的反应。虽 
然基于连接协议(流套接字)的服务是设计客户机/服务器应用程序时的 
标准,但有些服务也是可以通过无连接协议(数据报套接字)提供的。 
其编程模型分别如下: 

面向连接协议的SOCKET编程模型 


======================================================= 

无连接协议的SOCKET编程模型 

====================================================== 
一般在使用中,面向连接协议的SOCKET编程模型应用最为广泛,因 
为面向连接协议提供了一系列的数据纠错功能,可以保证在网络上传输 
的数据及时、无误地到达对方。 
总的来说,使用SOCKET接口(面向连接或无连接)进行网络通信时, 
必须按下面简单的四步进行处理: 
1、程序必须建立一个 SOCKET。 
2、程序必须按要求配置此SOCKET。也就是说,程序要么将此 
SOCKET连接到远方的主机上,要么给此SOCKET指定一个 
本地协议端口。 
3、程序必须按要求通过此SOCKET发送和接收数据。 
4、程序必须关闭此SOCKET。

 

三:WINSOCK API主要函数简介 
作者利用WINSOCK API 编写了一个具有聊天室功能 
的应用程序,可用作学习 WINSOCK 程序设计的参照。WINSOCK API 包 
括很多函数,但其中最常用,包括在文章所附源程序中的有: 
注:只是有关函数的简要说明,具体规则、说明请参见VC++帮助和 
WINSOCK规范。 
函数名 功能 说明 

WSAStartup() 
  
连结应用程序与 Windows Sockets DLL 的第一个函数 
  
此函数是应用程序调用 Windows Sockets DLL函数中的第一个,也唯有此函数呼叫成功後,才可以再调用其他 Windows Sockets DLL 的函数。 
WSACleanup() 
结束 Windows Sockets DLL 的使用 当应用程序不再需要使用 Windows Sockets DLL时,须调用此函数来注销使用,以便释放其占用的资源。 
socket() 建立Socket 此函数用来建立一 Socket 描述字,并为此 Socket 建立其所使用的资源。 
closesocket() 关闭某一Socket 此一函数是用来关闭某一 Socket 
bind() 将一本地地址与一个SOCKET描述字连接在一起 此函数在服务程序上使用,是调用监听函数listen()必须要调用的函数 
listen() 设定 Socket 为监听状态,准备被连接 此函数在服务程序上使用,来设定 Socket 进入监听状态,并设定最多可有多少个在未真正完成连接前的客户端的连接要求。(目前最大值限制为 5, 最小值为1) 
  

accept() 接受某一Socket的连接要求,以完成面向连接的客户端 Socket 的连接请求 服务端应用程序调用此函数来接受客户端Socket 连接请求,accept() 函数的返回值为一新的 Socket,新 Socket 就可用来在服务端和客户端之间的信息传递接收,而原来 Socket 仍然可以接收其他客户端的连接要求. 
connect() 要求连接某一Socket到指定的网络上服务端 此函数用在客户端,用来向服务端要求建立连接。当连接建立完成後,客户端即可利用此 Socket 来与服务端进行信息传递。 
recv() 从面向连接的 Socket 接收信息 此函数用来从面向连接的 Socket 接收信息 
send() 使用面向连接的 Socket 发送信息 此函数用来从面向连接的 Socket 发送信息 
WSAAsyncSelect() 要求某一 Socket 有事件 (event) 发生时通知使用者 此函数用来请求Windows Sockets DLL 为窗口句柄发一条消息-无论它何时检测到由lEvent参数指明的网络事件。要发送的消息由wMsg参数标明.被通知的套接口由s标识。本函数自动将套接口设置为非阻塞模式。 


lEvent参数由下表中列出的值组成 

FD_READ 欲接收读准备好的通知 
FD_WRITE 欲接收写准备好的通知 
FD_OOB 欲接收带边数据到达的通知 
FD_ACCEPT 欲接收将要连接的通知 
FD_CONNECT 欲接收已连接好的通知 
FD_CLOSE 欲接收套接口关闭的通知 

这个函数可以认为是 WINSOCK API 中最为重要的 
一个函数。要想使用好这个函数,你必须对 WINDOWS 编程的事件驱动 
和消息传递有很清楚的了解。 

四:聊天室应用程序的设计说明: 
软件功能: 
Internet上可以提供一种叫IRC 的服务。使用者通过客户端的程序 
登录到IRC服务器上,就可以与登录在同一IRC服务器上的客户进行交谈,这也就是平常 
所说的聊天室。在这里,给出了一个在运行TCP/IP协议的网络上实现IRC服务的程序。 
软件使用说明: 
首先,在一台计算机上运行服务端程序,然后就可以在同一网络的 
其他计算机上运行客户端程序,登录到服务器上,各个客户之间就可以聊天了。 
软件设计要点: 
1、服务端 
核心代码在 CServerViwe 类中,有一个 SOCKET 变量 
m_hServerSocket 和 SOCKET 数组 m_aClientSocket[MAXClient](MAXClient: 
所定义的接收连接客户的最大数目),m_hServerSocket 用来在指定的端口(>1000) 
进行侦听,如果有客户端请求连接,则在 m_aClientSocket 数组中查找一个空 socket, 
将客户端的地址赋予此 socket。 
每当一个 ClientSocket 接收到信息,都将会向窗口发一条消息。 
程序接收到这个消息后,再把接收到的信息发送给每一个 ClientSocket。 
2、客户端 
客户端比较简单,核心代码在 CClientDlg 类中。只有一个 socket 
变量 m_hSocket,与服务端进行连接。连接建立好后,通过此 SOCKET 发送和接收信息。 
为了简化设计,用户名在客户端控制,服务器端只进行简单的接收信 
息和“广播”此信息,不进行名字校验,也就是说,可以有同名客户登录到服务端。这个 
程序设计虽然简单,但是已经具备了聊天室的最基本的功能。 
程序在VC++ 6.0 下编译通过,在使用 TCP/IP 协议的 WINDOWS 95/98 
对等局域网和使用 TCP/IP 协议的 WINDOWS NT 局域网上运行良好。  

 

 

4.核心代码分析

  限于篇幅,这里仅给出与网络编程相关的核心代码,其他的诸如聊天文字的服务器和客户端显示读者可以自行添加。

  4.1服务器端代码

  开启服务器功能:

void OnServerOpen() //开启服务器功能
{
  WSADATA wsaData;
  int iErrorCode;
  char chInfo[64];
  if (WSAStartup(WINSOCK_VERSION, &wsaData)) //调用Windows Sockets DLL
   { MessageBeep(MB_ICONSTOP);
    MessageBox("Winsock无法初始化!", AfxGetAppName(), MB_OK|MB_ICONSTOP);
    WSACleanup();
    return; }
  else
   WSACleanup();
   if (gethostname(chInfo, sizeof(chInfo)))
   { ReportWinsockErr("\n无法获取主机!\n ");
    return; }
   CString csWinsockID = "\n==>>服务器功能开启在端口:No. ";
   csWinsockID += itoa(m_pDoc->m_nServerPort, chInfo, 10);
   csWinsockID += "\n";
   PrintString(csWinsockID); //在程序视图显示提示信息的函数,读者可自行创建
   m_pDoc->m_hServerSocket=socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
   //创建服务器端Socket,类型为SOCK_STREAM,面向连接的通信
   if (m_pDoc->m_hServerSocket == INVALID_SOCKET)
   { ReportWinsockErr("无法创建服务器socket!");
    return;}
   m_pDoc->m_sockServerAddr.sin_family = AF_INET;
   m_pDoc->m_sockServerAddr.sin_addr.s_addr = INADDR_ANY;
   m_pDoc->m_sockServerAddr.sin_port = htons(m_pDoc->m_nServerPort);
   if (bind(m_pDoc->m_hServerSocket, (LPSOCKADDR)&m_pDoc->m_sockServerAddr,   
     sizeof(m_pDoc->m_sockServerAddr)) == SOCKET_ERROR) //与选定的端口绑定
    {ReportWinsockErr("无法绑定服务器socket!");
     return;}
    iErrorCode=WSAAsyncSelect(m_pDoc->m_hServerSocket,m_hWnd,
    WM_SERVER_ACCEPT, FD_ACCEPT);
    //设定服务器相应的网络事件为FD_ACCEPT,即连接请求,
    // 产生相应传递给窗口的消息为WM_SERVER_ACCEPT
   if (iErrorCode == SOCKET_ERROR)
    { ReportWinsockErr("WSAAsyncSelect设定失败!");
     return;}
   if (listen(m_pDoc->m_hServerSocket, QUEUE_SIZE) == SOCKET_ERROR) //开始监听客户连接请求
    {ReportWinsockErr("服务器socket监听失败!");
     m_pParentMenu->EnableMenuItem(ID_SERVER_OPEN, MF_ENABLED);
     return;}
   m_bServerIsOpen = TRUE; //监视服务器是否打开的变量
  return;
}


  响应客户发送聊天文字到服务器:ON_MESSAGE(WM_CLIENT_READ, OnClientRead)

LRESULT OnClientRead(WPARAM wParam, LPARAM lParam)
{
  int iRead;
  int iBufferLength;
  int iEnd;
  int iRemainSpace;
  char chInBuffer[1024];
  int i;
  for(i=0;(i
  //MAXClient是服务器可响应连接的最大数目
   {}
  if(i==MAXClient) return 0L;
   iBufferLength = iRemainSpace = sizeof(chInBuffer);
   iEnd = 0;
   iRemainSpace -= iEnd;
   iBytesRead = recv(m_aClientSocket[i], (LPSTR)(chInBuffer+iEnd), iSpaceRemaining, NO_FLAGS);  
//用可控缓冲接收函数recv()来接收字符
   iEnd+=iRead;
  if (iBytesRead == SOCKET_ERROR)
   ReportWinsockErr("recv出错!");
   chInBuffer[iEnd] = '\0';
  if (lstrlen(chInBuffer) != 0)
   {PrintString(chInBuffer); //服务器端文字显示
    OnServerBroadcast(chInBuffer); //自己编写的函数,向所有连接的客户广播这个客户的聊天文字
   }
  return(0L);
}


  对于客户断开连接,会产生一个FD_CLOSE消息,只须相应地用closesocket()关闭相应的Socket即可,这个处理比较简单。

  4.2客户端代码

  连接到服务器:

void OnSocketConnect()
{ WSADATA wsaData;
  DWORD dwIPAddr;
  SOCKADDR_IN sockAddr;
  if(WSAStartup(WINSOCK_VERSION,&wsaData)) //调用Windows Sockets DLL
  {MessageBox("Winsock无法初始化!",NULL,MB_OK);
   return;
  }
  m_hSocket=socket(PF_INET,SOCK_STREAM,0); //创建面向连接的socket
  sockAddr.sin_family=AF_INET; //使用TCP/IP协议
  sockAddr.sin_port=m_iPort; //客户端指定的IP地址
  sockAddr.sin_addr.S_un.S_addr=dwIPAddr;
  int nConnect=connect(m_hSocket,(LPSOCKADDR)&sockAddr,sizeof(sockAddr)); //请求连接
  if(nConnect)
   ReportWinsockErr("连接失败!");
  else
   MessageBox("连接成功!",NULL,MB_OK);
   int iErrorCode=WSAAsyncSelect(m_hSocket,m_hWnd,WM_SOCKET_READ,FD_READ);
   //指定响应的事件,为服务器发送来字符
  if(iErrorCode==SOCKET_ERROR)
  MessageBox("WSAAsyncSelect设定失败!");
}

  接收服务器端发送的字符也使用可控缓冲接收函数recv(),客户端聊天的字符发送使用数据可控缓冲发送函数send(),这两个过程比较简单,在此就不加赘述了。

windows网络编程 | 阅读 4133 次
文章评论,共0条
游客请输入验证码