聊天室项目:

分析:

/*** 小项目:聊天室步骤分析;*       客户端:*           main:*              01> 使用TCP编程,创建客户端的Socket对象; *                02> 创建键盘输入对象,用来输入用户名;*               03> 创建通道内的流对象(输入流,输出流);*             04> 因为有对人聊天,所以需要不断的注册用户(while循环);*               05> 循环里面键盘输入用户名(username),将用户名发送给服务器端,用来判断用户名是否重复;*              06> 使用new String()方法用来接收服务器的反馈;*             07> 在客户端进行判断,如果反馈的字符串为"yes"那么用户注册成功,退出循环,否则注册失败,继续注册;*             08> 开启子线程:客户端用来读取服务器端反馈消息的子线程;*              09> 使用while循环和switch语句创建一个简单的菜单,每一个选项实现一个功能;*                10> 整体用try-catch进行异常处理(注意:关闭Socket对象的时候,会抛出一个异常,因为会影响界面效果,所以会加一个catch,抛出SocketException异常,进行空处理);*               11> 在switch语句中,每一个功能都会实现一个方法,用来约定每一种功能的消息格式,并且拥有功能的结束条件;*            *           ClientThread:(参数:InputStream)*              01> 因为要不断接收服务器端的数据,所以需要一个while循环;*               02> 接收数据;*               03> 将接收的字符串以":"进行分割,得到一个字符串输出;*                04> 得到的msgs[0]:表示发送者;*               05> 得到的msgs[1]:表示消息的内容;*             06> 得到的msgs[2]:表示消息类型;*              07> 得到的msgs[3]:表示进行这个操作的时间好眠值;*              08> 使用一个工具类,将时间毫秒值,进行转换;*                09> 在根据消息类型,进行if判断,得到相应功能的的输出操作;*        *       服务器端:*          main:*              01> 创建服务器端的Socket对象;*                02> 创建HashMap集合,用来存储用户名,以及它所对应的Socket对象;*                03> 因为需要不断的检测用户名,所以这里需要用while循环;*                04> 循环中,先监听客户端,之后开启检测用户名的SaveUserThread的线程;*             05> 给整个程序抛一个IOException异常;**         SaveUserThread:(参数:Socket,HashMap<String, Socket>)*               01> 创建输入输出流对象,用来接收用户名(因为需要不断的接收,所以需要while循环);*               02> 接收客户端传来的用户名,将用户名,与HashMap集合的健进行比较,没有就将用户名,与它所对应的Socket添加到集合中;*               03> 还要给客户端进行反馈,保存成功反馈为"yes",否则为"no";*                04> 检测完之后需要实现一个功能:上线提醒;*             05> 遍历HashMap集合,给排除自己的所以人发送一句话;*             06> 但是这句话还不能直接打印到显示屏上,需要在自己的客户端进行读取和转换;*             07> 创建一个子线程ServeThread,让服务端的读取客户端消息的子线程;*                08> 将整个程序进行异常处理;**           ServeThread:(参数:Socket,HashMap<String,Socket>,String username)*               01> 创建输入输出流对象;*              02> 使用while循环,不断的读取客户端发来的消息;*                03> 读取消息,将所得到的字符创赋给msgStr(String类型);*                04> 拆分消息(以":"进行拆分),得到一个字符数组String[] msgs;*             05> 得到的msgs[0]:表示接收者;*               06> 得到的msgs[1]:表示消息的内容;*             07> 得到的msgs[2]:表示消息类型;*              08> 使用System.currentTimeNillis()得到现在的时间毫秒值;*             09> 之后进行if条件的判断(根据消息的类型,进行判断);*              10> 根据不同的类型,将消息进行重组,发送给需要发送的客户端;*                11> 里面需要用到HashMap集合的get()方法,得到接收者的Socket对象;*             12> 整个程序实现一个异常处理;*/

代码实现:
                客户端主线程:

