聊天室项目分析

这是一个基于网络线程的可以群聊的SWT工程,支持获取登录的端口号,及用户名,当登录后
用户所发送的信息会经服务器转发给其他所有在线用户,也支持及时显示登录用户,或用户退出。
具体内容:
在bean包下:
1,Content类:
这个类是用来处理客户端发送消息,
存放客户端的ip,用户名,以及要发送的消息。
2,User类:
这个类是用来处理用户登录或退出的,存放了ip地址
和用户名。提供两个构造方法。
3,YcConstants类:
用来存放常量的值,及发送协议所需要的code值。
4,JsonModel类:
存放了一个 code值 和一个泛型,发送协议时用到,
因为这里用到泛型,后面就需要用到反射,也提高了
项目的安全性。
server包:
服务端:TalkServer类
在TalkServer类中写一个ClientSocket内部类并实现Runnable接口。
在ClientSocket类:
创建一个有参的构造方法:用来创建socket,输入流,输出流和通过控制flag的值
来控制线程的运行。
在其run 方法中:
一但有客户端连入flag的值就为true,这段就会变成一个死循环,一直
接受来自客户端的消息,如果读到客户端有下一行消息.
则调用ParseProtocal()方法,进行处理。

        在parseProtocal()方法中:    
            首先会调用parseCode()方法用来判断code的值

            在praseCode(String jsonline)方法中,因为从客户端传过来的是一个经gosn解析后的值
            形式是{"code:x,...."}.
            但是在判断startsWith方法时需要注意应该这么写("{\"code\":x,")
            然后返回X;

        返回的X=1,则表明t是content对象,则操作文本显示聊天消息
            用sendMesgToAllClient(jsonline)方法:
                循环list 通过流将内容写出去。List在后面会说明。

        返回的X=3则表明有新的用户登录,服务器端处理就是将这个新的登录的用户存到list中,
           再将这个用户列表发给各个客户端

            a,new 一个Gson 等下用来解析json数据。
            b,Type type=new TypeToken<JsonModel<User>>(){}.getType();
            注意:因为这里是登录所以这里jsonModel中的放的是User.
            这里导包时也应该导入其反射的那个。
            JsonModel<User> jsonModel=g.fromJson(jsonline, type);
            通过jsonmodel获得值,然再转换
            User loginuser=jsonModel.getT();//取出登录用户的信息
            List<User> userlist=new ArrayList<User>();
            //将新登录的用户加入到list中
            ClientSocket.this.ip=loginuser.getIp();
            ClientSocket.this.nickname=loginuser.getNickname();
            list.add(ClientSocket.this);
            然后在循环这个list,将最新的数据添加到userlist,
            通过sendUserListJson()方法发送给各个客户端
                在这个方法中:
                JsonModel<List<User>> jm=new JsonModel<List<User>>();
                注意这里jsonmodel里面的泛型是List<User>
                将code值设为2,
                然后设置jm的反射实列
                jm.setT(userlist);
                Type t=new TypeToken<JsonModel<User>>(){}.getType();
                然后在通过gosn将其转换为json数据
                通过循环发个各个对象。


        返回的X=4时需要做的就是将list中的这个数据移除,
        然后跟上面一样跟新list,将数据发出即可。


    start()方法中:
        先创建一个服务器端口
        然后做个死循环一直监听是否有客户端连上
        有客户端连上就会开启线程


    在公共区域会定义list用来存 了所有的在线用户列表

    调用mian()
        就会启动服务器。

client包:
    TalkClient类:

        登录按钮:
            点击登录按钮,获取到界面上的值,
            建立Socket,输入,输出流
            根据上面的同样的方法将数据转换写出
            并开启线程

    其他按钮也基本差不多;
    但在在刷新消息与联系人列表时值注意:
        因为不能直接操作主线程里面的组件;
        要用Display.getDefault().asyncExec(new Runnable(){
            @Override
            public void run() {
                ..........
                ......
                }
            });
            将代码套起来。



具体代码:

package com.yc.bean;
//发送的内容
public class Content {
private String ip;
private String nickname;
private String msg;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return ip+”\t”+nickname+”\t”+msg;
}
}

package com.yc.bean;

public class JsonModel {
private int code;
private T t;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}

}

