局域网文件文本在线传输助手
具体实现代码链接
下面我将详细记录,我在完成该项目涉及的具体问题的具体分析实现。

要进行局域网通信首先要做的就是客户端和局域网建立链接。
链接过程
服务器端是无法知道都有哪些客户端要和其建立连接的。所以必须客户端首先拿到服务器的ip和端口号发起建立请求。但是,这也有个要求,就是服务器这个时候已经启动等待客户端发来请求。所以我们在最终启动的时候,应先启动服务器。
客户端:client 为想要连接的客户端的socket。

client = new Socket("127.0.0.1", 6666);

服务器端:
由于服务器的一对多,我们必然面对的就是服务器不能过载。而且,我们的服务器肯定不能同步的进行线性的一个个和客户端完成交流,肯定是一个客户端对应一个线程我在设计的时候采用了固定大小线程池来控制连接的客户端数量。client为连接来的客户端的socket。

serverSocket = new ServerSocket(6666);
Socket client = null;
executorService = Executors.newFixedThreadPool(5);
while(true){System.out.println("等待客户端连接...");client = serverSocket.accept();System.out.println("有新的客户端连接,端口号为:"+client.getPort());executorService.submit(new ExecuteClient(client));
}

客户端的读写分离
首先我们不能够说等待你发数据的时候,你不能够接收数据。这样明显效率不高。我们常用的这些资源传输工具明显不是这样用的。所以多线程不只是应用在客户端和服务器的链接上,也在客户端自身的读操作和写操作上。

readFromServer.start();
sendToServer.start();

用户的登录注册
在用户和客户端建立好连接后,首先客户端要求用户先完成登录注册操作。
典型异常操作:
1.如若用户在未完成登录注册前提下发送数据,服务器不予以响应并客户端返回提示信息,提示用户先完成登录注册
2.如果用户重复使用同一用户名在不同端口登录,则不予以登录注册许可,并返回提示信息,异地重复登录
3.如果用户在不同时间换端口登录,则修改服务器端存储信息,予以登录许可,提示登录成功。
4.如果用户在同一端口重复登录注册,服务器不予以理会。
作为客户端我只需要把数据发给服务器端,而信息头格式判断信息提取信息的操作,我放到了服务器端完成。
针对以上问题,我采用了不同解决策略
1、4问题解决:我使用了一个flag标志位跟随这个客户端对应的线程,来使得这个客户端想要和服务器通信,必须先进入注册模块并且注册成功修改flag的属性,此后直到该用户退出,都不能再次进入该模块。

