上一篇 java socket编程(2)——利用socket实现聊天之单聊
中我们讲到如何使用socket让客户端和客户端之间传递消息,实现一对一的聊天,接下来我将写出如何让服务器建立客户端与客户端之间的一对多通讯。
还是在原有的代码上加以修改,增加功能。
1,UserGroupBean 聊天群表
public class UserGroupBean implements Serializable {
private static final long serialVersionUID = 3L;
private int groupId;//群id
private int userId;//用户id
private int groupNumberByMax;//总人数
private int groupNumberByCurrent;//当前人数
private int groupType;//群类型,朋友,家人,同事
private int groupUserType;//当前用户在群里面的角色,管理员-2,创建者-1,普通人等级:1,2,3,4,5
//省略get、set方法
}
2,MessageBean 消息表修改,增加聊天类型,是单聊还是群聊的标识
public class MessageBean implements Serializable {
private static final long serialVersionUID = 1L;
private long messageId;// 消息id
private long groupId;// 群id
private int chatStyle;//0,普通消息;1,群消息;2,讨论组消息;3,系统推送消息;4,好友通知消息
private int chatType;// 消息类型;1,文本;2,图片;3,小视频;4,文件;5,地理位置;6,语音;7,视频通话
private String content;// 文本消息内容
private String errorMsg;// 错误信息
private int errorCode;// 错误代码
private int userId;//用户id
private int friendId;//目标好友id
private MessageFileBean chatFile;// 消息附件
//省略get、set方法
}
3, UserInfoBean 用户信息表,加入该用户所拥有的群集合
public class UserInfoBean implements Serializable {
private static final long serialVersionUID = 2L;
private int userId;// 用户id
private String userName;// 用户名
private String likeName;// 昵称
private String userPwd;// 用户密码
private String userIcon;// 用户头像
private List groupList;//当前用户所拥有的群
//省略get、set方法
}
1,UserData 添加模拟数据
public class UserData {
/**
* 模拟数据库的用户信息,这里创建id不同的用户信息
*
* @param userId
* @return
*/
public static UserInfoBean getUserInfoBean(int userId) {
UserInfoBean userInfoBean = new UserInfoBean();
userInfoBean.setUserIcon("用户头像");
userInfoBean.setUserId(userId);
userInfoBean.setUserName("admin");
userInfoBean.setUserPwd("123123132a");
userInfoBean.setGroupList(getUserGroup());
return userInfoBean;
}
/**每个用户模拟两个群
* @return
*/
private static List getUserGroup() {
List list = new ArrayList<>();
for (int i = 1; i <= 2; i++) {
UserGroupBean userGroupBean = new UserGroupBean();
userGroupBean.setGroupId(i);
userGroupBean.setGroupNumberByMax(500);
userGroupBean.setGroupNumberByCurrent(20);
userGroupBean.setGroupType(i);
userGroupBean.setGroupUserType(-1);
list.add(userGroupBean);
}
return list;
}
}
2,ChatServer ,修改聊天服务的发送信息逻辑
public class ChatServer {
// socket服务
private static ServerSocket server;
// 使用ArrayList存储所有的Socket
public List socketList = new ArrayList<>();
// 模仿保存在内存中的socket
public Map socketMap = new HashMap();
// 模仿保存在数据库中的用户信息
public Map userMap = new HashMap();
public Gson gson = new Gson();
/**
* 初始化socket服务
*/
public void initServer() {
try {
// 创建一个ServerSocket在端口8080监听客户请求
server = new ServerSocket(SocketUrls.PORT);
createMessage();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 创建消息管理,一直接收消息
*/
private void createMessage() {
try {
System.out.println("等待用户接入 : ");
// 使用accept()阻塞等待客户请求
Socket socket = server.accept();
// 将链接进来的socket保存到集合中
socketList.add(socket);
System.out.println("用户接入 : " + socket.getPort());
// 开启一个子线程来等待另外的socket加入
new Thread(new Runnable() {
public void run() {
// 再次创建一个socket服务等待其他用户接入
createMessage();
}
}).start();
// 用于服务器推送消息给用户
getMessage();
// 从客户端获取信息
BufferedReader bff = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 读取发来服务器信息
String line = null;
// 循环一直接收当前socket发来的消息
while (true) {
Thread.sleep(500);
// System.out.println("内容 : " + bff.readLine());
// 获取客户端的信息
while ((line = bff.readLine()) != null) {
// 解析实体类
MessageBean messageBean = gson.fromJson(line, MessageBean.class);
// 将用户信息添加进入map中,模仿添加进数据库和内存
// 实体类存入数据库,socket存入内存中,都以用户id作为参照
setChatMap(messageBean, socket);
// 将用户发送进来的消息转发给目标好友
getChatStyle(messageBean);
System.out.println("用户 : " + userMap.get(messageBean.getUserId()).getUserName());
System.out.println("内容 : " + messageBean.getContent());
}
}
// server.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("错误 : " + e.getMessage());
}
}
/**
* 发送消息
*/
private void getMessage() {
new Thread(new Runnable() {
public void run() {
try {
String buffer;
while (true) {
// 从控制台输入
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
buffer = strin.readLine();
// 因为readLine以换行符为结束点所以,结尾加入换行
buffer += "\n";
// 这里修改成向全部连接到服务器的用户推送消息
for (Socket socket : socketMap.values()) {
OutputStream output = socket.getOutputStream();
output.write(buffer.getBytes("utf-8"));
// 发送数据
output.flush();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
/**
* 模拟添加信息进入数据库和内存
*
* @param messageBean
* @param scoket
*/
private void setChatMap(MessageBean messageBean, Socket scoket) {
// 将用户信息存起来
if (userMap != null && userMap.get(messageBean.getUserId()) == null) {
userMap.put(messageBean.getUserId(), UserData.getUserInfoBean(messageBean.getUserId()));
}
// 将对应的链接进来的socket存起来
if (socketMap != null && socketMap.get(messageBean.getUserId()) == null) {
socketMap.put(messageBean.getUserId(), scoket);
}
}
/**
* 将消息转发给目标好友
*
* @param messageBean
*/
private void getChatStyle(MessageBean messageBean) {
switch (messageBean.getChatStyle()) {
case 0:// 普通消息
getFriendChat(messageBean);
break;
case 1:// 群消息
getGroupChat(messageBean);
break;
case 3:// ,系统推送消息;
break;
case 4:// ,好友通知消息
break;
}
}
/**
* 发送给好友的普通消息
*
* @param messageBean
*/
private void getFriendChat(MessageBean messageBean) {
// 如果聊天内容是该内容,则表示这是一个用户上线信息,不需要发送给其他用户
if (messageBean.getContent().equals("[USER_LOGIN]"))
return;
if (socketMap != null && socketMap.get(messageBean.getFriendId()) != null) {
Socket socket = socketMap.get(messageBean.getFriendId());
String buffer = gson.toJson(messageBean);
// 因为readLine以换行符为结束点所以,结尾加入换行
buffer += "\n";
try {
// 向客户端发送信息
OutputStream output = socket.getOutputStream();
output.write(buffer.getBytes("utf-8"));
// 发送数据
output.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 发送群聊消息
*
* @param messageBean
*/
private void getGroupChat(MessageBean messageBean) {
// 如果聊天内容是该内容,则表示这是一个用户上线信息,不需要发送给其他用户
if (messageBean.getContent().equals("[USER_LOGIN]"))
return;
if (userMap == null)
return;
// 遍历每一个在线用户,遍历出他们的群集合
for (UserInfoBean userInfoBean : userMap.values()) {
// 在每个用户的群集合中查找该条消息的目标群id
for (UserGroupBean userGroupBean : userInfoBean.getGroupList()) {
// 说明该用户拥有这个群,则向这个用户发送这条群消息
if (userGroupBean.getGroupId() == messageBean.getGroupId()) {
// 如果当前socket集合中查找到了该用户的id,说明该用户在线,则发送该条群消息;并且过滤掉发送群消息本人;
if (socketMap != null && socketMap.get(userInfoBean.getUserId()) != null
&& userInfoBean.getUserId() != messageBean.getUserId()) {
Socket socket = socketMap.get(userInfoBean.getUserId());
String buffer = gson.toJson(messageBean);
// 因为readLine以换行符为结束点所以,结尾加入换行
buffer += "\n";
try {
// 向客户端发送信息
OutputStream output = socket.getOutputStream();
output.write(buffer.getBytes("utf-8"));
// 发送数据
output.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
1,增加选择是进入单聊还是进入群聊。xml布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:orientation="vertical">
<LinearLayout
android:id="@+id/friend_ly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:src="@drawable/ic_friend" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:text="好友名:张三"
android:textColor="@android:color/black"
android:textSize="18sp" />
LinearLayout>
<LinearLayout
android:id="@+id/group_ly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:background="@android:color/white"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:src="@drawable/ic_chat" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:text="群名:闲聊群"
android:textColor="@android:color/black"
android:textSize="18sp" />
LinearLayout>
LinearLayout>
2,ChatListActivity 选择聊天类型的activigty
public class ChatListActivity extends AppCompatActivity implements View.OnClickListener {
private LinearLayout friend_ly, group_ly;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chatlist);
friend_ly = (LinearLayout) findViewById(R.id.friend_ly);
group_ly = (LinearLayout) findViewById(R.id.group_ly);
friend_ly.setOnClickListener(this);
group_ly.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.friend_ly://单聊
Intent intent = new Intent(ChatListActivity.this, MainActivity.class);
startActivity(intent);
break;
case R.id.group_ly://群聊
Intent intent1 = new Intent(ChatListActivity.this, GroupActivity.class);
startActivity(intent1);
break;
}
}
}
3,ChatServer 客户端的聊天服务修改
public class ChatServer {
private Socket socket;
private Handler handler;
private MessageBean messageBean;
private Gson gson = new Gson();
// 由Socket对象得到输出流,并构造PrintWriter对象
PrintWriter printWriter;
InputStream input;
OutputStream output;
DataOutputStream dataOutputStream;
//在socket初始化成功以后发送一个空消息让服务器先保存该用户的信息
private Handler handlers = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == -1) {
sendMessages("[USER_LOGIN]");
}
}
};
public ChatServer(int status) {
initMessage(status);
initChatServer();
}
/**
* 消息队列,用于传递消息
*
* @param handler
*/
public void setChatHandler(Handler handler) {
this.handler = handler;
}
private void initChatServer() {
//开个线程接收消息
receiveMessage();
}
/**
* 初始化用户信息
*/
private void initMessage(int status) {
messageBean = new MessageBean();
UserInfoBean userInfoBean = new UserInfoBean();
userInfoBean.setUserId(2);
messageBean.setMessageId(1);
messageBean.setChatType(1);
messageBean.setChatStyle(1);
userInfoBean.setUserName("admin");
userInfoBean.setUserPwd("123123123a");
//以下操作模仿当用户点击了某个好友展开的聊天界面,将保存用户id和聊天目标用户id
if (status == 1) {//如果是用户1,那么他就指向用户2聊天
messageBean.setUserId(1);
messageBean.setFriendId(2);
} else if (status == 2) {//如果是用户2,那么他就指向用户1聊天
messageBean.setUserId(2);
messageBean.setFriendId(1);
} else if (status == 3) {//如果是用户2,那么他就指向用户1聊天
messageBean.setUserId(3);
messageBean.setFriendId(1);
}
messageBean.setGroupId(1);//聊天的目标群id,这里模拟成id为1的群
ChatAppliaction.userInfoBean = userInfoBean;
}
/**
* 发送消息
*
* @param contentMsg
*/
public void sendMessages(String contentMsg) {
if (TextUtils.isEmpty(contentMsg)) return;
try {
if (socket == null) {
Message message = handler.obtainMessage();
message.what = 1;
message.obj = "服务器已经关闭";
handler.sendMessage(message);
return;
}
byte[] str = contentMsg.getBytes("utf-8");//将内容转utf-8
String aaa = new String(str);
messageBean.setContent(aaa);
String messageJson = gson.toJson(messageBean);
/**
* 因为服务器那边的readLine()为阻塞读取
* 如果它读取不到换行符或者输出流结束就会一直阻塞在那里
* 所以在json消息最后加上换行符,用于告诉服务器,消息已经发送完毕了
* */
messageJson += "\n";
output.write(messageJson.getBytes("utf-8"));// 换行打印
output.flush(); // 刷新输出流,使Server马上收到该字符串
} catch (Exception e) {
e.printStackTrace();
Log.e("test", "错误:" + e.toString());
}
}
/**
* 接收消息,在子线程中
*/
private void receiveMessage() {
new Thread(new Runnable() {
@Override
public void run() {
try {
// 向本机的8080端口发出客户请求
socket = new Socket(SocketUrls.IP, SocketUrls.PORT);
// 由Socket对象得到输入流,并构造相应的BufferedReader对象
printWriter = new PrintWriter(socket.getOutputStream());
input = socket.getInputStream();
output = socket.getOutputStream();
dataOutputStream = new DataOutputStream(socket.getOutputStream());
// 从客户端获取信息
BufferedReader bff = new BufferedReader(new InputStreamReader(input));
// 读取发来服务器信息
String line;
Message messages = handlers.obtainMessage();
messages.what = -1;
handlers.sendMessage(messages);
while (true) {
Thread.sleep(500);
// 获取客户端的信息
while ((line = bff.readLine()) != null) {
Log.i("socket", "内容 : " + line);
MessageBean messageBean = gson.fromJson(line, MessageBean.class);
Message message = handler.obtainMessage();
message.obj = messageBean;
message.what = 1;
handler.sendMessage(message);
}
if (socket == null)
break;
}
output.close();//关闭Socket输出流
input.close();//关闭Socket输入流
socket.close();//关闭Socket
} catch (Exception e) {
e.printStackTrace();
Log.e("test", "错误:" + e.toString());
}
}
}).start();
}
public Socket getSocekt() {
if (socket == null) return null;
return socket;
}
}
所有的代码逻辑都修改完了,在原来一对一单聊的基础上修改成了一对多的群聊。
如果能够顺利的理解socket的消息推送,一对一聊天,那么一对多的聊天也不是什么难事。
附上demo供大家学习