package com.yc.bean;
//登录退出的用户信息
public class User {
private String nickname;
private String ip;
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public User(String nickname, String ip) {
super();
this.nickname = nickname;
this.ip = ip;
}
public User() {
super();
}

}

package com.yc.bean;

public class YcConstants {
//登录的code值为3
public static final int USER_LOGIN_CODE=3;
//退出的code值为4
public static final int USER_LOGIN_OUT_CODE=4;
//发送信息的code值为1
public static final int SEND_MESSAGE=1;
//发送用户列表的code值为2
public static final int SEND_USERLIST=2;

}

package com.yc.server;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Type;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.yc.bean.JsonModel;
import com.yc.bean.User;
import com.yc.bean.YcConstants;

public class TalkServer {
private List list= new ArrayList(); //存 了所有的在线用户列表

public static void main(String[] args) throws IOException{
    TalkServer ts=new TalkServer();
    ts.start();
}

public void start() throws IOException{
    ServerSocket ss=new ServerSocket(23456);
    System.out.println("服务器:"+ss.getInetAddress()+"启动,正在监听"+ss.getLocalPort()+"端口");

    while(true){
        Socket s=ss.accept();
        System.out.println("客户端:"+s.getInetAddress()+"连接上了服务器");
        //将这个socket放到一个Runnable对像中,用一个线程来操作
        ClientSocket cs=new ClientSocket(s);

        Thread t=new Thread(cs);
        t.start();
    }
}

//与一个客户端的套接字
class ClientSocket implements Runnable{
private Socket s;
private Scanner sc;
private PrintWriter pw;
private boolean flag=false;
private String nickname;
private String ip;

