以流式的Socket实现面向连接的TCP服务
一.功能要求
1.用户可以选择聊天服务器进行登录.
2.用户使用用户名登录到聊天室,这个登录名就是用户在聊天
室的昵称.
3.可以选择群聊,广播信息,使所有用户都能看到聊天信息
4.可以选择和某个用户私聊,其他用户无法得知聊天内容.
5.聊天信息要试试反应到聊天记录中.
6.用户登录退出时,要给其他用户发出通知.
二.设计
1.界面设计
..........发挥你的想象力.........
2.整体设计
聊天室整体采用C/S模式,客户端启动后,主动向服务器发出
连接请求,建立Socket连接.服务器启动后,监听固定端口
5210,当有客户连接请求时,便响应此请求,将此连接交由线
程Talking类处理.
1.来看服务器的代码实现
- 1 package jffx.blogs.net;
- 2
- 3 import java.io.*;
- 4 import java.net.*;
- 5 import java.util.*;
- 6
- 7 /**
- 8 * 代码文件: TalkRoomServer.java
- 9 * 功能描述: 管理服务器与客户端的活动连接
- 10 */
- 11 public class TalkRoomServer {
- 12 public static void main(String[] args) {
- 13 try {
- 14 //服务器端serversocket, 绑定端口(5210), 随意选(1024后的)
- 15 ServerSocket server = new ServerSocket(5210);
- 16
- 17 /**
- 18 * 容器来保存服务器与客户端的连接, 键为姓名,值为socket
- 19 */
- 20 Map<String, Socket> socketMap = new HashMap<>() ;
- 21
- 22 while(true) {
- 23 //监听客户端的连接, accept的方式是阻塞的
- 24 try {
- 25 Socket ss = server.accept();
- 26
- 27 //创建流
- 28 //采用缓冲流,提高效率
- 29 DataInputStream in = new DataInputStream(
- 30 new BufferedInputStream(ss.getInputStream())
- 31 ) ;
- 32 DataOutputStream out = new DataOutputStream(
- 33 new BufferedOutputStream(ss.getOutputStream())
- 34 ) ;
- 35
- 36 /**
- 37 * 在客户端设计时,一个新的用户登录的同时就向服务器发送其姓名
- 38 */
- 39 String name = in.readUTF() ;
- 40 //获取IP
- 41 String IP = ss.getInetAddress().toString() ;
- 42 //显示到服务器
- 43 System.out.println(name + " : " + IP) ;
- 44
- 45 //查看已监听的客户,并向他们发送新用户登录消息
- 46 Collection<Socket> values = socketMap.values() ;
- 47 Iterator<Socket> iter = values.iterator() ;
- 48 while(iter.hasNext()) {
- 49 Socket temp = iter.next() ;
- 50 DataOutputStream write = new DataOutputStream(temp.getOutputStream()) ;
- 51 write.writeUTF("Add:" + name + IP) ;
- 52 write.flush() ;
- 53 }
- 54 //将新用户添加到容器中
- 55 socketMap.put(name, ss) ;
- 56
- 57 /**
- 58 * 向新登录的用户发送都有谁在线
- 59 */
- 60 Set<String> names = socketMap.keySet() ;
- 61 Iterator<String> iterName = names.iterator() ;
- 62 while(iterName.hasNext()) {
- 63 String loginUser = iterName.next() ;
- 64 out.writeUTF("Add:" + loginUser + IP) ;
- 65 out.flush() ;
- 66 }
- 67
- 68
- 69 /**
- 70 * 创建新线程转发用户给服务器发送的消息
- 71 * 由于需要分
- 72 * 向某个人发送消息,即:私聊
- 73 * 向所有发送消息,即:广播.
- 74 * 我们采用修改处理客户端的发送方式:
- 75 * 在消息的基础上,给前面即消息前缀加上一些表示发送目标的字符串.
- 76 * 具体看Talking.java的处理方式
- 77 */
- 78 //由于客户有可能下线,所以需要将姓名和容器都传递给线程类
- 79 new Thread(new Talking(name, ss, socketMap)).start() ;
- 80
- 81 } catch (Exception ex) {
- 82 ex.printStackTrace() ;
- 83 }
- 84 }
- 85 } catch (Exception ex) {
- 86 ex.printStackTrace() ;
- 87 }
- 88 }
- 89 }
下面这个是单独处理每个用户的线程代码.
大概思路是这样的:先读取这个用户的发送的消息,然后进行解析,拆分,
分情况发送给客户端;如果由用户退出了聊天室,将用户从所在的
Socket容器中剔除,并给所有客户发送这个用户退出的消息.
- 1 package jffx.blogs.net;
- 2
- 3 import java.io.*;
- 4 import java.net.*;
- 5 import java.util.*;
- 6
- 7 /**
- 8 * 代码文件:Talking.java
- 9 * 功能描述:线程类转发用户的数据
- 10 */
- 11 public class Talking implements Runnable {
- 12 String name ;
- 13 Socket connecter ;
- 14 Map<String, Socket> socketMap = null ;
- 15 public Talking(String name, Socket socket, Map<String, Socket> socketMap) {
- 16 this.name = name ;
- 17 this.connecter = socket ;
- 18 this.socketMap = socketMap ;
- 19 }
- 20
- 21 @Override
- 22 public void run() {
- 23 try {
- 24 DataInputStream in = new DataInputStream(
- 25 new BufferedInputStream(connecter.getInputStream())
- 26 ) ;
- 27
- 28 while(true) {
- 29 String words = in.readUTF() ;
- 30 /**
- 31 * 我们约定,客户端发送("name@text")这种格式的消息
- 32 * 不过用户不需要处理,我们交由客户端程序在发送消息时自动加上
- 33 */
- 34 String [] tokens = words.split("@") ;
- 35 String sendName = tokens[0] ;
- 36 String text = tokens[1] ;
- 37
- 38 if("All".equals(sendName)) {
- 39 //容器的值为Socket变量
- 40 Collection<Socket> sockets = socketMap.values() ;
- 41 Iterator<Socket> iter = sockets.iterator() ;
- 42 while(iter.hasNext()) {
- 43 //创建流.并以固定格式写出
- 44 Socket sendSocket = iter.next() ;
- 45 DataOutputStream out = new DataOutputStream(sendSocket.getOutputStream()) ;
- 46 out.writeUTF("Text:" + text) ;
- 47 out.flush() ;
- 48 }
- 49 } else { //私聊
- 50 Socket sendSocket = socketMap.get(sendName) ;
- 51 DataOutputStream out = new DataOutputStream(sendSocket.getOutputStream()) ;
- 52 out.writeUTF("Text:" + text) ;
- 53 out.flush() ;
- 54 }
- 55 }
- 56 } catch (Exception ex) {
- 57 ex.printStackTrace() ;
- 58 } finally { //当登陆的用户退出后,就会跳出while(true)
- 59 try {
- 60 /**
- 61 * 查找登陆的用户,删除服务器与其的连接,并发送给所有的客户端
- 62 */
- 63 this.socketMap.remove(this.name) ;
- 64 Collection<Socket> sockets = this.socketMap.values() ;
- 65 Iterator<Socket> iter = sockets.iterator() ;
- 66 while(iter.hasNext()) {
- 67 Socket sender = iter.next() ;
- 68 DataOutputStream out = new DataOutputStream(sender.getOutputStream()) ;
- 69 out.writeUTF("Del:" + this.name) ;
- 70 out.flush() ;
- 71 }
- 72 } catch (Exception ex) {
- 73 ex.printStackTrace() ;
- 74 }
- 75 }
- 76 }
- 77 }
至于客户端,留给明天.呵呵...........