package chatroom.client;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;import chatroom.client.utils.InputAndOutputUtil;
import chatroom.client.utils.InputUtil;
import chatroom.configs.Configs;//客户端/*** 当前程序设计不合理 原因: 将客户端,以及服务端,发消息和读消息都写在一个类中,可能会出现问题:消息阻塞的现象,* 客户端发送端消息,如果内容够大,服务器端没有加载完,客户端还需要服务器端反馈,这个可能出现问题!* * 并且Java开发原则:低耦合,高内聚, 可以将部分内容放到子线程中,关键是把发送消息放在子线程还是读消息放在子线程中呢?* 一般情况:将读消息放在线程,因为在 子线程中,一般不键盘录入的!* * 改进方案:客户端和服务器端分别开启两个读消息的子线程中*/
public class ClientChatRoom {private static Scanner sc;private static InputStream in;// private static ObjectInputStream in ;private static OutputStream out;// private static ObjectOutputStream out ;public static void main(String[] args) {try {// 使用TCP编程// 创建客户端的Socket对象Socket s = new Socket("192.168.159.1", 8888);//创建通道内的流对象,用来传送用户名,以及接收服务器的反馈out = s.getOutputStream();in = s.getInputStream();// 创建键盘录入对象,输入用户名sc = new Scanner(System.in);// 客户端注册用户名// 不断的注册while (true) {System.out.println("请您输入您要注册的用户名:"); // 张三 ---->数组角标越界String username = sc.nextLine();// 使用通道内的输出流,将用户名写给服务器端out.write(username.getBytes());// 接收服务器端的反馈// 读取保存用户名线程的一个反馈byte[] bys = new byte[1024];int len = in.read(bys);String fkName = new String(bys, 0, len);//进行判断if (fkName.equals("yes")) {System.out.println("用户名注册成功....");break;} else if (fkName.equals("no")) {System.out.println("用户名已经存在,请重新注册");}}// 开启客户端的子线程ClientThread ct = new ClientThread(in);ct.start();// 给用户提供选择// 定义一个变量:boolean flag = true;while (flag) {System.out.println("请输入您的选择:1 私聊,2 公聊 3 在线列表 ,4 退出  ,5 发送文件 ,6 在线隐身");// int num = sc.nextInt() ;// 使用工具类InputUtil中的方法inputIntType(),用来判断键盘的输入是否满足要求int num = InputUtil.inputIntType(new Scanner(System.in));// 使用switch语句,给用户提供选项switch (num) {case 1:// 私聊privateTalk();break;case 2:// 公聊publicTalk();break;case 3:// 在线列表getOnList();break;case 4:// 退出 在客户端应该显示谁谁下线了....exitTalk();// 修改flag变量flag = false;// 跳出菜单 为了关闭Socket对象// 停掉子线程ct.flag = false;break;case 5:// 发送文件sendFile();break;}}s.close(); // 关闭客户端的Socket对象} catch (SocketException e) { // 关闭socket对象的时候,本身就抛出一个异常,会将SocketException异常打印控制台上,不好看// 为了让控制台不出现异常的消息字符串,所以空处理!// 空处理} catch (IOException e) {e.printStackTrace();} finally {}}// 发送文件private static void sendFile() throws IOException {// 思路:// 将发送的消息以及文件通过某种方式拼接成一个大的字节数组发送过去// 规定:发送整个字节的大小10kb,文件内容可能不够10kb,用空字节补齐// 用空字节数组,服务器端读到内容,去除两端空格System.out.println("请输入目标用户:");String mbUser = sc.nextLine();System.out.println("请输入文件路径:");String filePath = sc.nextLine();// 将文件封装成File对象File file = new File(filePath);// 组装消息: 接收者:消息内容:消息类型 (文件:文件名称,和文件的大小)String msg = mbUser + ":" + (file.getName() + "#" + file.length()) + ":" + Configs.MSG_FILE;// 将msg转成消息字节数组byte[] msgBytes = msg.getBytes();// 空字节数组byte[] emptyBytes = new byte[1024 * 10 - msgBytes.length];// 获取文件的大小字节数组byte[] fileBytes = InputAndOutputUtil.readFile(filePath);// 将上面三个字节数组在内存中拼接成一个大的字节数组--->发送过去// 创建内存操作流(针对文件不宜过大)ByteArrayOutputStream bos = new ByteArrayOutputStream();// 将上面字节字节写到对象对象中bos.write(msgBytes);bos.write(emptyBytes);bos.write(fileBytes);// 内存操作流中已经有这几个字节数组// public byte[] toByteArray()byte[] allBytes = bos.toByteArray();out.write(allBytes);}private static void exitTalk() throws IOException {// 思路:// 客户端要做的事情:停掉客户端所在的Socket,并且将读取服务器端转发消息的子线程停掉(ClientThread)// 服务器端要做的事情:停电服务器端读取消息的子线程ServetThread,并且将用户从集合中移出用户自己(username),给其他人XX 下线了..// 发送的消息格式:接收者:消息内容:消息类型String msg = "null" + ":" + "null" + ":" + Configs.MSG_EXIT;out.write(msg.getBytes());}// 在线列表private static void getOnList() throws IOException {// 约定的消息格式:接收者:消息内容:消息类型// 组装消息// 发送消息到服务器读取消息的子线程中String msg = "null" + ":" + "null" + ":" + Configs.MSG_ONLIST;out.write(msg.getBytes());}// 公聊private static void publicTalk() throws IOException {while (true) {// 约定的消息格式:接收者:消息内容:消息类型System.out.println("当前您处于公聊模式 消息格式  接收者:消息内容:消息类型  -q 退出当前模式 ");String msg = sc.nextLine();if ("-q".equals(msg)) {break;}// 组装消息 接收者:消息内容:消息类型msg = "null" + ":" + msg + ":" + Configs.MSG_PUBLIC;// 使用流对象发送过去out.write(msg.getBytes());}}// 私聊private static void privateTalk() throws IOException {while (true) {// 约定的新的消息格式 接收者:消息内容:消息类型System.out.println("当前您处于私聊模式 消息格式  接收者:消息内容:消息类型  -q 退出当前模式 ");String msg = sc.nextLine();if ("-q".equals(msg)) {break;}// 约定消息格式:接收者:消息内容:消息类型// 客户端组装消息msg = msg + ":" + Configs.MSG_PRIVATE;// 发送到服务器端out.write(msg.getBytes());}}
}