    //构造方法:创建socket及流,并控制线程的flag 为true;
    public ClientSocket(Socket s){

        try {
            this.s=s;
            sc=new Scanner(s.getInputStream());
            pw=new PrintWriter(s.getOutputStream());
            flag=true;
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("客户端:"+s.getInetAddress()+"已经掉线了");
            list.remove(ClientSocket.this);
            flag=false;
            sc.close();
            pw.close();
            try {
                s.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }






@Override
    public void run() {
        while(flag){
            //1接受客户发送过来的数据jsonline
            if(sc.hasNextLine()){
                String jsonline=sc.nextLine();
                parseProtocal(jsonline);
            }
        }
    }

    //解析状态码
    private int parseCode(String jsonline){
        if(jsonline.startsWith("{\"code\":1,")){
            return 1; 
        }else if(jsonline.startsWith("{\"code\":3,")){
            return 3; 
        }else if(jsonline.startsWith("{\"code\":4,")){
            return 4; 
        }
        return 404;
    }

    //发送信息到客户端
    private void sendMesgToAllClient(String jsonline){
        for(ClientSocket cs:list){
            cs.pw.println(jsonline);
            cs.pw.flush();
        }
    }
    //解析协议
    private void parseProtocal(String jsonline){
        //判断codede 值
        int code=parseCode(jsonline);
        //如果code为1,则表明t是content对象,则操作文本显示聊天消息
        if(code==1){
            //1是普通聊天信息,直接向所有的客户端广播
            sendMesgToAllClient(jsonline);
        }else if(code==3){
            //如果为3则表明有新的用户登录,服务器端处理就是将这个新的登录的用户存到list中,再将这个用户列表发给各个客户端
            Gson g=new Gson();
            Type type=new TypeToken<JsonModel<User>>(){}.getType();
            JsonModel<User> jsonModel=g.fromJson(jsonline, type);
            //将这个user存到list中
            User loginuser=jsonModel.getT();//取出登录用户的信息
            List<User> userlist=new ArrayList<User>();
            //将新登录的用户加入到list中
            ClientSocket.this.ip=loginuser.getIp();
            ClientSocket.this.nickname=loginuser.getNickname();
            list.add(ClientSocket.this);
            for(ClientSocket cs: list){
                User u=new User();
                u.setIp(cs.ip);
                u.setNickname(cs.nickname);
                userlist.add(u);
            }
            sendUserListJson(userlist);
        }else if(code==4){
            //如果是code=4,则要从clientSocket中删除这个用户
            /*Gson g=new Gson();
            Type type=new TypeToken<JsonModel<User>>(){}.getType();
            JsonModel<User> jsonModel=g.fromJson(jsonline, type);*/
            //将这个user存到list中
            List<User> userlist=new ArrayList<User>();
            list.remove(ClientSocket.this);
            for(ClientSocket cs: list){
                User u=new User();
                u.setIp(cs.ip);
                u.setNickname(cs.nickname);
                userlist.add(u);
            }
            sendUserListJson(userlist);
        }
    }
    //发送用户列表的json字符串
    private void sendUserListJson(List<User> userlist){
        String jsonline;
        JsonModel<List<User>> jm=new JsonModel<List<User>>();
        jm.setCode(YcConstants.SEND_USERLIST);
        jm.setT(userlist);
        Type t=new TypeToken<JsonModel<User>>(){}.getType();
        Gson gson=new Gson();
        jsonline=gson.toJson(jm,t);
        for(ClientSocket cs:list){
            cs.pw.println(jsonline);
            cs.pw.flush();
        }
    }

}

}

package com.yc.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Type;
import java.net.Socket;
import java.net.UnknownHostException;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.custom.SashForm;

import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ibm.icu.text.SimpleDateFormat;
import com.yc.bean.Content;
import com.yc.bean.JsonModel;
import com.yc.bean.User;
import com.yc.bean.YcConstants;

import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;

public class TalkClient implements Runnable{

protected Shell shell;
private Text txtForeest;
private Text text_1;
private Text text_2;
private Socket s;
private Scanner sc;
private PrintWriter out;
private Table table;
private Text text_3;
private Text text_4;

private boolean flag;
private String info;

private String  nickname;
/**
 * Launch the application.
 * @param args
 */
public static void main(String[] args) {
    try {
        TalkClient window = new TalkClient();
        window.open();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * Open the window.
 */
public void open() {
    Display display = Display.getDefault();
    createContents();
    shell.open();
    shell.layout();
    while (!shell.isDisposed()) {
        if (!display.readAndDispatch()) {
            display.sleep();
        }
    }
}

/**
 * Create contents of the window.
 */
protected void createContents() {
    shell = new Shell();
    shell.setSize(822, 734);
    shell.setText("SWT Application");
    shell.setLayout(new FillLayout(SWT.HORIZONTAL));

    Composite composite = new Composite(shell, SWT.NONE);
    composite.setLayout(new FillLayout(SWT.HORIZONTAL));

    SashForm sashForm = new SashForm(composite, SWT.NONE);
    sashForm.setOrientation(SWT.VERTICAL);

    Composite composite_1 = new Composite(sashForm, SWT.NONE);
    composite_1.setLayout(new FillLayout(SWT.HORIZONTAL));

    Group 登录 = new Group(composite_1, SWT.NONE);
    登录.setText("\u767B\u5F55");
    登录.setToolTipText("\u767B\u5F55");

    Label label = new Label(登录, SWT.NONE);
    label.setBounds(20, 29, 44, 17);
    label.setText("\u6635\u79F0\uFF1A");

    txtForeest = new Text(登录, SWT.BORDER);
    txtForeest.setText("foreest");
    txtForeest.setBounds(87, 23, 150, 23);

    Label label_1 = new Label(登录, SWT.NONE);
    label_1.setBounds(278, 29, 48, 17);
    label_1.setText("\u670D\u52A1\u5668\uFF1A");

    text_1 = new Text(登录, SWT.BORDER);
    text_1.setText("localhost");
    text_1.setBounds(357, 29, 170, 23);

    Label label_2 = new Label(登录, SWT.NONE);
    label_2.setBounds(558, 29, 36, 17);
    label_2.setText("\u7AEF\u53E3\uFF1A");

    text_2 = new Text(登录, SWT.BORDER);
    text_2.setText("23456");
    text_2.setBounds(614, 29, 144, 23);

    final Button button = new Button(登录, SWT.NONE);

    button.setBounds(247, 72, 80, 27);
    button.setText("\u767B\u5F55");

    final Button button_1 = new Button(登录, SWT.NONE);

    button_1.setBounds(398, 72, 80, 27);
    button_1.setText("\u65AD\u5F00");

    Composite composite_2 = new Composite(sashForm, SWT.NONE);
    composite_2.setLayout(new FillLayout(SWT.HORIZONTAL));

    Group group = new Group(composite_2, SWT.NONE);
    group.setText("\u804A\u5929\u8BB0\u5F55");
    group.setLayout(new FillLayout(SWT.HORIZONTAL));

    SashForm sashForm_1 = new SashForm(group, SWT.NONE);

    Composite composite_4 = new Composite(sashForm_1, SWT.NONE);
    composite_4.setLayout(new FillLayout(SWT.HORIZONTAL));

    text_3 = new Text(composite_4, SWT.BORDER | SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL);

    Composite composite_5 = new Composite(sashForm_1, SWT.NONE);
    composite_5.setLayout(new FillLayout(SWT.HORIZONTAL));

    table = new Table(composite_5, SWT.BORDER | SWT.FULL_SELECTION);
    table.setHeaderVisible(true);
    table.setLinesVisible(true);

    TableColumn tableColumn = new TableColumn(table, SWT.NONE);
    tableColumn.setWidth(100);
    tableColumn.setText("\u6635\u79F0\uFF1A");

    TableColumn tblclmnIp = new TableColumn(table, SWT.NONE);
    tblclmnIp.setWidth(100);
    tblclmnIp.setText("ip");
    sashForm_1.setWeights(new int[] {537, 260});

    Composite composite_3 = new Composite(sashForm, SWT.NONE);
    composite_3.setLayout(new FillLayout(SWT.HORIZONTAL));

    Group group_1 = new Group(composite_3, SWT.NONE);
    group_1.setText("\u5185\u5BB9");

    Label label_3 = new Label(group_1, SWT.NONE);
    label_3.setBounds(25, 40, 61, 17);
    label_3.setText("\u5185\u5BB9\uFF1A");

    text_4 = new Text(group_1, SWT.BORDER);
    text_4.setBounds(92, 40, 369, 63);

    final Button button_2 = new Button(group_1, SWT.NONE);

    button_2.setBounds(537, 106, 80, 27);
    button_2.setText("\u53D1\u9001");
    sashForm.setWeights(new int[] {150, 324, 151});

    //登录
    button.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            nickname=txtForeest.getText().trim().toString();
            String ip=text_1.getText().trim().toString();
            String port=text_2.getText().trim().toString();

            if(nickname==null||"".equals(nickname)){
                MessageDialog.openError(shell, "参数错误", "昵称不能为空");
                return;
            }
            if(ip==null||"".equals(ip)){
                MessageDialog.openError(shell, "参数错误", "ip不能为空");
                return;
            }
            if(port==null||"".equals(port)){
                MessageDialog.openError(shell, "参数错误", "端口不能为空");
                return;
            }

            int p=Integer.parseInt(port);

            try {
                s=new Socket(ip,p);
                sc=new Scanner(s.getInputStream());
                out=new PrintWriter(s.getOutputStream());
                txtForeest.setEditable(false);
                text_1.setEditable(false);
                text_2.setEditable(false);

                //当服务器连接上了,则启动线程,来监听服务器给我会送信息

                 button.setEnabled(false);
                 button_1.setEnabled(true);
                 button_2.setEnabled(true);

                 JsonModel<User> jsonModel=new JsonModel<User>();
                 jsonModel.setCode(YcConstants.USER_LOGIN_CODE);
                 User user=new User();
                 user.setNickname(nickname);
                 user.setIp(s.getLocalAddress().toString());
                 jsonModel.setT(user);
                 //通过jsonmodel 对象转为json字符串通过gson转换
                 //通过TypeToken 对像,向gson来说明这个泛型的构成
                 Gson g=new Gson();
                 Type type=new TypeToken<JsonModel<User>>(){}.getType();
                String jsonstring=g.toJson(jsonModel, type);   //这里有问题:泛型的对象不能被gson解析成字符串
                out.println(jsonstring);
                out.flush();
                flag=true;
                Thread t=new Thread(TalkClient.this);
                t.start();

            } catch (Exception e1) {
                e1.printStackTrace();
                MessageDialog.openError(shell, "l连接错误", "无法建立连接");
                txtForeest.setEditable(true);
                text_1.setEditable(true);
                text_2.setEditable(true);
                 button.setEnabled(true);
                 button_1.setEnabled(false);
                 button_2.setEnabled(false);
            } 

        }
    });


    //断开
    button_1.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            if(s==null){
                return;
            }
            JsonModel<User> jsonModel=new JsonModel<User>();
            jsonModel.setCode(YcConstants.USER_LOGIN_OUT_CODE);
             User user=new User();
             user.setNickname(nickname);
             user.setIp(s.getLocalAddress().toString());
             jsonModel.setT(user);

             Gson g=new Gson();
             Type type=new TypeToken<JsonModel<User>>(){}.getType();
            String jsonstring=g.toJson(jsonModel, type);   //这里有问题:泛型的对象不能被gson解析成字符串
            out.println(jsonstring);
            out.flush();

            //退出
            String content=text_3.getText();
            Date d=new Date();
            SimpleDateFormat sdf=new SimpleDateFormat(
            "yyyy年MM月dd日 HH:mm:ss");
            content="\n"+nickname+"退出聊天\n\t\t\tt\t"+sdf.format(d)+content;
            text_3.setText(content);

            out.close();
            sc.close();

            try {
                s.close();
                txtForeest.setEditable(true);
                text_1.setEditable(true);
                text_2.setEditable(true);
                 button.setEnabled(true);
                 button_1.setEnabled(false);
                 button_2.setEnabled(false);

                 flag=false;

            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }

        }
    });


    //发送
    button_2.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            String content=text_4.getText().trim().toString();
            if(content==null || "".equals(content)){
                MessageDialog.openError(shell, "出错啦,", "要发送的消息不能为空");
                return;
            }
            JsonModel<Content> jsonModel=new JsonModel<Content>();
            jsonModel.setCode(YcConstants.SEND_MESSAGE);

            Content c=new Content();
            c.setIp(s.getInetAddress().toString());
            c.setNickname(nickname);
            c.setMsg(content);

            jsonModel.setT(c);

            //将jsonModel对象转换为json字符串通过gson转换
            Type type=new TypeToken<JsonModel<Content>>(){}.getType();

            Gson gson=new Gson();
            String jsonstring =gson.toJson(jsonModel,type);

            out.println(jsonstring);
            out.flush();

            text_4.setText("");

        }
    });

}

