1.对于Java聊天器的实现是参照15.4的基础上做出的修改,主要就是实现客户与客户之间的单独通信,以及文件的传输功能的实现。客户与客户之间的单独通信通过服务器的转发对的过程,在充分理解15.4的基础上,就能做出改变,增加储存成对的socket套接字就能识别这个功能。值得注意的是在修改15.4代码的基础上,不能使用PrinterWriter来实现输出流的过程,PrinterWriter在传送消息的时候会有阻塞的情况,因此我用了DataOutputStream来实现输出流。在储存socket的时候,我使用了对象数组来储存。(Java面向对象程序设计袁绍欣版)
客户端
package javaliaotianqi;
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);
System.out.println(socket);//输出本客户端的编号,方便观察,可删除
//由系统标准输入设备构造BufferedReader对象
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由Socket对象得到输出流,并构造DataOutputStream对象
DataOutputStream os=new DataOutputStream(socket.getOutputStream());
//由Socket对象得到输入流,并构造相应的DataInputStream对象
DataInputStream is=new DataInputStream(socket.getInputStream());
String readline;
System.out.println("样例输入:编号 消息 example:1 hello");
while(true){//若从标准输入读入的字符串为 "bye"则停止循环
//发送消息或文件部分
if(sin.ready())//DataOutputStream特有的方法,在不读取的情况下,查看缓冲区是否有可读取的内容
{//如果键盘有输入,即先发消息
readline=sin.readLine();
os.writeUTF(readline);;
os.flush();//刷新输出流,使Server马上收到该字符串
String []take=readline.split(" ");
String[]wf=take[1].split("\\.");
if(wf.length>1) {
File file=new File(take[1]);//take【1】中已经存在文件名,打开文件
FileInputStream fis=new FileInputStream(file);//通过文件流读取文件对象
long l=file.length();
int len,n;
if(l%1024==0) len=n=1024;
else len=n=1023;
byte[]sendBytes=new byte[n];
//int len=fis.read(sendBytes,0,sendBytes.length);
while (len>=n) {
len=fis.read(sendBytes, 0, sendBytes.length);
os.write(sendBytes, 0, len);
os.flush();
}
fis.close();
System.out.println("文件发送成功");
}
else
if(take[1].equals("bye"))
{
break;
}
}
//接收消息或文件
if(is.available()>0)//如果先收消息,也是先available()查看输入流的长度(未读取的情况下)
{
//System.out.println(is.readUTF());
String getmessage=is.readUTF();
System.out.println(getmessage);
String[]take1=getmessage.split(" ");
String[] take2=take1[2].split("\\.");//\\为转义字符
if(take2.length>1) {
String name ="C:\\"+take1[2];//为储存文件的地址,可以自行修改
File file1=new File(name);
if(!file1.exists()) {
file1.createNewFile();
System.out.println("文件已新建");
}
else {
System.out.println("文件存在或文件创建失败");
}
FileOutputStream fos=new FileOutputStream(name);//通过文件输出流将文件内容输出缓冲区
int len1=1024;
byte []data=new byte[len1];
while(len1>=1024) {
len1=is.read(data,0,len1);
fos.write(data,0,len1);
fos.flush();
}
System.out.println("uploading file is sucessful");
fos.close();
}
}
} //继续循环
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
System.out.println("您已退出系统");
}catch(Exception e) {
System.out.println("Error:"+e); //出错,则打印出错信息
}
}
}
文件传输的实现就是通过文件流来实现的,将所要传输的文件转换为比来实现传输,但是这个传输的过程一般不会传输单位,我是以1024比特为小单位来实现传输的。
服务器端
package javaliaotianqi;
import java.io.*;
import java.net.*;
public class MultiTalkServer{
public static Socket[] socket=new Socket[100];//建立Socket数组,这里简单编写,用静态数组
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);
}catch(IOException e) {
System.out.println("Could not listen on port:4700.");
//出错,打印出错信息
System.exit(-1); //退出
}
while(listening){ //循环监听
//监听到客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动之
socket[clientnum]=serverSocket.accept();//保存监听到的客户端,这步很重要
new ServerThread(socket[clientnum],clientnum).start();
clientnum++; //增加客户计数
}
serverSocket.close(); //关闭ServerSocket
}
}
服务器端在15.4的基础上增加了储存socket的过程,定义了一个socket类型的数组来储存,在与想要通信客户通信是通过输入想要通信这的编号就能实现通信了。
线程
package javaliaotianqi;
import java.io.*;
import java.net.*;
public class ServerThread extends Thread{
Socket lssocket=null;
Socket socket=null; //保存与本线程相关的Socket对象
int p;//用来记录这是第几个客户端
static int clientnum; //保存本进程的客户计数
public ServerThread(Socket socket,int num) { //构造函数
this.socket=socket; //初始化socket变量
clientnum=num+1; //初始化clientnum变量
p=num;//这里是从0开始编号客户端
}
public void run() { //线程主体
try{
String line;
DataInputStream is=new DataInputStream(socket.getInputStream());
while(true){
if(is.available()>0)//查看缓冲区输入流
{
line=is.readUTF();//读取字符串,格式example: 1 aaa
String[] talk=line.split(" ");//以空格为分隔符分割字符串
int number = Integer.parseInt(talk[0]);//这是要收消息的客户端的编号
//System.out.println(p+" is talk to "+number);
lssocket=MultiTalkServer.socket[number];//取到对应套接字
DataOutputStream os=new DataOutputStream(lssocket.getOutputStream());
line="Client "+p+": "+talk[1];//把消息连接上发消息的编号,方便阅读
os.writeUTF(line);//向客户端输出该字符串
os.flush();//刷新输出流,使Client马上收到该字符串
String[]send=talk[1].split("\\.");
if(send.length>1) {
System.out.println(p+" is send file to "+number+": "+talk[1]);
int len=1024;int n=1024;
byte[]data=new byte[n];
while(len>=n) {
len=is.read(data,0,n);//输入一段数据后,接着就输出一段数据,所以是从0开始
os.write(data, 0, len);
os.flush();
}
}
else {
System.out.println(p+"is talk to "+number+": "+talk[1]);
if (talk[1].equals("bye")) {
System.out.println("客户"+p+"已退出系统");
break;
}
}
}
}//继续循环
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
}catch(Exception e){
System.out.println("Error:"+e);//出错,打印出错信息
}
}
}
线程的实现在15.4线程的基础上增加了字符串处理的过程,因为在实现通信的过程中,需要输入想要通信人的编号所以需要字符串处理的过程。
弊端
1.储存socket的过程中可以使用Map来实现,这样就能很好的实现socket套接字的,也能通过输入客户姓名来实现识别想要与之通信的客户。 2.在与客户通信的过程是要不断的指定用户编号的,这样实现的让人感觉的是很难受的。 3.在实现文件传输的时候需要指定发送的文件,以及发送文件的存储位置的问题,不知道如何来解决。