代码仅供参考,希望大家不要照抄
第一部分:实现客户与客户之间发送消息不受影响
聊天器项目是我们学院历来的一个传统,确实通过写这个项目我对java网络编程以及监听过程,还有java里边多线程的处理有了一个新的认识,下面我们进入正题。
首先看如何实现客户端的代码吧。
我在写这里的代码的时候从书上参考了一部分通过服务器进行中转的代码,具体的我在代码中加入了一个Socket 数组用于记录客户的数量的变化。代码如下
package fawenjian;
import java.io.*;
import java.net.*;
public class MultiTalkServer {
public static Socket[] member= new Socket[20];//创建一个数组用于存放客户的数量
static int clientnum=0;//静态成员变量 记录当前客户的数量
public static void main(String args[]) throws IOException{
ServerSocket serverSocket=null;
boolean listening=true;
try {
//创建一个ServerSocket在端口4700监听客户请求
serverSocket=new ServerSocket(4700);
//System.out.print("we");
}catch(IOException e) {
System.out.println("Could not listen on port:4700.");//出错,打印出错信息
System.exit(-1); //退出
}
while(listening) { //循环监听
//监听到客户请求,根据得到的Socket对象和客户技术创建并启动服务线程
member[clientnum]=serverSocket.accept();
new ServerThread(member[clientnum],clientnum).start();//保存监听到客户端
clientnum++; //增加客户计数
}
serverSocket.close(); //关闭ServerSocket
}
}
用一个 static int clientnum=0数组用于记录当前客户的数量。
在服务器的代码中,最后经过调试我遇到了空指针异常的问题,由于编译器的问题没有提醒报错的行数,所以我写入了几个输出,最后发现问题出现在循环监听的环节。我犯错误是由于我没有和服务器的Socket端口进行连接。由于我相对于书上的代码加入了一个数组用于控制用户数量的增减。所以我是这么修改的。
member[clientnum]=serverSocket.accept();
new ServerThread(member[clientnum],clientnum).start();//保存监听到客户端
clientnum++; //增加客户计数
一定要注意保存监听到客户端,我当时就是因为这个问题。
接下来说一下客户端
客户端我采用输入编号之后 输入消息那样才能对另一个客户端进行通讯。实现两个客户之间通讯不受影响,这样做的好处我认为可以使对话的逻辑清晰,下面首先我放一下代码,然后之后我再把程序的演示图放在下边。
import java.io.*;
import java.net.*;
public class TalkClient {
public static void main(String args[]) {
try {
//向本机的4700端口发出客户请求
Socket socket=new Socket("127.0.0.1",4700);
//由系统标准输入设备构造BufferedReader对象
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由Socket对象得到输出流,并构造相应的PrintWriter对象
DataOutputStream os=new DataOutputStream(socket.getOutputStream());
//由Socket对象得到输入流,并构造相应的BufferedReader对象
DataInputStream is=new DataInputStream(socket.getInputStream());
//BufferedReader is =new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
System.out.println("输入样例: 编号 消息 ");
while(true)//如果输入读取到bye则停止循环
{
if(sin.ready())//DataOutputStream特有的方法,在不读取的情况下,看缓冲区中是否有可读取的内容
{
readline=sin.readLine();
os.writeUTF(readline);
os.flush();//刷新输出流
if(readline.equals("bye"))
{
break;
}
}
if(is.available()>0)
{
System.out.println(is.readUTF());
}
}
//readline=sin.readLine();//从系统标准输入上读入一字符串
//若读入的字符串为bye则停止循环
//while(!readline.contentEquals("bye")) {
//将读入的字符串输出到Server
// os.println(readline);
// os.flush(); //刷新输出流,使Server马上收到该字符串
//在显示屏上输出读入的字符串
// System.out.println("Client:"+readline);
//从Server读入一字符串,并输出到显示屏
// System.out.println("Server:"+is.readLine());
// readline=sin.readLine();//从系统标准输入读入下一字符串
//}//继续循环
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
} catch(Exception e) {
System.out.println("Error"+e); //在显示屏上输出错误信息
}
}
}
以上分别是服务器端,两个用户端的对话内容。
关于available方法是这样使用的available是一个获取字节长度的方法,例如一个文件有1024个字节,通过available返回的int的值就是1024.在FileInputStream有一个read(byte[] arr)的方法,用来把读取的字节放到一个字节数组中去;在FileOutputStream类下有一个write(byte[] arr)的方法,用来把字节数组写入到文件。所以我采用available方法判断是否有字符串的输入。
最后说一下多线程部分的代码吧。
关于多线程部分,其实当时我进了很多坑,因为我开了一个Socket数组,所以在多线程部分如何应用数组的作用去实现我想要的结果是一个难题。以及需要处理好监听以及模块之间的关系。代码我是这样写的
import java.io.*;
import java.net.*;
public class ServerThread extends Thread{
Socket kehusocket=null;
Socket socket=null; //保存与本线程相关的Socket对象
int number;//用于记录客户的序列号
static int clientnum; //保存本进程的客户计数
public ServerThread(Socket socket,int num) { //构造函数
this.socket=socket; //初始化socket变量
clientnum=num+1; //初始化clientnum变量
number=num;//对客户数量编号
}
public void run() { //线程主体
try{
String write;
//由Socket对象得到输入流,并构造相应的BufferedReader对象
DataInputStream is =new DataInputStream(socket.getInputStream());
while(true)
{
if(is.available()>0)//如果有输入
{
write=is.readUTF();//读取字符串
String []talk=write.split(" ");//以空格作为分隔符分割字符
int xuhao = Integer.parseInt(talk[0]);//这是要收消息的客户端的编号
System.out.println("网友"+number+"将要跟"+xuhao+"说话"+talk[1]);
kehusocket=MultiTalkServer.member[xuhao];
write="网友"+number+" :"+talk[1];
talk[1]=write;//将消息与发消息的标号相对应
DataOutputStream os=new DataOutputStream(kehusocket.getOutputStream());
os.writeUTF(talk[1]);//向客户端输出该字符串
os.flush();//刷新输出流
if(talk[1].equals("bye"))
{
break;
}
}
}//继续循环
//由Socket对象得到输出流,并构造PrintWriter对象
//PrintWriter os=new PrintWriter(kehusocket.getOutputStream());
//int xuhao=Integer.parseInt("");//xuhao是用来记录收消息的客户编号
//System.out.println(number+"想要和"+xuhao+"通话");
//kehusocket=MultiTalkServer.member[xuhao];
//由系统标准输入设备构造BufferedReader对象
//BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//在标准输出上打印从客户端读入的字符串
// System.out.println("Client:"+ number +is.readLine());
//从标准输入读入一字符串
// line=sin.readLine();
// while(!line.equals("bye")){//如果该字符串为 "bye",则停止循环
// os.println(line);//向客户端输出该字符串
// os.flush();//刷新输出流,使Client马上收到该字符串
//在系统标准输出上打印该字符串
// System.out.println("Server:"+line);
//从Client读入一字符串,并打印到标准输出上
// System.out.println("Client:"+ clientnum +is.readLine());
// line=sin.readLine();//从系统标准输入读入一字符串
// }//继续循环
// os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
}catch(Exception e){
System.out.println("Error:"+e);//出错,打印出错信息
}
}
}
我采用了readUTF和writeUTF方法去处理,采用Integer方法加上重新命名一个int型变量的方法去处理数组发送关系的部分。
这个思路是我查询Integer方法之后得到的。我觉得是一个比较好的处理方法,另外一些注释我的代码里有很详细的解释,大家可以参考。
好啦,以上部分就是我们实现客户端与客户端通讯的实例了,因为时间仓促没有形成局域网通讯,不过你可以找一个好朋友和你一起试试在一台电脑上通讯丫。
下面我们该说说发文件的部分了。
第二部分:实现客户之间发送文件
这个部分,其实写出了前边的聊天器部分,这个就已经很容易了。服务器端的代码不需要改变,我们只需要在客户端加入读取文件的部分,以及在多线程的程序中加入对客户端的处理就可以了。其实就是if else就能解决的问题了。
好吧我先把客户端的代码放一下。
import java.io.*;
import java.net.*;
public class TalkClient {
private static BufferedReader fis=null;
public static void main(String args[]) {
try {
//向本机的4700端口发出客户请求
Socket socket=new Socket("127.0.0.1",4700);
//由系统标准输入设备构造BufferedReader对象
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由Socket对象得到输出流,并构造相应的PrintWriter对象
DataOutputStream os=new DataOutputStream(socket.getOutputStream());
//由Socket对象得到输入流,并构造相应的BufferedReader对象
DataInputStream is=new DataInputStream(socket.getInputStream());
//BufferedReader is =new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
System.out.println("输入样例: 编号 消息 ");
while(true)//如果输入读取到bye则停止
{
if(sin.ready())//DataOutputStream特有的方法,在不读取的情况下,看缓冲区中是否有可读取的内容
{
readline=sin.readLine();
if(readline.contentEquals("bye"))
{
break;
}
String []talk=readline.split(" ");//以空格作为分隔符分割字符
int xuhao = Integer.parseInt(talk[0]);//这是要收消息的客户端的编号
if(talk[1].equals("file"))
{
fis=new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\Administrator\\Desktop\\java学习\\聊天器\\more Q\\src\\fawenjian\\test.txt")));
StringBuilder message=new StringBuilder();
message.append(talk[0]+" "+"file.");
while((readline=fis.readLine())!=null)
{
message.append(readline+".");
System.out.println(readline);
}
os.writeUTF(message.toString());
os.flush();
fis.close();
}
// if(talk[1].equals("file"))
// {
//fis=new BufferedReader(new InputStreamReader(new FileInputStream("test.txt")));
// while((readline=fis.readLine())!=null)
// {
// os.writeUTF(talk[0]+" "+readline);//由于在服务器端用截取字符串的首字母来获取所发客户端的序号,并用了Integer强制转换,也就是说所有从客户端输入的字符串的首个字符必须是数字,不然就会出现错误。
// os.flush();//刷新输出流,使Server马上收到该字符串
//在系统标准输出上打印读入的字符串
// System.out.println(readline);
// }}
else
{System.out.println(1);
os.writeUTF(readline);
os.flush();//刷新输出流
}
}
if(is.available()>0)
{
System.out.println(is.readUTF());
}
}
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
} catch(Exception e) {
System.out.println("Error"+e); //在显示屏上输出错误信息
}
}
}
其实我们也能发现确实没有很大的改动,对比上边的代码,只是有一些代码的顺序进行了调整。关于文件的转换我试用了BufferedReader,InputStreamReader和FileInputStream这样的方法就实现了文件的转换。具体的在代码里边有详细的注释。
下边是多线程部分的代码
import java.io.*;
import java.net.*;
public class ServerThread extends Thread{
Socket kehusocket=null;
Socket socket=null; //保存与本线程相关的Socket对象
int number;//用于记录客户的序列号
static int clientnum; //保存本进程的客户计数
public ServerThread(Socket socket,int num) { //构造函数
this.socket=socket; //初始化socket变量
clientnum=num+1; //初始化clientnum变量
number=num;//对客户数量编号
}
public void run() { //线程主体
try{
String write;
//由Socket对象得到输入流,并构造相应的BufferedReader对象
DataInputStream is =new DataInputStream(socket.getInputStream());
while(true)
{
if(is.available()>0)//如果有输入
{
write=is.readUTF();//读取字符串
String []talk=write.split(" ");//以空格作为分隔符分割字符
int xuhao = Integer.parseInt(talk[0]);//这是要收消息的客户端的编号
//System.out.println(number+"将要和"+xuhao+"对话");
kehusocket=MultiTalkServer.member[xuhao];
//write="Client"+number+" :"+talk[1];
//talk[1]=write;//将消息与发消息的标号相对应
DataOutputStream os=new DataOutputStream(kehusocket.getOutputStream());//这个代表文件和客户消息的输出流
write="网友"+number+" :"+talk[1];
os.writeUTF(talk[1]);//向客户端输出该字符串
os.flush();//刷新输出流
String wenjian[]=talk[1].split("\\.");//"."是转义字符在java中要加上\\
if(wenjian.length>1)
{
//System.out.println("网友"+number+"将要给"+xuhao+"发文件"+talk[1]);
for(String m:wenjian)
{System.out.println("网友"+number+"将要给"+xuhao+"发文件"+talk[1]);
os.writeUTF(m);
os.flush();
}
}
else
{
System.out.println("网友"+number+"将要跟"+xuhao+"说话"+talk[1]);
if(talk[1].equals("bye"))
{
System.out.println("网友"+number+"退出聊天器");
break;
}
}
}
}//继续循环
//由Socket对象得到输出流,并构造PrintWriter对象
//PrintWriter os=new PrintWriter(kehusocket.getOutputStream());
//int xuhao=Integer.parseInt("");//xuhao是用来记录收消息的客户编号
//System.out.println(number+"想要和"+xuhao+"通话");
//kehusocket=MultiTalkServer.member[xuhao];
//由系统标准输入设备构造BufferedReader对象
//BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//在标准输出上打印从客户端读入的字符串
// System.out.println("Client:"+ number +is.readLine());
//从标准输入读入一字符串
// line=sin.readLine();
// while(!line.equals("bye")){//如果该字符串为 "bye",则停止循环
// os.println(line);//向客户端输出该字符串
// os.flush();//刷新输出流,使Client马上收到该字符串
//在系统标准输出上打印该字符串
// System.out.println("Server:"+line);
//从Client读入一字符串,并打印到标准输出上
// System.out.println("Client:"+ clientnum +is.readLine());
// line=sin.readLine();//从系统标准输入读入一字符串
// }//继续循环
// os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
}catch(Exception e){
System.out.println("Error:"+e);//出错,打印出错信息
}
}
}
主要改动部分位于while循环中,然后注意下我创建的wenjian String数组就可以了,这是线程中解决这个问题的关键代码部分。最后的运行效果图。
嘿嘿。聊天器也是写了一段时间的,java不愧是应网络时代而生的,大家加油呀,好好学习java。