资源描述
课程设计指导书
姓
名
宣红东
宛立生
学
号
09838024
09838027
班
级
一
班
课程名称
Java课程设计
课程性质
设计时间
2010年 11月4日—— 2010年12月31日
设计名称
聊天小程序
设计目的
本次课程设计是对前面学过的面向对象的编程思想以及编程方法的一个总结、回顾和实践。开始设计前学生一定要先回顾以前所学的内容,明确本次设计所要用到的技术点并到网上搜索以及查阅相关的书籍来搜集资料。通过编写一个基于JAVA的应用系统综合实例,来掌握Java语言编程技巧。
设计要求
1、使用图形用户界面。
2、能实现一个聊天室中多人聊天。
3、可以两人私聊。
设计思路
与
设计过程
思路与原理:服务器端接受客户端的连接请求,同时启动一个线程处理这个连接,线程不停的读取客户端输入,然后把输入加入队列中,等候处理。在线程启动的同时将线程加入队列中,以便在需要的时候定位和取出。
过程:首先设计ChatServer服务端与ChatClinet客户端
1、 服务器(ChatServer),使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
2、 客户端(ChatClinet),使用Socket对网络上某一个服务器的 某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。(具体详细的过程步骤及代码见下面)
计划与进度
计划在两个月内完成任务,首先在前半个月内(11月4日--11月19日),构思通过网上(图书馆)的知识与课本上的相关内容的了解与认识,确认好选择的课题与怎样设计,基本的思路想好。十五天(11月19日--12月5日)内参考资料争取完成服务端的程序,在12月5日--12月20日完成客户端的程序设计,在12月20日—12月30日内完成实现报告的填写工作。在12月31日全面完成整个课题,并上交给老师。
任课教师
意 见
备 注
课程设计报告
课程:
学号:
姓名:
班级
教师:
时间
计算机科学与技术
设计名称:聊天小程序
日期: 2010 年 11月 4 日
设计内容:设计一个在图形界面下,实现一个聊天室中多人聊天,也可以私聊的聊天小程序
设计目的与要求:目的是为了更好的掌握java这门课程的内容,同时更好的理解与掌握socket编程,对所有面向对象的编程思想以及编程方法的一个总结、回顾和实践,掌握Java语言编程技巧。要求使用图形用户界面。能实现一个聊天室中多人聊天。可以两人私聊。(提示:使用socket通信)程序描述清晰准确,思路清晰,程序能够实现相应的功能。
设计环境或器材、原理与说明、主要完成的功能、设计思路和框图:
设计环境或者器材:j d k或者j c编辑器,笔记本电脑两台,网线三条,小型交换机一个。
原理与说明: 建立服务端与客户端的一个或者多个连接,其中服务器,使用Server Socket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。
主要功能:1、实现服务端与客户端的连接,实现两者之间的信息交换。
2、实现了一个服务器可以与多个客户端实现连接,即实现了一个聊天室里面可以多人聊天。
3、亦可以实现私聊的功能。
设计思路与框图:服务器端接受客户端的连接请求,同时启动一个线程处理这个连接,线程不停的读取客户端输入,然后把输入加入队列中,等候处理。在线程启动的同时将线程加入队列中,以便在需要的时候定位和取出。
大概的框图:
chatServer端 chatclient端
︳
创建ServerSocket对象在某
端口提供监听服务
等待来自chartclient的服务
请求
接受chartclient端的请求用 建立连接 创建Socket对象向server的监听端
返回的Socket建立连接 口请求
通过向Socket中读写数据来 数据通信 通过向新的Socket中读写数据来
与chartclient端通信 与server端通信
关闭Socket结束与当前的 拆除连接 关闭Socket结束与server的通信
client的通信等待其他的请求
关闭ServerSocket对象结束
监听服务
设计过程(步骤)或程序代码(可以加页):
主要代码:服务端ChatServer
class SocketThread extends Thread{ //线程类,用于和每个客户端连接
static Vector<SocketThread> clientVector=new Vector<SocketThread>();//存放每个线程信息
private Socket socket; //存放每个线程的Socket对象
String nick; //存放每个线程的昵称
private boolean firstRead; //用于判断是否为第一次接收数据
SocketThread(Socket s){ //构造函数
socket=s;
nick=null;
firstRead=true;
}
private void noticeDelUser(){ //通告所有线程的客户端,本用户退出
String msg="###"+this.nick;
for(int i=0;i<clientVector.size();i++)
if(clientVector.get(i).nick.equals(this.nick)==false)
sendMsg(i,msg);
}
private void delSocket(){ //关闭socket
noticeDelUser(); //通告所有线程的客户端,本用户退出
clientVector.remove(this); //从clientVector中移除本线程
try{
socket.close();
System.out.println(socket.getInetAddress()+"断开连接!!");
}
catch(IOException closeError){}
}
private void noticeAddUser(){ //通告所有线程的客户端,本用户进入
String msg="##"+this.nick;
for(int i=0;i<clientVector.size();i++)
sendMsg(i,msg);
}
private void requestAllUser(){ //本线程请求其他有用户信息
int index=0;
int i=0;
for(i=0;i<clientVector.size();i++)
if(clientVector.get(i).nick.equals(this.nick))
break;
index=i;
for(i=0;i<clientVector.size();i++)
if(index!=i){
String msg="##"+clientVector.get(i).nick;
sendMsg(index,msg);
}
}
private String msgForward(String msg){ //对接收到的信息转发
String name; //存放目的用户昵称
String tempMsg; //存放生成的转发数据
if(firstRead==true){ //若为第一次收到数据
int i;
for(i=0;i<clientVector.size();i++) //检查昵称是否存在
if(clientVector.get(i).nick.equals(msg)==true)
break;
if(i>=clientVector.size()){ //昵称不存在则:
nick=msg; //在clientVector中添加本线程信息
msg="#您使用的昵称:"+msg; //并通告所有线程的客户端,本用户进入
noticeAddUser(); //以及请求其他有用户信息
clientVector.add(this);
requestAllUser();
firstRead=false;
}
else{
msg="#您输入的昵称已经存在,请重新输入:"; //昵称存在,发送系统提示
}
return msg;
}
if(msg.startsWith("!")==true){ //若客户端发送密语
name=msg.substring(1, msg.indexOf(' ')); //获得目的用户昵称,生成发送信息
tempMsg=this.nick+"悄悄对"+name+"说:\r\n\t"+msg.substring(msg.indexOf(' '));
}
else{
tempMsg=this.nick+":\r\n\t"+msg; //若为全体聊天
name="610665c2a05631a7bc460993bae36ee6"; //此name为特殊值,专指全体聊天
}
if(name.equals("610665c2a05631a7bc460993bae36ee6")==false){//若为密语
int i=0;
for(;i<clientVector.size();i++) //查找目的客户线程在clientVector中的位置
if(clientVector.get(i).nick.equals(name)==true)
break;
if(i>=clientVector.size()){ //若找不到目的客户线程,则返回系统提示
msg="#您所发送的用户名昵称不存在!!";
}
else{
sendMsg(i,tempMsg); //找到目的客户线程并转发
}
}
else{ //为全体聊天,全体转发
int i=0;
for(;i<clientVector.size();i++)
sendMsg(i,tempMsg);
}
return msg;
}
private void sendMsg(int index,String msg){ //向特定客户转发,msg为转发信息
Socket tempSocket=clientVector.get(index).socket; //index为客户在clientVector中位置
try{
PrintWriter pw=new PrintWriter(tempSocket.getOutputStream(),true);
pw.println(msg);
}
catch(IOException printError){}
}
public void run(){ //建立读写流
String msg=null;
InputStreamReader isr=null;
BufferedReader br=null;
try{
isr=new InputStreamReader(socket.getInputStream());
br=new BufferedReader(isr);
}
catch(IOException inOutError){
System.out.println("无法与客户建立流!!");
delSocket();
return;
}
do{
try{
msg=br.readLine();
}
catch(IOException readError){
System.out.println("读取流数据错误!!");
delSocket();
return;
}
System.out.println(msg);
msg=msgForward(msg); //处理读取的数据
if(msg.startsWith("#")==true){ //若有系统提示返回,则返回系统提示
try{
PrintWriter pw=new PrintWriter(socket.getOutputStream(),true);
pw.println(msg);
}
catch(IOException printError){}
}
}
while(true);
}
}
public class ChatServer {
public static void main(String args[]){
ServerSocket serverSocket=null;
Socket socket=null;
SocketThread st;
try{
serverSocket=new ServerSocket(8324); //监听端口8324
System.out.println("服务器启动成功,正在监听8324端口!!");
}
catch(IOException e){
System.out.println("不能使用端口8324,无法启动服务器!!");
return;
}
while(true){
try{
socket=serverSocket.accept(); //获得连接,并建立Socket
}
catch(IOException e){
System.out.println("不能建立连接:"+socket.getInetAddress().toString()+"/"+socket.getPort());
}
st=new SocketThread(socket); //创建新线程
System.out.println("建立连接:"+socket.getInetAddress().toString()+"/"+socket.getPort());
st.start(); //开始新线程
}
}
}
客户端ChartClient
主要代码:
package ch02;
private void initSocket(){ //对socket的初始化
try{
transferSocket=new Socket("localhost",8324); //与服务器建立连接
ReceiveThread rt=new ReceiveThread(transferSocket,this);
rt.start(); //开启新线程用于读取数据流
}
catch(IOException createError){ //出现错误则退出程序
welcomeInfo.setText("无法连接服务器!!");
try{
transferSocket.close();
welcomeInfo.setText("与服务器连接已经断开!!");
System.exit(0);
}
catch(IOException closeError){}
System.exit(0);
} //对输出流的初始化
try{
socketWriter=new PrintWriter(transferSocket.getOutputStream(),true);
}
catch(IOException streamError){
welcomeInfo.setText("流建立错误!!");
try{
transferSocket.close();
welcomeInfo.setText("与服务器连接已经断开!!");
System.exit(0);
}
catch(IOException closeError){}
System.exit(0);
}
}
public void addNewMsg(String msg){ //向chatRecord中追加数据
chatRecord.append(msg+"\r\n");
}
public void setWelcome(String msg){ //改变welcomeInfo值
welcomeInfo.setText(msg);
}
public void windowIconified(WindowEvent e){}
public void windowClosed(WindowEvent e){}
public void windowDeactivated(WindowEvent e){}
public void windowActivated(WindowEvent e){}
public void windowClosing(WindowEvent e){ //窗体关闭事件
System.exit(0);
}
public void windowDeiconified(WindowEvent e){}
public void windowOpened(WindowEvent e){}
public void keyPressed(KeyEvent e){ //对键盘事件的监视
switch(e.getKeyCode()){
case KeyEvent.VK_ENTER : //回车键,就发送信息
sendMsg(); //向服务器发送信息
break;
case KeyEvent.VK_CANCEL: //esc键则,friendList选中第0项
friendList.select(0);
transName.setText("发送给:"+friendList.getSelectedItem().toString());
break;
default :
}
}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e){}
public void itemStateChanged(ItemEvent e){ //监视friendList选中项变化
if(e.getSource().equals(friendList)){ //改变transName值
transName.setText("发送给:"+friendList.getSelectedItem().toString());
}
}
public void textValueChanged(TextEvent e){ //若chatRecod字符多余100000
if(chatRecord.getText().length()>100000){ //则清除部分历史记录,防止占用过大内存
chatRecord.setText(chatRecord.getText().substring(chatRecord.getText().indexOf("\r\n")+2));
}
}
public void actionPerformed(ActionEvent e){ //点击sendButton则发送信息
if(e.getSource()==sendButton){
sendMsg();
}
}
public void sendMsg(){ //向服务器发送信息
if(msgEditor!=null){
if(friendList.getSelectedIndex()==0){ //发送全体聊天信息
socketWriter.println(msgEditor.getText());
}
else{ //发送密语,并回显
socketWriter.println("!"+friendList.getSelectedItem().toString()+" "+msgEditor.getText());
chatRecord.append(this.nick+"对"+friendList.getSelectedItem().toString()+"悄悄说:\r\n\t"+msgEditor.getText()+"\r\n");
}
}
else //若发送信息为空,则系统提示
chatRecord.append("!不能发送空消息\r\n");
msgEditor.setText(""); //将发送信息栏置空
}
}
class ReceiveThread extends Thread{ //监视输入流的类
Socket socket; //连接服务器的socket
ClientWindows clientWindow; //主窗口
ReceiveThread(Socket s,ClientWindows cw){ //初始化
socket=s;
clientWindow=cw;
}
public void run(){
String msg=null;
BufferedReader br=null;
try{ //建立输入流
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
catch(IOException ReadError){
clientWindow.setWelcome("读入流建立错误,程序关闭!!");
try{
socket.close();
clientWindow.setWelcome("与服务器连接已经断开!!");
System.exit(0);
}
catch(IOException closeError){}
System.exit(0);
}
while(true){ //读取数据流的信息
try{
msg=br.readLine(); //获得信息
}
catch(IOException e){
try{
socket.close();
clientWindow.setWelcome("与服务器连接已经断开!!");
System.exit(0);
}
catch(IOException closeError){}
System.exit(0);
}
msg=msgChange(msg); //转变读取到的信息
if(msg!=null)
clientWindow.addNewMsg(msg); //向聊天窗口添加新聊天内容
}
}
private String msgChange(String msg){
if(msg.startsWith("###")){ //删除摸一个用户的信息
for(int i=1;i<clientWindow.friendList.getItemCount();i++){
if(clientWindow.friendList.getItem(i).toString().equals(msg.substring(3)))
clientWindow.friendList.remove(i);
}
clientWindow.friendList.select(0);
return null;
}
else if(msg.startsWith("##")){ //增加某一个用户信息
clientWindow.friendList.add(msg.substring(2));
clientWindow.friendList.select(0);
return null;
}
else if(msg.startsWith("#")){ //系统提示
msg="!"+msg.substring(1);
if(msg.startsWith("!您使用的昵称:"))
clientWindow.nick=msg.substring(8);
}
return msg;
}
}
设计结果与分析(可以加页):
运行ChatServer服务端
,运行ChatClient端的界面如下图:
当打开两个或者多个客户端时:实现多人会话或者一对一的会话 ,界面如下图
私聊即点击想要私聊的用户名即可;
设计体会与建议:
该实验中运用到了一些以前没有了解的东西: 首先是
. Accept方法用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象
实例。"阻塞"是一个术语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,然后
程序继续;通常"阻塞"是由循环产生的
。
. getInputStream方法获得网络连接输入,同时返回一个IutputStream对象实例。
. getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。
注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
其次是:提供固定的服务方是怎么建立的,java提供了ServerSocket支持,创建该类的一
个实例对象并提供一个端口资源就可以创建一个固定的地址。要注意的一点是端口的分配必
须是唯一的。因为端口是为了唯一标示每台计算机的唯一服务。另外端口号是从0—65535
之间的,前面1024已经被tcp/ip所占有,因此所分配的端口只能是1024后面的一些端口。
再就是:简单的程序只能实现两台计算机之间的交流,如何实现多个客户同时访问一个服务
器呢?服务器是通过accept方法同意和客户建立连接的。我们只要在外层建立一个while
循环就可以实现该功能,但是新的问题依然会存在的,那就是虽然解决的多客户的运行,但
是是排队执行的。也就是说一个客户与服务器完成了通讯后,才能进行下次的通讯,这样我
们可以直接从thread类继承下来。并且通过构造函数传递引用和客户Socket建立联系,这
样每个线程都有一个通讯管道,同样我们可以填写run方法,把之前的操作交给线程来运行,
这样多客户并行的Socket就建立了。
建议:我们可以实现传递语音信息,和视屏信息,同时添加一些表情符号等等完善一下该聊天小程序。
设计成绩: 教师签名:
2010 年 12月 31日
展开阅读全文