要实现通信,首先我们需要一个服务器,然后需要多个客户机。主要方法是通过java.net 包下面的API。创建一个服务器ServerSocket,然后用Socket创建多个客户机。
首先,我们的目地是创建一个简单服务器,能将客户机发来的字符串显示出来,并且再回送给客户
机——有必要解释一下:在这里,服务器指的是等待别人来连接的机器;客户机,当然就指的是主
动去连接别人的机器了,这就像打电话过程中的主叫与被叫的区分一样,一旦连结成功,就不存在
这样谁是客户机谁是服务器的区分了。
package HelloQQxy_0302;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 简单服务器的实现
* @author
*
*/
public class ChatServer {
/**
* 在指定的端口上启动一个服务器
*/
private void setUpServer(int port){
try{
//建立绑定在指定端口上的服务器对象
ServerSocket server=new ServerSocket(port);
System.out.println("服务器创建成功!"+port);
while(true){
//让服务器进入等待状态:阻塞状态
Socket client=server.accept();
//从连接对象上得到输入输出流对象
OutputStream out=client.getOutputStream();
InputStream ins=client.getInputStream();
String s="hello,welcome to our chatroom\r\n";
byte[] data=s.getBytes();//取得组成这个字符串的字节
out.write(data);//用输出对象发送数据!
out.flush();//强制输出
int in=0;//一个一个字节读取客户机的输入函数
while(in!=13){//如果读到的不是13,即回车字符
in=ins.read();
System.out.println("读到是:"+in);
}
System.out.println("客户机按了回车,退出:"+in);
client.close();//关闭与客户机的连接
}
}
catch(Exception ef){
ef.printStackTrace();
}
}
//主函数
public static void main(String[] args){
ChatServer cs=new ChatServer();
cs.setUpServer(1000);
}
}
然后在 cmd 下输入telnet localhost 1000 这里的telnet 是连接的意思,localhost是本地的IP地址 1000是计算机的端口号,这样就实现了通信之间的连接。
<!--StartFragment -->然后就是,传文件。我们组自定义文件的写入格式为
dous.writeInt(type);//文件类型
dous.writeInt(size);//文件名长度
int filesize = ins.available();//输入流取出文件的长度
byte[] filedatalen=new byte[filesize];
ins.read(filedatalen);
dous.writeInt(filesize);//文件总长度
dous.write(filename);//文件名
dous.write(filedatalen);//文件数据组
总的来说我们只要记住一点how to write ,how to read就行
下面是具体代码
package ChatServer0713_xy;
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 简单服务器实现
* 1.根据协议,接受解析文本聊天消息
* 2.根据协议,接受解析文件传送消息
* @author
*
*/
public class ChatServer {
//启动主函数
public static void main(String[] args){
ChatServer cs=new ChatServer();
cs.setUpSever(8080);
}
/**
* 在指定端口上启动一个服务器
* @param port:服务器所用的端口
*/
private void setUpSever(int port) {
try {
//1.建立绑定在指定端口上的服务器对象
ServerSocket server=new ServerSocket(port);
System.out.println("服务器创建成功!"+port);
//2.让服务器进入等待状态:阻塞状态
//3.当有客户机连接上来时,等待方法就会返回,返回一个代表与客户机连接的对象
while(true){//让服务器进入循环等待状态
Socket client=server.accept();
System.out.println("Incoming client"
+client.getRemoteSocketAddress());
//调用处理连接对象的方法去处理连接
processChat(client);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//处理客户机进入的连接对象
private void processChat(Socket client) {
try {
System.out.println("hi");
//得到一个输入、输出流对象
OutputStream out=client.getOutputStream();
InputStream ins=client.getInputStream();
//将输入流包装为DataInputStream方便读取原始类型的流
DataInputStream dins=new DataInputStream(ins);
while(true){
//开始读取数据:每一条消息,总是以一个int开头
//1.读取消息长度,readInt()方法底层从流中读取4个字节,组成一个int
int totalLen=dins.readInt();
System.out.println("***********进入一条消息总长: "+totalLen);
//2.读取消息类型表示,只读取一个字节
byte flag=dins.readByte();
System.out.println("消息接受的类型为:"+flag);
//3.读取目标客户号码,一个int
int destNum=dins.readInt();//消息头解析完毕
System.out.println("消息接受目标用户号 是:"+destNum);
//根据消息的类型,读取消息体部分
if(flag==1){//类型为1,是文本聊天消息,按其规则读取
//创建对应消息体部分字节的长度的数据
byte[] data=new byte[totalLen-4-1-4];
//从流中读取DATA.length个字节放入数组中
dins.readFully(data);
String msg=new String(data);//转换成字符串
System.out.println("发给文本给:"+destNum+"类容是"+msg);
}
else if(flag==2){
//文件数据包体解析
System.out.println("发给文本给:"+destNum);
byte[] data=new byte[256];
dins.readFully(data);//读取256个字节作为文件名字
//解析出文件名字,并除去末尾空格
String fileName=new String(data).trim();
System.out.println("读到的文件名字是:"+fileName);
//余下的字节就是文件类容
data=new byte[totalLen-4-1-4-256];//文件字节数据总长
dins.readFully(data);//读入文件的字节
//保存文件到当前目录下:
FileOutputStream fous=new FileOutputStream(fileName);
fous.write(data);
fous.flush();
fous.close();
System.out.println("文件保存完成!");
}else{
System.out.println("收到未知数据包:"+flag);
client.close();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面是客户机的代码
package ChatServer0713_xy;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 简单客户端实现
* 1.根据协议,发送文本聊天消息
* 2.根据协议,发送文件传送消息
* @author
*
*/
public class ChatClient {
private DataOutputStream dous;//输入流对象
/**
* 向流中写入定长字节,如果不足,补二进制0
* @param out:要写入的流
* @param str字符串
* @param len要写入的长度
*/
private void writeString(DataOutputStream out,String str,int len){
try {
byte[] data=str.getBytes();
out.write(data);
//假设都是短,需要补0
while(len>data.length){
out.writeByte('\0');//补二进制0
len--;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 发送一条文本消息
* @param msg:消息内容
* @param destNum:接受者号码
* 打包过程
*/
private void sendTextMsg(String msg,int destNum){
//System.out.println("1");
try {
// System.out.println("2");
byte[] strb=msg.getBytes();//得到消息的字节数
int totalLen=4+1+4+strb.length;
System.out.println("发送总长度为:"+totalLen);
dous.writeInt(totalLen);//总长
dous.writeByte(1);//类型:1为文本消息
dous.writeInt(destNum);//用户号
dous.write(strb);//写入消息内容
dous.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//总长
}
/**
* 发送一个文件数据包
* @param fileName:文件的绝对路径名
* @param destNum:用户号
* @throws IOException
*/
private void sendFileMsg(String fileName,int destNum) throws IOException{
//根据文件名创建文件对象
File file=new File(fileName);
//根据文件对象,构造一个输入流
InputStream ins=new FileInputStream(file);
int type=2;
dous.writeInt(type);
byte[] filename = file.getName().getBytes();
int size=filename.length;
dous.writeInt(size);
int filesize = ins.available();
byte[] filedatalen=new byte[filesize];
ins.read(filedatalen);
dous.writeInt(filesize);
dous.write(filename);
dous.write(filedatalen);
dous.flush();
/**
* int fileDataLen=ins.available();//文件数据总长
int totalLen=4+1+4+256+fileDataLen;
dous.writeInt(totalLen);
dous.writeByte(2);//类型是2,即文件数据包
//写入目标用户号
dous.writeInt(destNum);
//文件名:得到文件的短名字
String shortFileName=file.getName();
writeString(dous,shortFileName,256);
//写入文件名,不足256个长度时,补\0
* byte[] fileData=new byte[fileDataLen];
ins.read(fileData);
dous.write(fileData);//写出到服务器的流
*/
}
/**
* 连接上服务器
* @param ip:服务器ip
* @param port:服务器端口
*/
public void conn2Server(String ip, int port){
// 创建一个到服务器端的Socket对象
try {
Socket client = new Socket(ip, port);
// 得到输入输出流对象
InputStream ins = client.getInputStream();
OutputStream ous = client.getOutputStream();
//将输出流包装为DataOutputStream对象
dous=new DataOutputStream(ous);
int testCount=0;
while(true){
System.out.println("登录服务器成功,请选择你要发的类型(1:聊天 2:文件:");
//让用户从命令行输入要发送的文件的名字
Scanner sc=new Scanner(System.in);
int type=sc.nextInt();
if(type==1){//发文本
sendTextMsg("abc聊天类容"+testCount,8080);
}
if(type==2){//发文件,祝这个文件必须存在
sendFileMsg("F:\\a.txt",8080);
}
testCount++;
}
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
ChatClient qqc = new ChatClient();
qqc.conn2Server("localhost", 8080);
//qqc.conn2Server("192.168.0.185", 8080);
}
}
其次,我们要注意
第一 ,将 InputStream 包装为DataInputStream 流对象 :
InputStream ins = client.getInputStream();
//将输入流包装为DataInputStream方便读取原始类型的流
DataInputStream dins=new DataInputStream(ins);
如上代码中将从Socket上得到的输入流包装成为DataInputStream流对象,随后如果调用dins的readByte()时,只会从底层的数据流中读取一个字节返回:而调用readInt()时,方法内部经过位运算 实现了将读到的 4个字节 组成一个 int型数据返回。
所以读取数据,读几个字节,是什么类型,必须按照与客户端所准守的协议执行,如果多读或少读,都会导致通信出错。
第二read()方法与readFully()方法的区别:
byte[] data=new byte[totalLen-4-1-4];
//从流中读取data.length个字节放入数组中
dins.readFully(data);
此处从流中读取字节,填充字节数组时,没有使用read(要填充的数组)方法,而是调用readFully()方法,这样做更为安全。网路通信中,当发送大的数据量时,有这样一种可能:一部分数据已发送到对方,有一部分数据还在本地的网卡缓存中,如果调用read()方法,可能会提前返回而没有读到足够的数据,在传大块数据(如一次传送一个较大文件时)可能出错,而readfully()方法会一直等待,读取到数组长度的所有数据,才会返回。