基于TCP的客户端与服务器编程网络聊天
一、 内容概述
1.实验目的
1) 掌握网络应用程序的开发方法;2) 掌握Client/ Server结构软件的设计与开发方法;3) 掌握Socket机制的工作原理。
2. 实验前的准备
1) 阅读教材关于TCP/IP协议和Socket的相关内容;2) 阅读WinSock编程指南;3) 阅读本实验所附内容;4) 熟悉VC++、C#或Java开发语言。
3.实验内容
使用Java网络编程技术实现多人在线聊天程序:能相互发送与接受文本消息。
4.实验要求
1) 服务器要求多线程接受客户端的连接;2) 客户端要求实现边发送边接受;3) 按实验内容进行软件编制和调试;4) 进行功能测试,记录测试步骤;5) 给出程序主要部分流程图。
二、 设计要点及解决方案
本实验采用Java网络编程技术开发;在服务器端和客户端分别使用ServerSocket和 Socket 来创建面向字节流、面向连接的TCP 套接字;在服务器端采用多线程来处理各个客户的连接,在客户端也分别创建了读和写两个线程来分别完成与服务器端的读与写操作;同时,在服务器端采用套接字向量Vector来保存所有已连接的客户端的套接字。
服务器程序的基本流程是:
1) 创建服务器端套接字并调用其accept( )等待接受客户端的连接请求。
2) 当与新的客户端连接之后,创建并启动一个新的线程来处理这个新的连接。
3) 与客户端进行通信为客户端提供服务。
客户端程序的基本流程是:
1) 创建与服务器端连接的套接字。
2) 若与服务器端连接成功,则创建并启动读和写两个线程。
3) 在读写线程中分别完成与服务器端的接收和发送信息操作。
正常情况下服务器与客户端通信的详细过程如下:
1) 服务器向客户端发送欢迎消息并向所有在线客户发送一条一个新客户端已连接上了的消息。
2) 服务器读取客户消息,并将客户端发来的消息向所在线客户发送;客户端由发送和接收线程向服务器发送和从服务器接收消息;此过程循环执行,直到客户端发送的消息是exit 或 quit 消息。
3) 服务器收到exit 或 quit 消息,向发送该消息的客户端发送 bye 消息,关闭与该客户端的输入流和套接字,将该套接字从套接字向量中删除,并向其它所有客户端发送该客户已下线的消息。
4) 客户端收到 bye 消息,关闭与服务器端连接的输入流和套接字,退出程序。
三、 源代码
/** * @(#)TalkRoomServer.java * * TalkRoomServer application * * @邝翼飞 20081602B086 * @version 1.00 2010/12/1 */ import java.io.*; import java.net.*; import java.util.Vector; public class TalkRoomServer extends Thread // 继承线程类,以启动线程处理客户端连接 { static int port=50000; // 服务器端口 static Vector<Socket> ClientSock=new Vector<Socket>(); // 用向量容纳客户端连接套接字 Socket CurrentClientSock; // 实例变量用于保存每个连接实例的套接字 private InputStream inputStream; private OutputStream outputStream; private PrintWriter out; private BufferedReader in; // 主函数 public static void main(String[] args) throws IOException { ServerSocket SerSock=new ServerSocket(port); // 创建服务器套接字 System.out.println("Server Started...... "); Socket sock; while(true) { sock=SerSock.accept(); // 等待接受客户端连接 TalkRoomServer ProcessClientConn=new TalkRoomServer(sock); // 创建线程处理客户端连接 ProcessClientConn.start(); // 启动线程 } } //-------- 构造函数 ---------- public TalkRoomServer(Socket socketPara) { CurrentClientSock=socketPara; ClientSock.addElement(socketPara); try { inputStream=socketPara.getInputStream(); outputStream=socketPara.getOutputStream(); in=new BufferedReader(new InputStreamReader(inputStream)); out=new PrintWriter(new OutputStreamWriter(outputStream),true); } catch(IOException e) { e.printStackTrace(); } } // 处理与客户端的输入输出,覆盖线程类的run()方法 public void run() { try { NewClientOnline(); // 向新客户发出欢迎信息,通知所有在线客户有新客户连接了 while(true) // 在死循环中处理与客户的交流 { String ClientMSG=in.readLine(); // 若接收到的信息是 exit 或 quit 则使用 break 语句退出循环 if(ClientMSG.equalsIgnoreCase("exit")|| ClientMSG.equalsIgnoreCase("quit")) { NewClientOffline(); // 调用客户请求下线预处理函数 in.close(); // 关闭输入流 CurrentClientSock.close(); // 关闭套接字 break; } // 否则将接收到的信息向所有在线客户发出去 else { PrintWriter outall; if(ClientSock.isEmpty()==false) for(int i=0;i<ClientSock.size();i++) { outall=new PrintWriter( new OutputStreamWriter(ClientSock.elementAt(i).getOutputStream()),true); outall.println(">>> "+CurrentClientSock.getInetAddress().toString()+" says: "+ClientMSG); } } } } catch(IOException e) { ClientSock.remove(CurrentClientSock); System.out.println(CurrentClientSock.toString()+" has been removed from Vector ClientSock"); e.printStackTrace(); } } // 新客户上线预处理函数 public void NewClientOnline() throws IOException { out.println("-*-*-*-*-Welcome to chat room."+ "Your IP is: " + CurrentClientSock.getInetAddress().toString()+" -*-*-*-*-"); PrintWriter outall; try { if(ClientSock.isEmpty()==false) for(int i=0;i<ClientSock.size();i++) { outall=new PrintWriter( new OutputStreamWriter(ClientSock.elementAt(i).getOutputStream()),true); outall.println(">>> Server says: "+CurrentClientSock.getInetAddress().toString() + " is online now ***"); } } catch(IOException e) { ClientSock.remove(CurrentClientSock); System.out.println(CurrentClientSock.toString()+" has been removed from Vector ClientSock"); e.printStackTrace(); } } // 新客户请求下线预处理函数 public void NewClientOffline() throws IOException { out.println("closing link......"); out.println("bye"); PrintWriter outall; if(ClientSock.isEmpty()==false) for(int i=0;i<ClientSock.size();i++) { outall=new PrintWriter( new OutputStreamWriter(ClientSock.elementAt(i).getOutputStream()),true); outall.println(">>> Server says: "+CurrentClientSock.getInetAddress().toString() + " is offline now ***"); } ClientSock.remove(CurrentClientSock); } }
/** * @(#)TalkRoomClient.java * * TalkRoomClient application * * @邝翼飞 20081602B086 * @version 1.00 2010/12/1 */ import java.net.*; import java.io.*; import java.util.*; class TalkRoomClient { // 主函数 public static void main(String[] args) { String SvrHost="172.16.248.64"; // 服务器IP地址 if(args.length==1) SvrHost=args[0]; Boolean flag=true; label: while(flag) { try { Socket ConnToSvrSock=new Socket(SvrHost,50000); // 创建连接到服务器的套接字 flag=false; ClientSend CS=new ClientSend(ConnToSvrSock); // 创建发送信息线程 ClientReceive CR=new ClientReceive(ConnToSvrSock); // 创建接收信息线程 CS.start(); // 启动发送信息线程 CR.start(); // 启动接收信息线程 } catch(Exception e) // 当服务器没有在线时,给出提示,并让用户选择是否继续尝试连接 { System.out.println("Server OUT OF SERVICE ! ! !"); System.out.print("Try again? y/n : "); String s=new Scanner(System.in).nextLine(); if(s.equalsIgnoreCase("y")) { flag=true; continue label; } else if(s.equalsIgnoreCase("n")) { flag=false; System.out.println("exit..."); } else { System.out.println("Wrong Command!"); } } } } } // ----------- 向服务器发送信息的类 -------------- class ClientSend extends Thread { private Socket ToSocket; private PrintWriter out; public ClientSend(Socket ToSock) { ToSocket=ToSock; try { out=new PrintWriter(new OutputStreamWriter(ToSocket.getOutputStream()),true); } catch(IOException e) { e.printStackTrace(); } } public void run() // 发送信息到服务器 { String MSG; Scanner StdInput=new Scanner(System.in); MSG=StdInput.nextLine(); while(true) { if(MSG.equalsIgnoreCase("exit") || MSG.equalsIgnoreCase("quit")) { out.println(MSG); break; } out.println(MSG); MSG=StdInput.nextLine(); } } } // ----------- 接收服务器发来的信息的类 -------------- class ClientReceive extends Thread { private Socket FromSocket; private BufferedReader in; public ClientReceive(Socket sock) { FromSocket=sock; try { in=new BufferedReader(new InputStreamReader(FromSocket.getInputStream())); } catch(IOException e) { e.printStackTrace(); } } public void run() // 接收服务器发来的信息 { try { String MSG=in.readLine(); while(MSG.equalsIgnoreCase("bye")==false) { System.out.println(MSG); MSG=in.readLine(); } in.close(); FromSocket.close(); } catch(IOException e) { e.printStackTrace(); } } }
四、 运行截图
- 服务器端运行截图:
- 客户端运行截图:
客户端1:IP地址 172.16.248.64
客户端2:IP地址 172.16.248.38
客户端3:IP地址 172.16.248.236
3.服务器端没有启动时客户端截图: