通信协议及简单聊天室构建

通信最基本的也就是一个输入流一个输出流,InputStream 和 OutputStream 。这样一开始我们写了一个最基本的服务器接着一步一步实现功能。
1、就实现一个简单的服务器,可以跟系统的最简单的客户端进行聊天

java.net.ServerSocket ss=new java.net.ServerSocket(8888);
java.net.Socket s=ss.accept();
//输出流
java.io.DataOutputStream ds=new java.io.DataOutputStream(s.getOutputStream());
//输入流
java.io.DataInputStream di=new java.io.DataInputStream(s.getInputStream());
String sss="你好、、、、";
ds.write(sss.getBytes());
int t=di.read();
while(t!=13){
System.out.println(t);
t=di.read();
}
s.close();

只是简单的包括一个输入流一个输出流,在输出流中把自己传的内容构成的byte数组传过去。读入的话就一次读一个字节,当为回车键时停止。
2、我可视化了这个服务器,添加了一个创建服务器按钮,在点击这个按钮时先取得输入框中的你输入的端口号值,创建ServerSocket对象,并且在这里启动一个输入流的线程,这就意味着创建服务器的同时就可以接收客户端发来的值,并且把这个值setText到界面上。发送按钮就是先取得界面上用户输入的值,再把它写出去。最后的关闭按钮就是关掉每个线程的输入输出流。

if("创建".equals(e.getActionCommand())){
String sl=tt.jt2.getText();
if(sl.length()==0){
javax.swing.JOptionPane.showMessageDialog(tt, "端口值不能为空");
System.exit(0);
}
int s2=Integer.parseInt(sl);
try {
//创建服务器
java.net.ServerSocket ss=new java.net.ServerSocket(s2);
s=ss.accept();
String sss="你好、、、、";
writeMsg(s,sss);
//这个线程用来启动输入流
readThread tc=new readThread(s,tt);
tc.start();
} catch (Exception ef) {
ef.printStackTrace();
}
}
//把我写好的信息发出去
else if("发送".equals(e.getActionCommand())){
String s3=tt.ja2.getText();
s3+="\r\n";
writeMsg(s,s3);
tt.ja2.setText("");//每次发送完毕后都将输入界面清空
}
//退出程序
else if("关闭".equals(e.getActionCommand())){
try {
s.close();
System.exit(0);
} catch (Exception e1) {
e1.printStackTrace();
System.exit(0);
}
}

3、上次的版本有一个Bug,就是点击创建按钮时会等客户端来接收这个Socket对象,如果没有接收的话就会卡死在那,然后我们用了一个线程来把接收Socket放到了这个线程中启动就会很好的改善这个问题。

public void run(){
while(true){
try {
Socket client=server.accept();
readThread rt=new readThread(client,tt);
rt.start();
lis.alist.add(rt);
sleep(10);
} catch (Exception e) {
e.printStackTrace();
}

}
}


并且在这个线程中启动输入流线程。
4、接下来遇到的是只能每次与一个客户机进行聊天,要想实现多个客户端连接就要在同一个服务器上的话就要用到线程了。在启动输入流线程的时候服务器接收到的每个Socket对象就是不同的客户端,

public void run(){
while(true){
try {
Socket client=server.accept();
readThread rt=new readThread(client,tt);
rt.start();
lis.alist.add(rt);
sleep(10);
} catch (Exception e) {
e.printStackTrace();
}

}
}


5.写客户机跟服务器差不多,界面基本一样,也是在得到登陆时现在获取IP地址和端口后实例化socket对象,并且启动一个输入流线程,然后在这里把用户的名字写过去,然后在服务器那边把读取线程改下,循环外面先读一次,也就是对应的用户名。然后在关闭按钮时出了一个问题,也就是你在点了关闭按钮时输入流关闭了,但是线程还在运行,线程中的socket对象此时已为空了,这就要求在输入流中加一个判断,判断socket对象是否为空。