@Override
public void run() {
    //死循环的接受服务器端传过来的信息
    while(flag){
        //接受服务器的回传数据
        if(s!=null && s.isClosed()==false && sc!=null && sc.hasNextLine()){
            String jsonline=sc.nextLine();
            //解析协议
            parseProtocal(jsonline);
        }
    }
}

private void parseProtocal(String jsonline){
    //判断code的值
    int code=parseCode(jsonline);
    //如果code为1则表明 t是content对象,则操作文本显示聊天消息
    if(code==1){
        Gson g=new Gson();
        Type type=new TypeToken<JsonModel<Content>>(){}.getType();
        JsonModel<Content> jsonModel=g.fromJson(jsonline, type);
        final Content c=jsonModel.getT();
        //在swt中不能直接在新的线程中操作主线程中的组件,只能通过异步的方式
        Display.getDefault().asyncExec(new Runnable(){

            @Override
            public void run() {
                // TODO Auto-generated method stub
                Date d=new Date();
                SimpleDateFormat sdf=new SimpleDateFormat(
                        "yyyy年MM月dd日 HH:mm:ss");
                String txt=text_3.getText().trim();
                String msg="\n\n"+c.getNickname()+"对大家说:\n"+c.getMsg()+"\n\t\t\t\t\t发表时间:"+sdf.format(d)+"\n";
                txt=msg+txt;
                text_3.setText(txt);

            }
        }); 
    }else if(code==2){
        //如果为2则表t是Userlist集合对象,则更新table
    //  System.out.println("2222222222222");
        Gson g=new Gson();
        Type type=new TypeToken<JsonModel<List<User>>>(){}.getType();
        JsonModel<List<User>> jsonModel=g.fromJson(jsonline, type);
        final List<User> list=jsonModel.getT();
        Display.getDefault().asyncExec(new Runnable(){

            @Override
            public void run() {
                table.removeAll();
                    for(User u:list){
                        TableItem ti=new TableItem(table,SWT.NONE);
                        ti.setText(new String[]{u.getNickname(),u.getIp()});
                    }
            }   
        }); 
    }
}
//解析状态码
private int parseCode(String jsonline){
    if(jsonline.startsWith("{\"code\":1,")){
        return 1; 
    }else if(jsonline.startsWith("{\"code\":2,")){
        return 2; 
    }
    return 404;
}

}

你可能感兴趣的:(线程,网络,服务器,聊天)