客户端的读服务器端反馈消息的子线程:

package chatroom.client;import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.SocketException;import chatroom.client.utils.InputAndOutputUtil;
import chatroom.client.utils.TimeUtil;
import chatroom.configs.Configs;//改进之后:客户端的读服务器端反馈消息的子线程
public class ClientThread extends Thread {private InputStream in;public ClientThread(InputStream in) {this.in = in;}// 成员位置定义一个变量boolean flag = true;@Overridepublic void run() {try {// 不断读取while (flag) {// 读取服务器端反馈的消息byte[] bys = new byte[1024 * 10];int len = in.read(bys);// 客户端接收过来的消息格式:发送者:消息内容:消息类型:系统时间String msgStr = new String(bys, 0, len).trim(); // 去除两端空格// 以":"开始拆分消息String[] msgs = msgStr.split(":");String sender = msgs[0];String msgContent = msgs[1];int msgType = Integer.parseInt(msgs[2]);String time = msgs[3];// String--->longlong timeLong = Long.parseLong(time);// 创建Date日期类对象/** Date date = new Date(timeLong) ; SimpleDateFormat sdf = new* SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ; String timeStr = sdf.format(date) ;*/// 日期文本格式// 调用工具类,将long类型的毫秒值转换成date类型的时间格式String timeStr = TimeUtil.changeMils2Date(timeLong, "yyyy-MM-dd HH:mm:ss");// 整个业务逻辑都在这里 :客户端的子线程,服务器转发的消息拆分后,做出相应的展示if (msgType == Configs.MSG_PRIVATE) {// 私聊的逻辑System.out.println(timeStr);System.out.println(sender + " 对你说: " + msgContent);} else if (msgType == Configs.MSG_ONLINE) {// 上线提醒System.out.println(timeStr);System.out.println(sender + " : " + msgContent);} else if (msgType == Configs.MSG_PUBLIC) {// 公聊的逻辑System.out.println(timeStr);System.out.println(sender + " 对大家说: " + msgContent);} else if (msgType == Configs.MSG_ONLIST) {// 在线列表System.out.println(timeStr);System.out.println("当前在线用户:");System.out.println(msgContent);} else if (msgType == Configs.MSG_EXIT) {// 退出逻辑System.out.println(timeStr);System.out.println(msgContent);} else if (msgType == Configs.MSG_FILE) {// 显示系统时间System.out.println(timeStr);// 将文件的名称和文件大小拆分出来String[] fileInfo = msgContent.split("#");String fileName = fileInfo[0];long fileLength = Long.parseLong(fileInfo[1]);// 展示内容System.out.println(sender + "给你发送来一个文件," + fileName + "文件大小" + (fileLength / 1024) + "KB");// 要读文件// 使用当前通道内内输入流对象,使用内存操作输出流写ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] cacheBytes = new byte[1024];int cacheLength = 0;while (true) {int len2 = in.read(cacheBytes);bos.write(cacheBytes, 0, len2);// 记录读取有效字节数组cacheLength += len2;if (cacheLength == fileLength) {break;}}// 获取文件字节数据byte[] fileBytes = bos.toByteArray();boolean b = InputAndOutputUtil.writeFile("D:\\" + fileName, fileBytes);if (b) {// 保存成功System.out.println("当前文件保存成功" + "D:\\" + fileName);break;} else {// 失败System.out.println("文件保存失败!");}}}} catch (SocketException e) {// 空处理} catch (Exception e) {e.printStackTrace();}}
}

