上一篇 java socket编程(3)——利用socket实现聊天之群聊
中我们讲到如何使用socket让客户端和客户端之间传递消息,实现一对多的聊天,接下来我将写出如何让服务器建立客户端与客户端之间的文件流传输通道。
还是在原有的代码上加以修改,增加功能。
1,服务器增加逻辑
ChatFileServer 接收文件的另一套逻辑,虽然独立出来了,也可以和接收聊天信息的逻辑合并
public class ChatFileServer {
// socket服务
private static ServerSocket server;
public Gson gson = new Gson();
/**
* 初始化socket服务
*/
public void initServer() {
try {
// 创建一个ServerSocket在端口8080监听客户请求
server = new ServerSocket(SocketUrls.PORT);
createMessage();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 创建消息管理,一直接收消息
*/
private void createMessage() {
FileOutputStream fos = null;
try {
System.out.println("等待用户接入 : ");
// 使用accept()阻塞等待客户请求
Socket socket = server.accept();
System.out.println("用户接入 : " + socket.getPort());
// 开启一个子线程来等待另外的socket加入
new Thread(new Runnable() {
public void run() {
// 再次创建一个socket服务等待其他用户接入
createMessage();
}
}).start();
// 用于服务器推送消息给用户
getMessage(socket);
// 从客户端获取信息
InputStream is = socket.getInputStream();
// 从客户端获取信息
BufferedReader bff = new BufferedReader(new InputStreamReader(is));
// 循环一直接收当前socket发来的消息
while (true) {
Thread.sleep(500);
int length = 0;
byte[] b = new byte[1024];
String json = null;
// 1、首先先得到实体类
while ((json = bff.readLine()) != null) {
// json = new String(b);
MessageBean messageBean = gson.fromJson(json, MessageBean.class);
//如果这不是一个文件消息,则不往下执行
if (messageBean.getChatFile() == null)
continue;
System.out.println("接受到的文件名为:" + messageBean.getChatFile().getFileName());
String fileNewName = messageBean.getChatFile().getFileName() + "."
+ messageBean.getChatFile().getFileTitle();
System.out.println("新生成的文件名为:" + fileNewName);
//d盘下创建该文件的空文件对象
fos = new FileOutputStream("D:\\" + fileNewName);
length = 0;
int fileSzie = 0;
// 2、把socket输入流写到文件输出流中去
while ((length = is.read(b)) != -1) {
fos.write(b, 0, length);
fileSzie += length;
System.out.println("当前大小:" + fileSzie);
System.out.println("总大小:" + messageBean.getChatFile().getFileLength());
//这里通过先前传递过来的文件大小作为参照,因为该文件流不能自主停止,所以通过判断文件大小来跳出循环
if (fileSzie == messageBean.getChatFile().getFileLength()) {
break;
}
}
fos.close();
System.out.println("文件:保存成功");
System.out.println("用户 : " + messageBean.getFriendId());
}
}
// server.close();
} catch (Exception e) {
try {
if (fos != null)
fos.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
System.out.println("错误 : " + e.getMessage());
}
}
/**
* 发送消息
*/
private void getMessage(Socket socket) {
new Thread(new Runnable() {
public void run() {
try {
String buffer;
while (true) {
// 从控制台输入
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
buffer = strin.readLine();
// 因为readLine以换行符为结束点所以,结尾加入换行
buffer += "\n";
// 这里修改成向全部连接到服务器的用户推送消息
OutputStream output = socket.getOutputStream();
output.write(buffer.getBytes("utf-8"));
// 发送数据
output.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
2,客户端增加逻辑
1,ChatListActivity 在原有单聊,群聊的基础上增加上传文件
public class ChatListActivity extends AppCompatActivity implements View.OnClickListener {
private LinearLayout friend_ly, group_ly, file_ly;
//自己在手机根目录放置一个文件
private final String filePath = Environment.getExternalStorageDirectory() + "/" + "test.gif";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chatlist);
friend_ly = (LinearLayout) findViewById(R.id.friend_ly);
group_ly = (LinearLayout) findViewById(R.id.group_ly);
file_ly = (LinearLayout) findViewById(R.id.file_ly);
friend_ly.setOnClickListener(this);
group_ly.setOnClickListener(this);
file_ly.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.friend_ly://单聊
Intent intent = new Intent(ChatListActivity.this, MainActivity.class);
startActivity(intent);
break;
case R.id.group_ly://群聊
Intent intent1 = new Intent(ChatListActivity.this, GroupActivity.class);
startActivity(intent1);
break;
case R.id.file_ly://传文件
ChatAppliaction.chatServer.sendFileMessage(filePath);
break;
}
}
}
2,ChatFileServer 增加上传文件服务逻辑,上传文件的逻辑不能像服务器端代码一样和聊天逻辑合并
public class ChatFileServer {
//文件信息实体类
private MessageFileBean messageFileBean;
private Gson gson = new Gson();
public void sendFileMessage(Socket socket, String filePath, Handler handler, MessageBean messageBean) {
if (TextUtils.isEmpty(filePath)) return;
try {
if (socket == null) {
Message message = handler.obtainMessage();
message.what = 1;
message.obj = "服务器已经关闭";
handler.sendMessage(message);
return;
}
//要传输的文件路径
File file = new File(filePath);
//说明不存在该文件
if (!file.exists()) return;
//说明该文件是一个文件夹
if (file.isDirectory()) return;
messageFileBean = new MessageFileBean();
/**
* 传入的文件长度很重要,服务器判断客户端文件是否结束的重要依据,
* 必须和你传的文件长度一直,
* 如果不一致服务器端则不能自行结束文件流或者提前结束文件流,
*
* 这么做的原因是因为文件流的最后不能像字符流的最后加上\n来让服务器知道已经结束
* 所以手动判断文件传输长度一致以后结束传输
* */
messageFileBean.setFileLength(file.length());
//文件名
messageFileBean.setFileName("test");
//文件id
messageFileBean.setFileId(1);
//文件类型
messageFileBean.setFileType("image");
//文件后缀
messageFileBean.setFileTitle("gif");
//messageFileBean.setFileByte(getBytes(file));
messageBean.setChatFile(messageFileBean);
OutputStream outputStream = socket.getOutputStream();
String json = gson.toJson(messageBean) + "\n";
//1、发送文件信息实体类
outputStream.write(json.getBytes("utf-8"));
//将文件写入流
FileInputStream fis = new FileInputStream(file);
//每次上传1M的内容
byte[] b = new byte[1024];
int length;
int fileSize = 0;//实时监测上传进度
while ((length = fis.read(b)) != -1) {
fileSize += length;
Log.i("socketChat", "文件上传进度:" + (fileSize / messageFileBean.getFileLength() * 100) + "%");
//2、把文件写入socket输出流
outputStream.write(b, 0, length);
}
//关闭文件流
fis.close();
//该方法无效
//outputStream.write("\n".getBytes());
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
Log.e("test", "错误:" + e.toString());
}
}
}
写到这里代码逻辑已经贴出来了。通过客户端就可以将置顶文件上传到服务器,保存在电脑本地;
这里虽然只在服务器处理了,保存文件。但是如果从服务器转发给指定好友,其实逻辑都是一样的,有心的人可以自己去完善。
这里把主要代码贴出来了,以便大家理解。
既然能够上传文件了,那么,发送语音,传图片,小视频,传文件的功能都能实现了。聊天sdk环信的源码中发送语音之类的功能其实都是传的文件,只不过标识不同而已。
总结:
通过socket来实现聊天的基本功能就这些了。1,消息推送;2,单聊;3,群聊;4,传文件;
当然,还有更高级的做法,比如NIO之类的。这些东西到实际项目用到的时候去优化也是可以的。
最后,附上socket传文件的Demo