聊天室Socket

聊天室版本1

服务器

客户端获得服务器端插头
客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server1 {
    public static void main(String[] args) throws IOException {
        //在本机80002端口上启动服务
        //如果端口被占用,这里会出现异常
        ServerSocket ss = new ServerSocket(8002);
        System.out.println("服务已启动。。。");
        //等待客户端发起连接请求并建立连接通道
        Socket s=ss.accept();
        System.out.println("客户端已连接。。。");
        
        //从Socket连接通道,获取流
        InputStream in = s.getInputStream();
        OutputStream out = s.getOutputStream();
        /*
         *通信协议
         *通信流程(谁收谁发?)、数据格式(视频,音频,文本,字符,图片)
         *
         *1.接受4个字符love(按字符的单字节值发送)
         *2.向对方发送3个字符you
         *
         */
        for(int i=0;i<4;i++)
        {
            //接收,收一个打印一个,字符的字节值改为char类型
            char c = (char)in.read();
            System.out.print(c);
        }
        //发送:向对方发送字符串。
        //getBytes,字符串转成字节值:把Unicode转系统默认编码,如gbk英文变单字节
        out.write("you".getBytes());
        out.flush();
        //断开连接
        s.close();
        //停止服务释放端口
        ss.close();
        
    }
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client1 {
    public static void main(String[] args) throws UnknownHostException, IOException {
        //127.0.0.1 本机回环ip
        //localhost 本机回环ip
        //如果不出异常就已经建立了连接通道
        Socket s = new Socket("localhost",8002);
        //从连接通道取出流
        InputStream in = s.getInputStream();
        OutputStream out = s.getOutputStream();
        /*
         * 通信协议
         * 1.发送4个字符love
         * 2.接受3个字符you
         */
        out.write("love".getBytes());
        out.flush();
        
        for(int i=0;i<3;i++)
        {
            char c=(char) in.read();
            System.out.print(c);
        }
        s.close();
        
    }
}

ss.accept()方法是阻塞的
程序执行到它就暂停,后面的代码不执行
直到客户端建立连接通道,后面的代码才
继续执行
in.read()方法是阻塞的
从对方接收数据,如果收不到指定的个数,
等待对方数据
阻塞操作都会让程序暂停,影响后面代码
执行。阻塞操作应该有单独的线程来并行
执行。

这种等待建立连接和通信两个操作如果要并行
执行就要多线程。


f服务器端线程模型

服务器端:
1.死循环执行accept方法
2.与一个客户端建立连接,要继续等待下一个客户端
通信线程:
1.等待客户端的数据。
2.一个通信线程针对一个客户端,执行通信操作(接收数据)。每连上一个客户端,针对客户端都要有一个通信线程。

回声例子

客户端向服务器端发送的数
服务器原封不动地再发回来
EchoServer

package liaotian;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class EchoServer {
    public void start(){
        //新启线程,循环执行accpet()
        new Thread(){
            public void run() {
                try {
                    ServerSocket ss = new ServerSocket(8002);
                    while(true)
                    {
                        Socket s = ss.accept();
                        //Socket连接通道交给线程来并行执行通信过程
                        TonXinThread t=new TonXinThread(s);
                        t.start();
                    }
                } catch (Exception e) {
                    System.out.println("端口被占用或者服务已经停止");
                }
            };
        }.start();
    }
    class TonXinThread extends Thread{

        /*
         *流插在网络上,基本字节流+文本编码编码转换流+读一行BufferedReader 
         */
        Socket s;
        public TonXinThread(Socket s) {
            this.s=s;
        }
        @Override
        public void run() {
            try {
                /*
                 *通信协议
                 *流程
                 *1.接收2.发送
                 *数据格式
                 *1.GBK编码字符
                 *2.一行文本,末尾要有一个换行符
                 *PW-PrintWriter
                 *BR-ISR-网络数流
                 *PW-OSW-网络输出流
                 *PW.println()
                 */
                BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
                PrintWriter out=new PrintWriter(new OutputStreamWriter(s.getOutputStream(),"GBK"));
                String line;
                while((line=in.readLine())!=null){
                    out.println(line);
                    out.flush();
                    System.out.println(line);
                }
                
            } catch (Exception e) {
            }
            System.out.println("客户端已断开");
        }
        
    }
}

EchoClient

package liaotian;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class EchoClient {
    Scanner sc=new Scanner(System.in);
    public void start(){
        try {
            Socket s = new Socket("localhost",8000);
            BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
            PrintWriter out=new PrintWriter(new OutputStreamWriter(s.getOutputStream(),"GBK"));
            while(true){
                System.out.println("输入exit退出");
                String str=sc.nextLine();
                if("exit".equals(str))
                {
                    s.close();
                    break;
                }
                //把str发送给服务器
                out.println(str);
                out.flush();
                //接收回声数据
                String echo=in.readLine();
                System.out.println("回声:"+echo);
            }
        } catch (Exception e) {
        }
        System.out.println("已经与服务器端口连接");
    }
    public static void main(String[] args) {
        EchoClient client=new EchoClient();
        client.start();
    }
}

聊天天室版本2

缺点:没法输入完整的话


聊天室

一个客户端对应一个通信线程对象
一个客户端断开,对应的通信线程对象发现断开,把自己从集合中移除
每次调用sendAll()遍历集合,调用remove移除集合,如果程序sendAll正在遍历,那么就不能往集合添加新的客户端,也通信线程也不能移除自己。要避免多线程情况下,对同一个集合操作,数据访问冲突的问题。要同步加锁,让线程抢集合的锁,所有对集合的访问,添加删除都得加锁。

synchronized (list) {
list.add(t);
}
synchronized (list) {
sendAll(name + "进入了聊天室,在线人数:" + list.size());
}
while ((line = in.readLine()) != null) {
synchronized (list) {
sendAll(name + "说:" + line);
}
}

聊天室服务器端模型
package liaotian2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class ChatServer {
    // 用来收集所有的线程通信对象
    private List<TongXinThread> list = new ArrayList<TongXinThread>();

    public static void main(String[] args) {
        ChatServer server=new ChatServer();
        server.start();
    }
    public void start() {
        new Thread() {
            ServerSocket ss ;
            @Override
            public void run() {
                try {
                     ss = new ServerSocket(8000);
                    System.out.println("服务已经启动");
                    while (true) {
                        Socket s = ss.accept();
                        TongXinThread t = new TongXinThread(s);
                        synchronized (list) {
                            list.add(t);
                        }

                        t.start();
                    }
                } catch (Exception e) {
                    System.out.println("端口被占用或服务已经停止");
                }finally{
                    try {
                        ss.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }.start();
    }

    class TongXinThread extends Thread {
        Socket s;
        BufferedReader in;
        PrintWriter out;
        String name;

        public TongXinThread(Socket s) {
            this.s = s;
        }

        // 向当前客户端发送
        public void send(String msg) {
            out.println(msg);
            out.flush();
        }

        // 向所有客户端发送
        public void sendAll(String msg) {
            // 遍历通信线程对象,调用send方法
            for (TongXinThread t : list) {
                t.send(msg);
            }
        }

        @Override
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
                out = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "GBK"));
                // 接收客戶端昵称
                name = in.readLine();
                // 群发消息XXX进入聊天室
                synchronized (list) {
                    sendAll(name + "进入了聊天室,在线人数:" + list.size());
                }
                // 循环接收聊天内容并群发
                String line;
                while ((line = in.readLine()) != null) {
                    synchronized (list) {
                        sendAll(name + "说:" + line);
                    }
                }
            } catch (Exception e) {
            }
            // 当前客户端连接断开!
            // 当前通信对象,从集合中移除
            synchronized (list) {
                list.remove(this);
                System.out.println(name + "离开了聊天室,在线人数:" + list.size());
            }

        }
    }

}
package liaotian2;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    private Socket s;
    private BufferedReader in;
    private PrintWriter out;
    Scanner sc;
    public static void main(String[] args) {
        ChatClient client=new ChatClient();
        client.start();
    }
    public void start() {
        try {
            s=new Socket("176.114.17.243", 8000);
            in = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
            out = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "GBK"));
            System.out.println("nick name:");
            sc = new Scanner(System.in);
            String name = sc.nextLine();
            out.println(name);
            out.flush();
            //等待输入聊天内容,并发送
            new Thread(){
                public void run() {
                    input();
                }
            }.start();
            //等待输入聊天室内容并显示
            new Thread(){
                public void run() {
                    receive();
                }
            }.start();
            sc.close();
        } catch (Exception e) {
            System.out.println("连接失败");
        }
    }
    protected void input() {
        //循环
        while(true)
        {
            System.out.print("输入聊天内容:");
            String s=sc.nextLine();
            out.println(s);
            out.flush();
        }
    }
    protected void receive() {
        try {
            String line;
            while((line=in.readLine())!=null)
            {
                System.out.println("* "+line);
            }
        } catch (Exception e) {
        }
        System.out.println("连接已断开");
    }
}