服务器端主线程:

package chatroom.myserver;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;/*** 需求:客户端不断发送消息和读取服务器端反馈的消息,服务器端不断读取消息并反馈**/
public class ServerChatRoom {public static void main(String[] args) {try {// 创建服务器端的Socket对象ServerSocket ss = new ServerSocket(8888);// 创建一个单例集合,来存储客户端所在的Socket对象// ArrayList<Socket> list = new ArrayList<Socket>() ;//用来存放用户名和它所对应的Socket对象HashMap<String, Socket> hm = new HashMap<String, Socket>();System.out.println("服务器已开启,正在等待客户端的连接...");// 定一个变量用来记录用户端的个数int i = 1;// 使用while循环,表示只要有用户就需要不停的检测用户名,while (true) {// 监听客户端连接Socket s = ss.accept();System.out.println("第" + (i++) + "个客户端已经连接了...");// 将客户端添加到集合中// list.add(s) ;//角标从0开始,添加客户端// 服务器端要先检验客户端输入的用户名,然后才能开启聊天线程SaveUserThread st = new SaveUserThread(s, hm);st.start();}} catch (IOException e) {e.printStackTrace();}}
}

服务器端保存用户名线程:

package chatroom.myserver;import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Set;import chatroom.myserver.configs.Configs;//服务器端开启单独的保存用户名线程
public class SaveUserThread extends Thread {private Socket s;private HashMap<String, Socket> hm;private String username;public SaveUserThread(Socket s, HashMap<String, Socket> hm) {this.s = s;this.hm = hm;}@Overridepublic void run() {try {// 获取通道内输入输出流InputStream in = s.getInputStream();OutputStream out = s.getOutputStream();// 不断地保存用户名// 不断读取客户端传递过来的usernamewhile (true) {byte[] bys = new byte[1024];int len = in.read(bys);username = new String(bys, 0, len);System.out.println(username);// 判断用户名是否存在,并且服务器给客户端做出反馈如果当前集合中不存在这个用户名才该用户名和它对应的Socket添加到集合中// public boolean containsKey(Object key)if (!hm.containsKey(username)) {// 集合中没有username,才添加// 存进来(username用户名和它对应的通道 Socket)hm.put(username, s);// 给客户端的反馈用户名的校验:自己约定是否保存成功// yes:表示成功 no 表示失败out.write("yes".getBytes());break;} else {// 给客户端的反馈用户名的校验out.write("no".getBytes());}}// 保存用户名之后--->上线提醒:遍历集合,获取每个用户名对应的通道的Socket对象,然后获取他们给自// 输出流对象,写给自己 ,当然,排除自己!Set<String> keySet = hm.keySet();// 遍历所有的注册成功的用户名for (String key : keySet) {// 如果排除自己if (key.equals(username)) {continue; // 立即进入下一次循环}Socket socket = hm.get(key);OutputStream os = socket.getOutputStream();// 保存用户名的线程:// 上线功能的发送的消息的内容要遵循服务器转发的消息格式// 遵循服务器转格式// 转发的消息格式:// 发送者:消息内容:消息类型:时间String zfMsg = username + ":" + "上线了" + ":" + Configs.MSG_ONLINE + ":" + System.currentTimeMillis();os.write(zfMsg.getBytes());// 之前的上线提醒的发送的格式// os.write((username+"上线了").getBytes());}new ServerThread(s, hm, username).start();// 整个保存用户名和上线提醒完成了, 这里开启聊天线程// ServeThread:服务端的读取客户端消息的子线程} catch (Exception e) {e.printStackTrace();}}
}

服务器端读取客户端发送来的消息的子线程:

package chatroom.myserver;import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Set;import chatroom.myserver.configs.Configs;//服务器端读取客户端发送来的消息的子线程
public class ServerThread extends Thread {/** private InputStream in;* * public ServerThread(InputStream in) { this.in = in; }*/private Socket s;// private ArrayList<Socket> list ;private HashMap<String, Socket> hm;private String username;public ServerThread(Socket s, HashMap<String, Socket> hm, String username) {this.s = s;this.hm = hm;this.username = username;}@Overridepublic void run() {try {// 获取客户端所在通道内的输入和输出流OutputStream out = s.getOutputStream();InputStream in = s.getInputStream();// 不断读取while (true) {byte[] bys = new byte[1024 * 10];int len = in.read(bys);// msgStr它是客户端发送来的消息// 之前的消息格式:接收者:消息内容:发送者// 现在拿到客户端发送过来消息: 接收者:消息内容:消息类型// 现在的消息格式String msgStr = new String(bys, 0, len).trim(); // 去掉两端空格System.out.println(msgStr);// 拆分消息String[] msgs = msgStr.split(":");// 接收者String receiver = msgs[0];// 消息内容String msgContent = msgs[1];// 消息类型// String msgType = msgs[2] ;int msgType = Integer.parseInt(msgs[2]);// 拆分消息之后,重新组装消息,约定服务器转发格式// 发送者:消息内容:消息类型:时间long time = System.currentTimeMillis();// 服务器端读取消息的子线程应该根据不同的消息类型做出不同的处理if (msgType == Configs.MSG_PRIVATE) {// 私聊处理// 要符合转发格式// 获取接收者所在的通道内的Socket对象Socket socket = hm.get(receiver);// 通过socket对象获取当前接收者的通道 内的输出流// 转发的消息格式:// 发送者:消息内容:消息类型:时间String zfMsg = username + ":" + msgContent + ":" + msgType + ":" + time;//getOutputStream():返回Socket对象的输出流对象socket.getOutputStream().write(zfMsg.getBytes());} else if (msgType == Configs.MSG_PUBLIC) {// 公聊处理// 遍历集合Set<String> keySet = hm.keySet();for (String key : keySet) {// 排除自己if (key.equals(username)) {continue;// 立即进入下一次循环}// 获取当前用户名所在的通道的内的Socket对象Socket socket = hm.get(key);// 获取通道当前用户的通道内的输出流对象OutputStream os = socket.getOutputStream();// 符合转发格式:组装消息: 发送者:消息内容:消息类型:系统时间String zfMsg = username + ":" + msgContent + ":" + Configs.MSG_PUBLIC + ":" + time;os.write(zfMsg.getBytes());}} else if (msgType == Configs.MSG_ONLIST) {// 在线列表// 创建一个字符串缓冲区对象StringBuffer sb = new StringBuffer();int i = 1;// 逻辑:遍历HashMap集合 取出键值 输出 获取每一个用户,排除自己Set<String> keySet = hm.keySet();for (String key : keySet) {// 排除自己if (key.equals(username)) {continue;}// 需要容器// 拼接sb.append((i++)).append(",").append(key).append("\n");// 获取谁所在的通道的Socket对象 获取发送者的Socket对象,将在线用户发送给发送者// 组装消息,转发:格式:发送者:消息内容:消息类型:时间String zfMsg = username + ":" + sb.toString() + ":" + Configs.MSG_ONLIST + ":" + time;hm.get(username).getOutputStream().write(zfMsg.getBytes());}} else if (msgType == Configs.MSG_EXIT) {// 下线处理// 遍历集合,排除自己Set<String> keySet = hm.keySet();for (String key : keySet) {// 排除自己if (key.equals(username)) {continue;}// 获取Socket对象Socket socket = hm.get(key);// 组装下线String zfMsg = username + ":" + "下线了" + ":" + Configs.MSG_EXIT + ":" + time;socket.getOutputStream().write(zfMsg.getBytes());}break;} else if (msgType == Configs.MSG_FILE) {// 发送文件// 将文件名称和文件大小拆分出来String[] fileInfo = msgContent.split("#");String fileName = fileInfo[0];long fileLength = Long.parseLong(fileInfo[1]);// 组装消息:发送者:消息内容:消息类型:时间String msg = username + ":" + msgContent + ":" + Configs.MSG_FILE + ":" + time;// 获取msg的消息字节数组byte[] msgBytes = msg.getBytes();// 文件两端空格去掉了,中间也会有空字节byte[] emptyByte = new byte[1024 * 10 - msgBytes.length];// 读取文件// 定义一个缓冲区byte[] cacheBytes = new byte[1024];int cacheLength = 0; // 读取的实际的有效字节数// 使用客户端所在通道内的输入流去读这个文件,然后在使用内存操作流,将读到的内容存储到内存操作流中ByteArrayOutputStream bos = new ByteArrayOutputStream();while (true) {int len2 = in.read(cacheBytes);bos.write(cacheBytes, 0, len2);cacheLength += len2;// 每次记录读到的实际有效字节数if (cacheLength == fileLength) {// 一旦读取完毕,停掉break;}}// //要么重置一下流对象,要么重新创建一个流的对象byte[] fileByes = bos.toByteArray(); // 获取文件字节数组bos.reset();// 获取到文件的所在的字节数组bos.write(msgBytes);bos.write(emptyByte);bos.write(fileByes);// 在拼成一个大的字节数组byte[] allBytes = bos.toByteArray();// 转发out.write(allBytes);// 转发出去}// 之前的格式// 接收者msgs[0].发送者// 获取接收者所在的通的内流// 任何包装类类型都有对应的parseXXX()方法 ---> Long.parseLong(long) ; Double Byte// Socket socket = list.get(Integer.parseInt(msgs[0]));// socket.getOutputStream().write((msgs[2]+"对你说"+msgs[1]).getBytes());}// username关闭掉自己的所在Socket对象.并且从集合中将自己移出掉hm.get(username).close(); // 关闭会SocketException,做空处理hm.remove(username);} catch (SocketException e) {//空处理} catch (Exception e) {e.printStackTrace();}}
}

JavaSE项目:聊天室相关推荐