if (flag == false && strFromClient.startsWith("userName:")) {//注册进入聊天室userName = strFromClient.substring(strFromClient.split(":")[0].length() + 1);if (registerUser() == true) {flag = true;sendToClient.println("用户注册登录成功!n" +"群发消息格式:G:消息内容/F=文件n" +"私法消息格式为:P:用户名-消息内容/F=文件");groupChat("服务器", "用户" + userName + "上线了!");} else {flag = false;sendToClient.println("异地重复登录警告!请重新输入");}

2.一个用户进行注册时我们会将该用户的用户名和socket绑定,存储到我们维护的ConcurrentHashMap中表示当前正在链接的客户端信息。在这个用户注册登录前,会先在哈希桶中判断是否存在同一用户名的信息块,如若有则判断此次登录注册失败,及已经存在相同客户名的异地在线用户,当前申请用户,申请失败。

private boolean registerUser() {if (clientMap.get(userName) != null) {if (clientMap.get(userName).getPort() != client.getPort()) {return false;} else return true;}System.out.println("用户姓名为" + userName + ",端口号为" + client.getPort());clientMap.put(userName, client);return true;
}

3.在一个客户端退出后,维护在我们哈希桶中的数据会被注销删除,也就不会影响到同一用户名的下一次登录。

if (flag == true && strFromClient.equals("C")) {String userName = null;for (String key : clientMap.keySet()) {if (clientMap.get(key).equals(client) == true) {userName = key;break;}}clientMap.remove(userName);System.out.println("用户" + userName + "退出下线");groupChat("服务器","用户" + userName + "退出下线!");break;
}

群发消息
首先客户端将消息发出,在服务器端判断为群发消息后将正文数据和格式头分离后,遍历ConcurrentHashMap拿到他们的socket将数据依次发送出去。

String sendmsg = name + "发来群消息:" + msg;
for(String user:clientMap.keySet()){Socket socket = clientMap.get(user);PrintStream sendToClient = null;try {sendToClient = new PrintStream(socket.getOutputStream(),true,"UTF-8");sendToClient.println(sendmsg);} catch (IOException e) {e.printStackTrace();}
}

私发消息
同样客户端将消息发送给服务器端。服务器提取出优先信息,私聊、接收方用户名、发送内容。首先我们要进行的判断就是该接收方的用户名是否合法,及在线。服务器是不知道这个用户名是不是拼错的,它只能够判断对方是否在线。

if (flag == true && strFromClient.startsWith("P:")) {//私聊String temp = strFromClient.substring(strFromClient.split(":")[0].length()+1);String msg = temp.substring(temp.split("-")[0].length()+1);if(privateChat(temp.split("-")[0],msg)==false){sendToClient.println("该用户未在线");}else{sendToClient.println("发出消息"+msg);}
private boolean privateChat(String name,String msg){
Socket socket = clientMap.get(name);
if(socket == null) return false;
PrintStream printStream = null;
try {printStream = new PrintStream(socket.getOutputStream(),true,"UTF-8");printStream.println("用户"+userName+"发来消息:"+msg);
} catch (IOException e) {e.printStackTrace();
}
}

文件数据的发送
首先文件由于发送的内容并不是用户输入的,用户只是将文件路径名称告诉客户端,告诉客户端我要发送文件在哪。所以需要在客户端将用户输入的文件名转为具体的传输实体数据。
我们首先需要判断该文件名是否存在、是否是文件不是目录。
然后以byte格式读取该文件内容,然后一固定的字符集转为String。为了方便服务器判断。
然后,服务器再以仙童字符集转发给接收方客户端。
接收方客户端,判断是否要接收,接收保存的目录。
然后以相同的字符集进行转byte,在写入文件。
我本次编写文件数据发送的重点,难点就是在传输过程字符集不一致的问题。
发送

tring path = str.substring(str.split("=").length + 2);
String[] paths = path.split("\\");
path = "";
for (int i = 0; i < paths.length - 1; i++) {path += paths[i] + File.separator;
}
path += paths[paths.length - 1];
File file = new File(path);
if (file.exists()) {InputStream readFromFile = new FileInputStream(file);byte[] bytes = new byte[1024];int ret = 1;String words = "";while (ret > 0) {ret = readFromFile.read(bytes);if (ret > 1) {words += new String(bytes, 0, ret,"UTF-8");}}str = str.substring(0, str.split("=").length + 2) + words;
} else {System.out.println("该文件不存在");continue;
}

接收

String words = str.split("=")[1];String change = ReadFromIn.readFromSystemIn(1);if (change.equals("Y")) {//文件写入String path = "";path = ReadFromIn.readFromSystemIn(2);write(words, path);System.out.println("文件写入成功");} else {continue;}
private void write(String words, String path) {String[] paths = path.split("\\");path = "";for (int i = 0; i < paths.length - 1; i++) {path += paths[i] + File.separator;}path += paths[paths.length - 1];byte[] bytes = new byte[0];try {bytes = words.getBytes("UTF-8");//**} catch (UnsupportedEncodingException e) {e.printStackTrace();}System.out.println(path+":"+words);File file = new File(path);if (file.exists()) {file.delete();}try {if (file.createNewFile()) {OutputStream writeToStream = new FileOutputStream(file);writeToStream.write(bytes);}} catch (IOException e) {e.printStackTrace();}
}