聊天室客户端,连接服务器,输入昵称后,把昵称发出去。开始通信,接收聊天室别人的聊天内容。输入聊天内容后回车,群发给所有人,自己也可以看到。
1从服务器不停地收内容显示2等待客户端输入内容。这两个阻塞操作放入线程,一个输入线程,一个接收线程(接收到数据不停地打印)执行。

//等待输入聊天内容,并发送
new Thread(){
public void run() {
input();
}
}.start();
//等待输入聊天室内容并显示
new Thread(){
public void run() {
receive();
}
}.start();

聊天天室版本3

按回车输入聊天内容,输入过程不受打扰(不显示聊天室信息),再按回车聊天内容一次性全输出出来了。
接收线程把聊天内容暂时放入集合中。(从服务器接收内容,存入集合)——生产者
接收线程往集合里加入数据,(向打印线程)发通知

protected void receive() {
        try {
            String line;
            while((line=in.readLine())!=null)
            {
                //受到聊天内容存入结合
                synchronized (list) {
                    list.add(line);
                    list.notifyAll();//通知等待的打印线程
                }
            }
        } catch (Exception e) {
        }
        System.out.println("连接已断开");
    }

打印线程从集合获取数据打印。(从集合取出内容进行打印,打印要从头部获取数据,先加入的聊天内容,先打印显示,最后加入的数据,最后取出【队列LinkedList】,没有数据输入不缓存,直接打印)——消费者