  1. Android小项目————聊天室(UI篇)

    Android小项目----聊天室(UI篇) 一.前言 这是所做的第二个android项目,主要目的对暑假所学的java和android知识点进行复习巩固和实践,由于知识所限,目前这个聊天室并不是很完 ...

  2. 暑假项目聊天室(0)--源码

    引言 暑假留校结束了,暑假中最重要的一个项目聊天室,在最终的努力下还是完成了,下面就来分析一下我写的聊天室; 文件 main.c 服务端主函数 main_cli.c 客户端主函数和客户端收发线程函数 ...

  3. 网络编程项目(聊天室项目)

    一.实现目标 一个在Linux下可以使用的聊天软件,要求至少实现如下功能: 1. 采用Client/Server架构 2. Client A 登陆聊天服务器前,需要注册自己的ID和密码 3. 注册成功 ...

  4. Linux C小项目 —— 聊天室

    多线程的聊天室 服务器端: 实现多用户群体聊天功能(人数上限可设置): 每个用户所发送的消息,其他已连接服务器的用户均可以收到: 用户输入"bye"退出,服务器输入"qu ...

  5. Java小项目——聊天室(多线程版本)

    目录 1. 前言 2. 功能实现 3. 模块划分 4. 功能分析 4.1 前期分析 4.2 具体实现 5. 使用技术 6. 代码 1. 前言 之前写过单线程版本的聊天室,这次对之前的版本进行扩展与优化 ...