public void run() {
try {
ins = s.getInputStream();
BufferedReader buffReader = new BufferedReader(new InputStreamReader(ins));
while (true) {
if (!s.isClosed()) {
String st = buffReader.readLine();
if (st == null)
return;
String sy = tt.ja1.getText();
st = sy + st + "\r\n";// 获取以前的聊天记录,并把新的记录加上去
tt.ja1.setText(st);// 显示
} else
return;
}
} catch (Exception e1) {
e1.printStackTrace();
}
}



6.其实中间还改过不少版本,因为我用的都是构造器传参,这样程序的耦合性比较差,改动一个地方会有很大问题,要改好多地方才能实现这个功能。我知道这里应该用接口,而且最好把方法封装到一个类中,但是确实还不是怎么会,所以目前没搞出来。

最后的版本中用到了协议,1表示发送文字,2表示发送图片等文件。协议内容就是一开始写入的是整个文件长度,接下来是标示符 cmd 1或2, 1表示文字,2表示图片。在这里判断后就进入不同的写入方法,写入文字时要求读取的时候用的是BufferedReadLine 这个方法是根据根据后面的换行符判断是否为一句话,这样就可以在每句话发送时在后面加上一个/r/n,这样读取的时候就可以一句一句读了。
读取文件时,先弹出一个文件选择框,选择得到文件名,然后用文件读入流FileInputStream fis,把这个读到文件体长度的byte数组中。这个写完标识符后要写文件名字的长度,文件名,消息体内容。这就是一个简单的完整协议了。

// 把我写好的信息发出去
else if ("发送文字".equals(e.getActionCommand())) {
if (tt.ja2.getText() != null) {
String s13 = "服务器说 :" + tt.ja2.getText() + "\r\n";
String sy = tt.ja1.getText();
String s14 = sy + s13;
tt.ja1.setText(s14);
try {
for (int i = 0; i < alist.size(); i++) {
System.out.println("--dd"+alist.size());
alist.get(i).dos.writeInt(4+4+s13.length());
alist.get(i).dos.writeInt(1);
alist.get(i).sendMsg(s13);
}
} catch (Exception e1) {
e1.printStackTrace();
}
tt.ja2.setText("");// 每次发送完毕后都将输入界面清空
} else {
System.out.println("发送值不能为空");
}
}else
//判断发送的为文件,文件协议为先是一个int型的文件总长度,接下来是控制符 2代表图片,在就是名字长度,名字,文件体长度
if("发送图片".equals(e.getActionCommand())){
JFileChooser jfs=new JFileChooser();
jfs.setFileSelectionMode(JFileChooser.FILES_ONLY);
jfs.showOpenDialog(null);
File file=jfs.getSelectedFile();
String fileName=file.getAbsolutePath();
java.io.FileInputStream fis;
byte[] fileByte=null;
try {
fis=new java.io.FileInputStream(fileName);
int fileLen=(int)file.length();
totallenth=4+4+4+fileName.getBytes().length+fileLen;
fileByte=new byte[fileLen];
fis.read(fileByte);
} catch (Exception e1) {
e1.printStackTrace();
}
try {
for (int i = 0; i < alist.size(); i++) {
alist.get(i).dos.writeInt(totallenth);
alist.get(i).dos.writeInt(2);
alist.get(i).dos.writeInt(fileName.getBytes().length);
alist.get(i).sendMsg(fileName);
alist.get(i).dos.write(fileByte);
}
}catch(Exception ef){
ef.printStackTrace();
}
try {
Document doc = tt.ja1.getDocument();
ByteArrayInputStream bis = new ByteArrayInputStream(fileByte);
BufferedImage image = ImageIO.read(bis);
ImageIcon icon = new ImageIcon(image);
tt.ja1.setCaretPosition(doc.getLength());
tt.ja1.insertIcon(icon);
} catch (Exception ed) {
ed.printStackTrace();
}
}

然后客户机跟服务器的读入读出流都要严格根据协议走,哪一个地方不行都会出错。
通信其实代码不难,但是要统一协议,加上传参就比较麻烦了,而且出一点错都不行。
这时候就更体会得到代码的规范性的重要性,如果能用到接口和方法的封装性就会好很多。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值