//缓存聊天数据的集合
private LinkedList<String>list=new LinkedList<String>();

打印线程

    protected void print() {
        //循环打印数据
        while(true){
            synchronized (list) {
                while((inputFlag==true)||(list.size()==0)){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(list.removeFirst());
            }
        }
    }

输入线程
输入线程输入要保证打印线程不打印。
输入线程输入完成,(向打印线程)发通知。

//输入标记,没有输入内容是false,开始输入内容是true
    private boolean inputFlag=false;
protected void input() {
        System.out.print("按回车开始输入:");
        //循环
        while(true)
        {
            sc.nextLine();//接收回车
            inputFlag=true;//改标记
            
            System.out.print("输入聊天内容:");
            String s=sc.nextLine();
            out.println(s);
            out.flush();
            inputFlag=false;//改标记
            //通知打印线程
            synchronized (list) {
                //******************
                list.notifyAll();
            }
        }
    }

回车输入inputFlag=true; 时打印线程等待,输入线程输入完成输入标记改为inputFlag=false;,通知打印线程打印


package liaotian3;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Scanner;

public class ChatClient {
    private Socket s;
    private BufferedReader in;
    private PrintWriter out;
    Scanner sc;
    //缓存聊天数据的集合
    private LinkedList<String>list=new LinkedList<String>();
    //输入标记,没有输入内容是false,开始输入内容是true
    private boolean inputFlag=false;
    public static void main(String[] args) {
        ChatClient client=new ChatClient();
        client.start();
    }
    public void start() {
        try {
            s=new Socket("localhost", 8000);
            in = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
            out = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "GBK"));
            System.out.println("nick name:");
            sc = new Scanner(System.in);
            String name = sc.nextLine();
            out.println(name);
            out.flush();
            //等待输入聊天内容,并发送
            new Thread(){
                public void run() {
                    input();
                }
            }.start();
            //等待输入聊天室内容
            new Thread(){
                public void run() {
                    receive();
                }
            }.start();
            //打印线程
            new Thread(){
                public void run() {
                    print();
                }
            }.start();
        } catch (Exception e) {
            System.out.println("连接失败");
        }
    }
    protected void print() {
        //循环打印数据
        while(true){
            synchronized (list) {
                while((inputFlag==true)||(list.size()==0)){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(list.removeFirst());
            }
        }
    }
    protected void input() {
        System.out.print("按回车开始输入:");
        //循环
        while(true)
        {
            sc.nextLine();//接收回车
            inputFlag=true;//改标记
            
            System.out.print("输入聊天内容:");
            String s=sc.nextLine();
            out.println(s);
            out.flush();
            inputFlag=false;//改标记
            //通知打印线程
            synchronized (list) {
                //******************
                list.notifyAll();
            }
        }
    }
    protected void receive() {
        try {
            String line;
            while((line=in.readLine())!=null)
            {
                //受到聊天内容存入结合
                synchronized (list) {
                    list.add(line);
                    list.notifyAll();//通知等待的打印线程
                }
            }
        } catch (Exception e) {
        }
        System.out.println("连接已断开");
    }
}
package liaotian3;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class ChatServer {
    // 用来收集所有的线程通信对象
    private List<TongXinThread> list = new ArrayList<TongXinThread>();

    public static void main(String[] args) {
        ChatServer server=new ChatServer();
        server.start();
    }
    public void start() {
        new Thread() {
            ServerSocket ss ;
            @Override
            public void run() {
                try {
                     ss = new ServerSocket(8000);
                    System.out.println("服务已经启动");
                    while (true) {
                        Socket s = ss.accept();
                        TongXinThread t = new TongXinThread(s);
                        synchronized (list) {
                            list.add(t);
                        }

                        t.start();
                    }
                } catch (Exception e) {
                    System.out.println("端口被占用或服务已经停止");
                }finally{
                    try {
                        ss.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }.start();
    }

    class TongXinThread extends Thread {
        Socket s;
        BufferedReader in;
        PrintWriter out;
        String name;

        public TongXinThread(Socket s) {
            this.s = s;
        }

        // 向当前客户端发送
        public void send(String msg) {
            out.println(msg);
            out.flush();
        }

        // 向所有客户端发送
        public void sendAll(String msg) {
            // 遍历通信线程对象,调用send方法
            for (TongXinThread t : list) {
                t.send(msg);
            }
        }

        @Override
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
                out = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "GBK"));
                // 接收客戶端昵称
                name = in.readLine();
                // 群发消息XXX进入聊天室
                synchronized (list) {
                    sendAll(name + "进入了聊天室,在线人数:" + list.size());
                }
                // 循环接收聊天内容并群发
                String line;
                while ((line = in.readLine()) != null) {
                    synchronized (list) {
                        sendAll(name + "说:" + line);
                    }
                }
            } catch (Exception e) {
            }
            // 当前客户端连接断开!
            // 当前通信对象,从集合中移除
            synchronized (list) {
                list.remove(this);
                System.out.println(name + "离开了聊天室,在线人数:" + list.size());
            }

        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容