  6. Android小项目——聊天室

    聊天室简介 简单介绍 更换图标 网络权限 登录界面 activity_main.xml MainActivity.java 选择头像 activity_choose_picture.xml Choos ...

  7. C++小项目(聊天室)——select模型+mysql+花生壳端口映射打造一个可以用外网连接的小qq

    成品展示: B站视频链接 这个小软件是我初学网络编程写的小玩具,记录一下,等学完完成端口模型再利用完成端口写别的好玩的软件,看的课程是这个老师,真的强烈推荐,课程28块钱,老师讲的巨棒,很细,很适合新 ...

  8. JAVASE多人聊天室项目实战

    文章目录 前言 一.服务器端 1.启动服务器端,打开服务器端窗口 2.创建ServerSocket,循环监听客户端连接 3.存储连接到服务器端的多个Socket(集合) 4.接收客户端发送的信息(多线 ...

  9. JavaSE项目之聊天室swing版

    引子: 当前,互联网 体系结构的参考模型主要有两种,一种是OSI参考模型,另一种是TCP/IP参考模型. 一.OSI参考模型,即开放式通信系统互联参考模型(OSI/RM,Open Systems In ...

最新文章

  1. PYTHON——数据存储:MySQL数据库
  2. MP3 编码解码 附完整c代码
  3. 拥抱.NET Core系列:MemoryCache 缓存选项
  4. 多行字符串的表示方式
  5. 丹麦红十字会与多家公司合作推出区块链支持的灾难债券
  6. java string查找_Java lastIndexOf() 方法
  7. 计算机组成原理学习-哈工大《计算机组成原理》第二章
  8. 如何用adb连接android手机
  9. Modularity(模块性)
  10. php出现网页无法运作怎么处理,该网页无法正常运作怎么解决?
  11. Meta-Learning之How to train your MAML
  12. shell 脚本实战 三
  13. excel隔行显示不同颜色
  14. HTML5期末大作业:网站——美丽家乡(武汉汉口)
  15. 干货!ERP系统优化生产管理流程五大步骤
  16. selenium提取数据的方法总结
  17. Windows 10系统下查看硬盘类型的方法
  18. 引流三十六招第十四招:蹭热门工具流量,日引上千精准粉
  19. 征服微信小程序视频教程-李宁-专题视频课程
  20. 如何携号转网只需三步

热门文章

  1. 线程池的5种使用方式
  2. 通过背景调查维护公司声誉?
  3. mac上那个动态壁纸软件好看?
  4. 愿逝者安息.为生者祈福.
  5. 如何设置合适的anchor
  6. Xcode 配置多套 App 图标的方法 --- AppStore 图标 A/B Test 实践
  7. 英伟达P8显卡_如何评价AMD强势回归市场,直面英伟达的显卡宝座?
  8. sockaddr操作C++封装
  9. Photon:6-Camera Work
  10. 4.8A可调USB限流开关芯片,带短路保护