【Java】Socket+多线程实现控制台聊天室
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827212.html
另:具体代码实现已移植github: https://github.com/ygj0930/Chat-Room-in-Java ,大家fork之余记得给我个star呀~
聊天室程序的结构图:
架构解释:
Server服务器相当于一个中转站,Client客户端程序传送信息到服务器,服务器再把信息分发到其他客户端上,实现即时通信。
所需技术:
1:数据传输。
服务器与客户端之间的信息传递,都通过数据通道实现,有一个客户端连接到服务器,就有一条数据通道架设于该客户端和服务器之间。
这条数据通道通过Socket来实现:每个客户端通过一个socket与服务器建立连接,这是通道的一个“端点”。而服务器端ServerSocket一旦检测到有客户端接入,accept()方法就会马上返回一个socket对象与客户端的socket对接起来,这是通道的另一个“端点”。“两点建立一条直线”,当然,这里不一定是直的(废话)。总之,一条像水管一样的通道就这样把一个客户端与服务器联系起来了。之后的事情,就是把我们要传输的数据,通过数据通道传输,如同水管运水一样,数据就是那些水。
2:持续监听。
即时聊天要求某一客户端(聊天者)敲出来的话要立刻更新到连接到该服务器上的所有客户端的界面上,这个简单,学过while循环的人应该都能立刻想到:我用一个while()语句持续地从数据通道检测数据不就成了?一旦数据通道有信息,马上把它取出来,在客户端显示就ok了。
然而!这事儿还没完。每个客户端都要保持持续接收用户输入的状态。你总不能一个客户端只发送一次信息就关闭掉了吧?
那么问题来了,又要保持持续监听服务器端传过来的信息,又要保持持续监听键盘输入,那这俩while循环到底怎么安排呢?
这里最容易犯的错误就是,用一个while循环嵌套了另一个while循环。比如下面的代码:
- while ((msg1 = br.readLine()) != null) {
- System.out.println(msg1);
- while (true) {
- msg2 = re.readLine();
- pw.println(msg2);
- }
外层循环持续监听数据通道,一旦有服务器端传过来的信息,就提取出来显示到客户端。内层循环持续监听键盘输入,有则通过pw(数据通道,等下会有源码)传输给服务器。
这里错误在于,内层循环是死循环,不会停止,所以会出现客户端不停输入信息发送到服务器,然而信息却没有更新到其他的客户端去。因为客户端代码停留在内层循环,没有出去执行监听并提取数据通道信息那一部分代码。
还有一种案例,就是在客户端输入信息,却没有显示出来,狂按键盘也没见打出一个字。但是把服务器程序关闭后却又在客户端程序窗口看到一大堆刚才输入的信息。
这种错误的原因与上面相反,是监听数据通道的while循环阻塞了下一个while循环(接收键盘输入部分)的执行。代码如下:
- BufferedReader br = new BufferedReader(new InputStreamReader(socket .getInputStream()));
- String msg1;
- while ((msg1 = br.readLine()) != null) {
- System.out.println(msg1);
- }
- BufferedReader re = new BufferedReader(new InputStreamReader(System.in));
- PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
- String msg2;
- while (true) {
- msg2 = re.readLine();
- pw.println(msg2);
- }
上面一个while循环持续监听数据通道(服务器向客户端传送信息),保持在等待接收信息状态,导致下面的代码没有执行,所以客户端不能接收用户输入,狂敲键盘也没用。
而关闭服务器程序后,数据通道断了,就不再监听,转而运行到了下面的while循环,所以就能接收用户输入。
那么正确的姿势是怎样的呢?那就是:并行。
在一个while循环运行的同时,另一个while循环也运行。两个while循环没有先后之分。
要实现并行,就需要用到多线程技术。【多线程是啥我就不讲了,基本上能做到这个项目的人都是学了多线程再来实践的吧哈哈哈~】
正确完整代码贴出来,仅供参考。如果你是用来应付老师布置的作业的,小心和别的同学撞车-_-:
- Server.java:
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class Server {
- int port;
- List<Socket> clients;
- ServerSocket server;
- public static void main(String[] args) {
- new Server();
- }
- public Server() {
- try {
- port = 8080;
- clients = new ArrayList<Socket>();
- server = new ServerSocket(port);
- while (true) {
- Socket socket = server.accept();
- clients.add(socket);
- Mythread mythread = new Mythread(socket);
- mythread.start();
- }
- } catch (Exception ex) {
- }
- }
- class Mythread extends Thread {
- Socket ssocket;
- private BufferedReader br;
- private PrintWriter pw;
- public String msg;
- public Mythread(Socket s) {
- ssocket = s;
- }
- public void run() {
- try {
- br = new BufferedReader(new InputStreamReader(ssocket.getInputStream()));
- msg = "欢迎【" + ssocket.getInetAddress() + "】进入聊天室!当前聊天室有【"
- + clients.size() + "】人";
- sendMsg();
- while ((msg = br.readLine()) != null) {
- msg = "【" + ssocket.getInetAddress() + "】说:" + msg;
- sendMsg();
- }
- } catch (Exception ex) {
- }
- }
- public void sendMsg() {
- try {
- System.out.println(msg);
- for (int i = clients.size() - 1; i >= 0; i--) {
- pw = new PrintWriter(clients.get(i).getOutputStream(), true);
- pw.println(msg);
- pw.flush();
- }
- } catch (Exception ex) {
- }
- }
- }
- }
- Client.java:
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class Client {
- public int port = 8080;
- Socket socket = null;
- public static void main(String[] args) {
- new Client();
- }
- public Client() {
- try {
- socket = new Socket("127.0.0.1", port);
- new Cthread().start();
- BufferedReader br = new BufferedReader(new InputStreamReader(socket
- .getInputStream()));
- String msg1;
- while ((msg1 = br.readLine()) != null) {
- System.out.println(msg1);
- }
- } catch (Exception e) {
- }
- }
- class Cthread extends Thread {
- public void run() {
- try {
- BufferedReader re = new BufferedReader(new InputStreamReader(System.in));
- PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
- String msg2;
- while (true) {
- msg2 = re.readLine();
- pw.println(msg2);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
老习惯,来个总结:用Socket构建数据通道(InputStream/OutputStream),用多线程同时运行两个while实现监听与更新,就是聊天室的技术所在。
(本人新手,恳请老码农们指正。感激不尽!若需要转载与交流,请于评论处留言告知我一声,谢谢)
【Java】Socket+多线程实现控制台聊天室的更多相关文章
- java socket之多人聊天室Demo
一.功能介绍 该功能实现了一个类似QQ的最简单多人聊天室,如下图所示. 二.目录结构 三.服务端 1)SocketServer类,该类是服务端的主类,主要负责创建聊天窗口,创建监听客户端的线程: pa ...
- java多线程控制台聊天室(转)
用java多线程实现一个控制台聊天室,呵呵,好玩! 聊天室服务器端 package tf.thread; import java.io.BufferedReader; import java.io.I ...
- java Socket多线程聊天程序
参考JAVA 通过 Socket 实现 TCP 编程 参考java Socket多线程聊天程序(适合初学者) 以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包 ...
- Java和WebSocket开发网页聊天室
小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...
- java Socket实现简单在线聊天(二)
接<java Socket实现简单在线聊天(一)>,在单客户端连接的基础上,这里第二步需要实现多客户端的连接,也就需要使用到线程.每当有一个新的客户端连接上来,服务端便需要新启动一个线程进 ...
- Express+Socket.IO 实现简易聊天室
代码地址如下:http://www.demodashi.com/demo/12477.html 闲暇之余研究了一下 Socket.io,搭建了一个简易版的聊天室,如有不对之处还望指正,先上效果图: 首 ...
- java socket 多线程网络传输多个文件
http://blog.csdn.net/njchenyi/article/details/9072845 java socket 多线程网络传输多个文件 2013-06-10 21:26 3596人 ...
- 示例:Socket应用之简易聊天室
在实际应用中,Server总是在指定的端口上监听是否有Client请求,一旦监听到Client请求,Server就会启动一个线程来响应该请求,而Server本身在启动完线程之后马上又进入监听状态. 示 ...
- Java 多线程Socket编程通讯--实现聊天室代码
1.创建服务器类 import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import ja ...
随机推荐
- uva12034Race
递推,组合. 考虑第一名有i个人,则f[n]=sum(C(n,i)*f[n-i]),递推即可.. #include<cstdio> #include<algorithm> #i ...
- Qt之自定义界面(添加自定义标题栏)
简述 通过上节内容,我们实现了自定义窗体的移动,但是我们缺少一个标题栏来显示窗体的图标.标题,以及控制窗体最小化.最大化.关闭的按钮. 自定义标题栏后,所有的控件我们都可以定制,比如:在标题栏中添加换 ...
- UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
这个题能1A纯属运气,要是WA掉,可真不知道该怎么去调了. 题意: 这是完全独立的6个子问题.代码中是根据字符串的长度来区分问题编号的. 给出三角形三点坐标,求外接圆圆心和半径. 给出三角形三点坐标, ...
- LeetCode Contains Duplicate II (判断重复元素)
题意:如果有两个相同的元素,它们之间的距离不超过k,那么返回true,否则false. 思路:用map记录每个出现过的最近的位置,扫一边序列即可.扫到一个元素就判断它在前面什么地方出现过.本题数据有点 ...
- Js原型模式
function Person(){ } Person.prototype.name = "xd"; Person.prototype.age = 26; Person.proto ...
- setImageResource和setImageDrawable区别
ImageView设置图片的方式有很多钟,可以在xml里面写android:src=”@drawable/xxx”,也可以在java代码里面设置. 在java里面的设置方式也有多种,方法包括:setI ...
- linux命令——磁盘管理cd
Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的. cd指令可让用户在不同的目录间切换,但该用户必须拥有足够的权限进入目的目录. 1 ...
- Color Length
题意: 给出两个字符串,求把两字符串组成一个字符串使的字符串中的相同字母的最远距离的和最小. 分析: 本题关键在于怎么计算距离和的方法上.dp[i][j]表示处理到长度i的a串,长度j的b串还需要的计 ...
- 常见设计模式解析和实现(C++)FlyWeight模式
作用:运用共享技术有效地支持大量细粒度的对象 UML结构图: 解析: Flyweight模式在大量使用一些可以被共享的对象的时候使用.比如,在QQ聊天时很多时候你懒得回复又不得不回复,一般会用一些客套 ...
- android:照片涂画功能实现过程及原理
这个功能可以帮你实现,在图片上进行随意的涂抹,可以用于SNS产品. 绘图本身很简单,但是要实现在图片上指定的部分精确(位置,缩放)的绘图,就有点麻烦了. 下面讲讲实现过程及原理: UI构图 这个UI, ...