1、武汉理工大学华夏学院课程设计计算机网络学生姓名: 毛超 学 号:10225509312 专业班级:计算机 2093 班指导老师: 用 WINSOCK 实现聊天室的 VC+程序设计摘 要:WINSOCK 是在 Windows 进行网络通信编程的 API 接口,也是 Windws 网络编程的事实标准。在网络编程中最常用的方案便是客户机/服务器模型。本文提出了在客户机/服务器模型下用 WINSOCK 实现 Internet 中常见的聊天室软件的方案。关键词:套接字,WINSOCK,客户机/服务器,网络编程一:SOCKET 简介80 年代初,美国政府的高级研究工程机构(ARPA)给加利福尼亚大学 Be
2、rkeley 分校提供了资金,让他们在 UNIX 操作系统下实现 TCP/IP 协议。在这个项目中,研究人员为TCP/IP 网络通信开发了一个 API(应用程序接口)。这个 API 称为 Socket 接口(套接字)。今天,SOCKET 接口是 TCP/IP 网络最为通用的 API,也是在 INTERNET 上进行应用开发最为通用的 API。90 年代初,由 Microsoft 联合了其他几家公司共同制定了一套 WINDOWS 下的网络编程接口,即 WindowsSockets 规范。它是 BerkeleySockets 的重要扩充,主要是增加了一些异步函数,并增加了符合 Windows 消息
3、驱动特性的网络事件异步选择机制。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 版的源代码,二进制
4、文件,应用程序都可以不加修改地在 2.0 规范下使用。SOCKET 实际在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有SOCKET 接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个 SOCKET 接口来实现。在应用开发中就像使用文件句柄一样,可以对 SOCKET 句柄进行读,写操作。二:基于 WINDOWS SOCKET 的应用开发介绍。在 WINDOWS95/98,WINDOWSNT 进行 WINSOCK 开发使用的编程语言有很多,VC+,JAVA,DELPHI,VB 等。其中 VC 时使用最普遍,和 WINSOCK 结合最紧密的。并且VC+对原来的 Windows
5、Sockets 库函数进行了一系列封装,继而产生了CAsynSocket、CSocket、CSocketFile 等类,它们封装着有关 Socket 的各种功能,是编程变得更加简单。但如果你是一个 WINSOCK 编程的初学者,那么建议你在一开始还是学习 WINSOCK 最基本的 API 函数进行编程,这样可以大大加深对 WINSOCK 的了解,对将来很有好处。在 VC 中进行 WINSOCK 的 API 编程开发,需要使用到下面三个文件:1 WINSOCK.H: 这是 WINSOCK API 的头文件。2 WSOCK32.LIB: WINSOCK API 连接库文件。在使用中,一点要把它作为
6、项目的非缺省的连接库包含到项目文件中去。3 WINSOCK.DLL: WINSOCK 的动态连接库,位于 WINDOWS 的安装目录下。可以看到,WINSOCK。DLL 位于 TCP/IP 协议栈和应用程序之间。也就是说,WINSOCK 管理与 TCP/IP 协议的接口。在一开始 WINSOCK 的应有开发时,你不必对 TCP/IP 协议有很深刻的了解。但是,如果想成为一个为网络编程的高手,就一定要对下层了解得十分清楚。在网络编程中最常用的方案便是客户机/服务器模型。在这种方案中客户应用程序向服务器程序请求服务。一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休
7、眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提供服务对客户的请求作出适当的反应。虽然基于连接协议(流套接字)的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过无连接协议(数据报套接字)提供的。一般在使用中,面向连接协议的 SOCKET 编程模型应用最为广泛,因为面向连接协议提供了一系列的数据纠错功能,可以保证在网络上传输的数据及时、无误地到达对方。 总的来说,使用 SOCKET 接口(面向连接或无连接)进行网络通信时,必须按下面简单的四步进行处理:1、程序必须建立一个 SOCKET。2、程序必须按要求配置此 SOCKET。也就是说
8、,程序要么将此 SOCKET 连接到远方的主机上,要么给此 SOCKET 指定一个本地协议端口。3、程序必须按要求通过此 SOCKET 发送和接收数据。4、程序必须关闭此 SOCKET。三:WINSOCK API 主要函数简介作者利用 WINSOCK API 编写了一个具有聊天室功能的应用程序,可用作学习 WINSOCK 程序设计的参照。WINSOCK API 包括很多函数,但其中最常用,包括在文章所附源程序中的有:注:只是有关函数的简要说明,具体规则、说明请参见 VC+帮助和 WINSOCK 规范。1、WSAStartup():连结应用程序与 Windows Sockets DLL 的第一个
9、函数。说明: 此函数是应用程序调用 Windows Sockets DLL 函数中的第一个,也唯有此函数呼叫成功後,才可以再调用其他 Windows Sockets DLL 的函数。2、WSACleanup():结束 Windows Sockets DLL 的使用。说明: 当应用程序不再需要使用 Windows Sockets DLL 时,须调用此函数来注销使用,以便释放其占用的资源。3、 socket():建立 Socket。说明: 此函数用来建立一 Socket 描述字,并为此 Socket 建立其所使用的资源。4、 closesocket():关闭某一 Socket。说明: 此一函数是用
10、来关闭某一 Socket。5、 bind():将一本地地址与一个 SOCKET 描述字连接在一起。说明:此函数在服务程序上使用,是调用监听函数 listen()必须要调用的函数。6、 listen():设定 Socket 为监听状态,准备被连接。说明: 此函数在服务程序上使用,来设定 Socket 进入监听状态,并设定最多可有多少个在未真正完成连接前的客户端的连接要求。(目前最大值限制为 5, 最小值为 1)7、 accept():接受某一 Socket 的连接要求,以完成面向连接的客户端 Socket 的连接请求。说明: 服务端应用程序调用此函数来接受客户端 Socket 连接请求,acce
11、pt() 函数的返回值为一新的 Socket,新 Socket 就可用来在服务端和客户端之间的信息传递接收,而原来 Socket 仍然可以接收其他客户端的连接要求。8、 connect():要求连接某一 Socket 到指定的网络上服务端。说明: 此函数用在客户端,用来向服务端要求建立连接。当连接建立完成後,客户端即可利用此 Socket 来与服务端进行信息传递。9、 recv():从面向连接的 Socket 接收信息。说明: 此函数用来从面向连接的 Socket 接收信息。10、send():使用面向连接的 Socket 发送信息。说明: 此函数用来从面向连接的 Socket 发送信息。11
12、、WSAAsyncSelect():要求某一 Socket 有事件 (event) 发生时通知使用者。说明: 此函数用来请求 Windows Sockets DLL 为窗口句柄发一条消息无论它何时检测到由 lEvent 参数指明的网络事件。要发送的消息由 wMsg 参数标明.被通知的套接口由 s 标识。本函数自动将套接口设置为非阻塞模式。lEvent 参数由下表中列出的值组成。值 意义FD_READ 欲接收读准备好的通知。FD_WRITE 欲接收写准备好的通知。FD_OOB 欲接收带边数据到达的通知。FD_ACCEPT 欲接收将要连接的通知。FD_CONNECT 欲接收已连接好的通知。FD_C
13、LOSE 欲接收套接口关闭的通知。这个函数可以认为是 WINSOCK API 中最为重要的一个函数。要想使用好这个函数,你必须对 WINDOWS 编程的事件驱动和消息传递有很清楚的了解。四:聊天室应用程序的设计说明:软件功能:Internet 上可以提供一种叫 IRC 的服务。使用者通过客户端的程序登录到 IRC 服务器上,就可以与登录在同一 IRC 服务器上的客户进行交谈,这也就是平常所说的聊天室。在这里,给出了一个在运行 TCP/IP 协议的网络上实现 IRC 服务的程序。软件使用说明:首先,在一台计算机上运行服务端程序,然后就可以在同一网络的其他计算机上运行客户端程序,登录到服务器上,各
14、个客户之间就可以聊天了。软件设计要点:1、服务端核心代码在 CServerViwe 类中,有一个 SOCKET 变量 m_hServerSocket 和 SOCKET 数组 m_aClientSocketMAXClient(MAXClient:所定义的接收连接客户的最大数目),m_hServerSocket 用来在指定的端口(1000)进行侦听,如果有客户端请求连接,则在 m_aClientSocket 数组中查找一个空 socket,将客户端的地址赋予此 socket。每当一个 ClientSocket 接收到信息,都将会向窗口发一条消息。程序接收到这个消息后,再把接收到的信息发送给每一个
15、ClientSocket。2、客户端客户端比较简单,核心代码在 CClientDlg 类中。只有一个 socket 变量 m_hSocket,与服务端进行连接。连接建立好后,通过此 SOCKET 发送和接收信息。为了简化设计,用户名在客户端控制,服务器端只进行简单的接收信息和“广播”此信息,不进行名字校验,也就是说,可以有同名客户登录到服务端。这个程序设计虽然简单,但是已经具备了聊天室的最基本的功能。程序在 VC+ 6.0 下编译通过,在使用 TCP/IP 协议的 WINDOWS 95/98 对等局域网 和使用 TCP/IP 协议的 WINDOWS NT 局域网上运行良好。- 程序代码:imp
16、ort java.applet.*; import java.awt.*; import java.io.*; import .*; import java.awt.event.*; public class ChatClient extends Applet protected boolean loggedIn;/登入状态 protected Frame cp;/聊天室框架 protected static int PORTNUM=7777; /缺省端口号 7777 protected int port;/实际端口号 protected Socket sock; protected Buff
17、eredReader is;/用于从 sock 读取数据的 BufferedReader protected PrintWriter pw;/用于向 sock 写入数据的 PrintWriter protected TextField tf;/用于输入的 TextField protected TextArea ta;/用于显示对话的 TextArea protected Button lib;/登入按钮 protected Button lob;/登出的按钮 final static String TITLE =“Chatroom applet“; protected String pain
18、tMessage;/发表的消息 public ChatParameter Chat; public void init() paintMessage=“正在生成聊天窗口“; repaint(); cp=new Frame(TITLE); cp.setLayout(new BorderLayout(); String portNum=getParameter(“port“);/呢个参数勿太明 port=PORTNUM; if (portNum!=null) /书上是 portNum=null,十分有问题 port=Integer.parseInt(portNum); /CGI ta=new Te
19、xtArea(14,80); ta.setEditable(false);/read only attribute ta.setFont(new Font(“Monospaced“,Font.PLAIN,11); cp.add(BorderLayout.NORTH,ta); Panel p=new Panel(); Button b; /for login button p.add(lib=new Button(“Login“); lib.setEnabled(true); lib.requestFocus(); lib.addActionListener(new ActionListener
20、() public void actionPerformed(ActionEvent e) login(); lib.setEnabled(false); lob.setEnabled(true); tf.requestFocus();/将键盘输入锁定再右边的文本框中 ); /for logout button p.add(lob=new Button (“Logout“); lob.setEnabled(false); lob.addActionListener(new ActionListener() public void actionPerformed(ActionEvent e) l
21、ogout(); lib.setEnabled(true); lob.setEnabled(false); lib.requestFocus(); ); p.add(new Label (“输入消息:“); tf=new TextField(40); tf.addActionListener(new ActionListener() public void actionPerformed(ActionEvent e) if(loggedIn) /pw.println(Chat.CMD_BCAST+tf.getText();/Chat.CMD是咩野来? int j=tf.getText().in
22、dexOf(“:“); if(j0) pw.println(Chat.CMD_MESG+tf.getText(); else pw.println(Chat.CMD_BCAST+tf.getText(); tf.setText(“);/勿使用 flush()? ); p.add(tf); cp.add(BorderLayout.SOUTH,p); cp.addWindowListener(new WindowAdapter() public void windowClosing(WindowEvent e) /如果执行了 setVisible 或者 dispose,关闭窗口 ChatClien
23、t.this.cp.setVisible(false); ChatClient.this.cp.dispose(); logout(); ); cp.pack();/勿明白有咩用? /将 Frame cp 放在中间 Dimension us=cp.getSize(), them=Toolkit.getDefaultToolkit().getScreenSize(); int newX=(them.width-us.width)/2; int newY=(them.height-us.height)/2; cp.setLocation(newX,newY); cp.setVisible(true
24、); paintMessage=“Window should now be visible“; repaint(); /登录聊天室 public void login() if(loggedIn) return; try sock=new Socket(getCodeBase().getHost(),port); is=new BufferedReader(new InputStreamReader(sock.getInputStream(); pw=new PrintWriter(sock.getOutputStream(),true); catch(IOException e) showS
25、tatus(“Cant get socket: “+e); cp.add(new Label(“Cant get socket: “+e); return; /构造并且启动读入器,从服务器读取数据,输出到文本框中 /这里,长成一个线程来避免锁住资源(lockups) new Thread (new Runnable() public void run() String line; try while(loggedIn catch(IOException e) showStatus(“我的天啊,掉线了也!“); return; ).start(); /假定登录(其实只是打印相关信息,并没有真正登
26、录) / pw.println(Chat.CMD_LOGIN+“AppletUser“); pw.println(Chat.CMD_LOGIN+“AppletUser“); loggedIn =true; /模仿退出的代码 public void logout() if(!loggedIn) return; loggedIn=false; try if(sock!=null) sock.close(); catch(IOException ign) / 异常处理哦 /没有设置 stop 的方法,即使从浏览器跳到另外一个网页的时候 /聊天程序还可以继续运行 public void paint(G
27、raphics g) Dimension d=getSize(); int h=d.height; int w=d.width; g.fillRect(0,0,w,2); g.setColor(Color.black); g.drawString(paintMessage,10,(h/2)-5); 聊天室服务器端 import .*; import java.io.*; import java.util.*; public class ChatServer /聊天室管理员 ID protected final static String CHATMASTER_ID=“ChatMaster“;
28、/系统信息的分隔符 protected final static String SEP=“: “; /服务器的 Socket protected ServerSocket servSock; /当前客户端列表 protected ArrayList clients; /调试标记 protected boolean DEBUG=false; public ChatParameter Chat; /主方法构造一个 ChatServer,没有返回值 public static void main(String argv) System.out.println(“Chat server0.1 star
29、ting“); ChatServer w=new ChatServer(); w.runServer(); System.out.println(“*ERROR* Chat server0.1 quitting“); /构造和运行一个聊天服务 ChatServer() Chat=new ChatParameter(); clients=new ArrayList(); try servSock=new ServerSocket(7777);/实有问题拉,不过可能是他自己定义既一个 class. System.out.println(“Chat Server0.1 listening on po
30、rt:“+7777); catch(Exception e) log(“IO Exception in ChatServer.“); System.exit(0); public void runServer() try while(true) Socket us=servSock.accept(); String hostName=us.getInetAddress().getHostName(); System.out.println(“Accpeted from “+hostName); /一个处理的线程 ChatHandler cl=new ChatHandler(us,hostNam
31、e); synchronized(clients) clients.add(cl); cl.start(); if(clients.size()=1) cl.send(CHATMASTER_ID,“Welcome!You are the first one here“); else cl.send(CHATMASTER_ID,“Welcome!You are the latest of“+ clients.size()+“ users.“); catch(Exception e) log(“IO Exception in runServer:“+e); System.exit(0); prot
32、ected void log(String s) System.out.println(s); /处理会话的内部的类 protected class ChatHandler extends Thread /客户端 scoket protected Socket clientSock; /读取 socket 的 BufferedReader protected BufferedReader is ; /在 socket 上发送信息行的 PrintWriter protected PrintWriter pw; /客户端出主机 protected String clientIP; /句柄 prot
33、ected String login; public ChatHandler (Socket sock,String clnt)throws IOException clientSock=sock; clientIP=clnt; is=new BufferedReader( new InputStreamReader(sock.getInputStream(); pw=new PrintWriter (sock.getOutputStream(),true); /每一个 ChatHandler 是一个线程,下面的是他的 run()方法 /用于处理会话 public void run() Str
34、ing line; try while(line=is.readLine()!=null) char c=line.charAt(0);/我顶你老母啊 ,果只 Chat.CMD 咩 xx 冇定义 扑啊/! line=line.substring(1); switch(c) /case Chat.CMD_LOGIN: case l: if(!Chat.isValidLoginName(line) send(CHATMASTER_ID,“LOGIN“+line+“invalid“); log(“LOGIN INVALID from:“+clientIP); continue; login=line
35、; broadcast(CHATMASTER_ID,login+“ joins us,for a total of“+ clients.size()+“ users“); break; / case Chat.CMD_MESG: case m: if(login=null) send(CHATMASTER_ID,“please login first“); continue; int where =line.indexOf(Chat.SEPARATOR); String recip=line.substring(0,where); String mesg=line.substring (whe
36、re+1); log(“MESG: “+login+“-“+recip+“: “+mesg); ChatHandler cl=lookup(recip); if(cl=null) psend(CHATMASTER_ID,recip+“not logged in.“); else cl.psend(login,mesg); break; /case Chat.CMD_QUIT: case q: broadcast(CHATMASTER_ID,“Goodbye to “+login+“+clientIP); close(); return;/ChatHandler 结束 / case Chat.C
37、MD_BCAST: case b: if(login!=null) broadcast(login,line); else log(“B“); else if(clients.size()=1) ChatHandler last=(ChatHandler)clients.get(0); last.send(CHATMASTER_ID,“Hey,you are talking to yourself again“); else broadcast(CHATMASTER_ID,“There are now“+clients.size()+“ users“); protected void clos
38、e() if(clientSock=null) log(“close when not open“); return; try clientSock.close(); clientSock=null; catch(IOException e) log(“Failure during close to “+clientIP); /发送一条消息给用户 public void send(String sender,String mesg) pw.println(sender+SEP+“*“+mesg); /发送私有的消息 protected void psend(String sender ,Str
39、ing msg) send(“,msg); /发送一条消息给所有的用户 public void broadcast (String sender,String mesg) System.out.println(“Broadcasting“+sender+SEP+mesg); for(int i=0;iclients.size();i+) ChatHandler sib=(ChatHandler)clients.get(i); if(DEBUG) System.out.println(“Sending to“+sib); sib.send(sender,mesg); if(DEBUG) Syst
40、em.out.println(“Done broadcast“); protected ChatHandler lookup(String nick) synchronized(clients) for(int i=0;iclients.size();i+) ChatHandler cl=(ChatHandler)clients.get(i); if(cl.login.equals(nick) return cl; return null; /将 ChatHandler 对象转换成一个字符串 public String toString() return “ChatHandler“+login
41、+“; public class ChatParameter public static final char CMD_BCAST=b; public static final char CMD_LOGIN=l; public static final char CMD_MESG=m; public static final char CMD_QUIT=q; public static final char SEPARATOR=:;/? public static final int PORTNUM=7777; public boolean isValidLoginName(String line) if (line.equals(“CHATMASTER_ID“) return false; return true; public void main(String argv)