接着前面的课程
,前面我们定义了一个接口来定义我们的协议字符
,我们定义了一个新的数据结构
,来保存我们聊天室里边的用户名
,代表客户端的用户名和对应的套接字关联的输出流
,之间的映射关系
,这对应一个数据结构
,那么接下来我们就要进而再更新我们的Server类
,服务器的这个类以及我们的服务端的那个线程类
,我们要把前面定义的协议字符
,和我们新的保存映射关系的数据结构
,要在我们的服务器类和服务器线程类当中
,我们要把它用起来
,当然我们后面的客户端也会用到这两个新的东西
,所以说这里边我们接下来就要我们服务器的第二次更新
,更新我们的Server类
,更新我们的ServerThread类
,来
,我们接着前面的课程内容
,继续
,先我们更新这个Server
,那么Server这个类我们在前面
,在前面课程当中我给大家说过
,我们这个服务器启动
,如果说你上一次服务器启动没有正常停止
,那么你第二次启动服务器的时候
,会遇到端口号被占用的这种异常
,那么这种异常我们应该把它捕获掉
,所以在这里边我们要进行更新
,这是第一个要做的更新
,第二个要做的更新呢
,我们前面是用一个List集合来保存
,我们这边所有链接进来的客户端
,现在要不得了
,因为我们List的这个集合保存的客户端
,这里边没有对这里边的每一个客户端进行标记
,也就是说进来的客户端
,我在服务器的程序当中没有办法去区分它们
,区别它们
,所以说这种保存方式就是要不得的
,那么根据我们要实现私聊
,我们必须要在群聊当中要谁发的信息
,
我们要知道是谁发的
,显示出是谁发的信息
,所以说这个逻辑这个思路肯定要不得
,所以说我们这边就把它换了
,所以说在这里边我要把它换成
,我们自己定义的新的一个数据结构
,来 把原来的List换成我们自己定义的新的数据结构
,来保存链接进来的所有客户端
,那么这个保存的客户端也跟前面不一样
,前面保存的是套接字Socket对象
,那么现在我们要保持什么
,现在我们保存的是我们代表客户端的用户名称
,和对应的套接字
,对应的Socket套接字关联的输出流
,我这边是这样子保存
,所以说这里边我们同样是要线程安全
,但是我们在这个数据结构当中本身就保证了线程安全
,所以说在这里边我们就只需要来public static
,然后是我们的ChatRoomMap这么一个结构
,看到没有
,来 这里边的泛型
,我们的键是字符串
,然后我们的Value值是我们的打印输出流PrintStream
,看到没有 是这样子的
,然后在这里边我们叫做客户端
,链接进来的所有的客户端clients等于什么
,直接new一个ChatRoomMap
,搞定
,看到没有
,我们在这里边首先把服务端里边保存客户端的这个集合
,我们把它变成我们自己升级的自己定义的数据结构来保存
,
那么而且保存的内容也跟原来有所区别
,那么既然这里边都改了
,所以说我在这儿还需不需要在这里边
,把我链接进来的Socket对象保存呢
,当然就不需要了
,这就不需要了
,那么这里边是我的Server这个类的第一个更新升级的地方
,那么第二个更新升级的地方就是我刚才说的
,我们的服务器的链接上面可能会出现
,服务器的链接上面可能会出现我们的
,可能会出现我们的什么
,可能会出现我们的异常
,可能会出现我们这个异常
,所以说在这里边我们把链接的整个的过程
,以及调用我们的线程这部分东西
,我们从Main方法 程序的执行入口里边把它抽离出来
,我们在这把绑定IP地址
,应该是启动服务器和端口的启动服务器的代码
,我们把它封装起来
,封装起来
,封装一个init的方法里边
,来 封装init方法里边
,来 我们这里边我们叫做public void init
,看到没有 我们这样子来
,那么这样子来
,然后把这边放进来
,那么在这里边新建一个ServerSocket
,然后我在这里边就不是把它简单的在Main方法当中去丢出去
,这里边我们要把它全部的try/catch起来
,try
,然后这里边是这儿
,catch Exception e
,然后这里边一旦报错
,我们把整个的启动服务器的这一部分代码
,
我们都把它封装在init里边
,然后用try/catch把它捕获一下
,如果说在这个过程当中捕获到了异常
,就意味着我的服务器启动失败
,那么服务器启动失败
,我们一个非常容易遇到的问题就是我们端口号被占用
,所以说在这里边我们就这样子做一个提示
,System.out.println
,那么这边是服务器启动失败
,那么可能是端口号被占用
,那么这个被占用的端口号是什么呢
,来 端口号
,来 可能是端口号被占用
,来 这边把它隔开
,然后++
,然后这个端口号是什么呢
,SERVER
,Server什么呢
,SERVER
,这里边这个端口号
,这里边端口号的话
,我最好就把它抽离出来
,就不写在这儿
,因为我在下面要用它
,所以说我在这边把端口号定义成一个常量
,来 定义在这
,服务器监听的端口号
,服务器监听的端口号
,来 这里边我叫做public static
,然后加上final 这就是常量了
,public static final int SERVER_PORT
,好 等于40000
,看到没有
,那我这里边当然就是我们的常量了
,SERVER_PORT
,好
,然后这里边也是SERVER_PORT
,那么在这里边如果说这边出现异常
,那服务器启动失败
,那么可能是端口号被占用
,所以说这个时候你如果出现这个提示
,你就去看你的端口号是不是被占了
,那么具体怎么看端口号是不是占了
,然后怎么把这个被占用的端口号解除
,
我在前面课程当中给大家讲过这个办法
,所以说我这儿就不啰嗦了
,那么这里边就是我的Server它的更新的内容
,我这边细节上来讲做了三个更新
,一个把我们保存客户端的这个集合
,把它变成我们自己的ChatRoomMap数据结构
,第二个
,我们把启动服务器的代码把它封装到了这个init里边
,那么第三个更新细节
,我把这个服务器监听的端口号我把它抽离出了一个常数
,那么接下来我在服务器 在程序的执行入口里边
,我们要来运行它
,来 Server
,我就要这样子玩了
,Server server等于new一个Server
,new一个Server对象
,然后用server.init
,我就这样子来实现
,那么这是我们的Server服务的这个类
,那么这里边进行这么一个更新
,进行这么一个更新
,第二次的升级
,好吧
,那么Server这个类它的升级工作暂时就做这么几个步骤
,你们下去一定要把这个逻辑把它理解一下
,当然这里边我都做了详细的注释
,应该都看得懂
,那么在这里边搞清楚了以后
,这里边搞清楚了以后
,那么我们接下来就是服务器端的线程类的二次升级
,所以说我们把这个线程类打开
,服务器端的线程类在这
,好
,服务器端的线程类打开
,这边已经没有了
,对不
,那么对它的升级就要复杂一些
,因为我们对它的升级要在线程类里边要判断我们
,接收到客户端的信息的类型
,这里边就要用到我们的协议字符
,
然后根据不同的类型
,然后来分别予以不同的这个动作
,如果是公聊信息就是广播
,如果是私聊信息
,那就找到它的目标用户
,然后给它单独的对这个客户端进行转发
,所以这里边最核心的逻辑是要实现这个逻辑
,那么实现这个逻辑的话
,我们具体要做哪些细节上的更新呢
,我们一点一点的来
,不着急
,那么在这里边基本的前面的东西
,我们基本的前面的东西
,这个socket
,还有这个br
,这些东西都没什么
,然后在我的构造器里边这边
,就不需要
,在这里边
,不需要在这里边去实例化这个输入流了
,我把这个输入流给去掉
,因为那个输入流我们要在执行体里边去获取
,获取了以后
,我们还要把它保存到我们的数据结构ChatRoomMap里面去
,所以说在这里边我们构造器简化了
,反而简单了
,然后在我们的整个run里边
,这里边我们就要好好的来实现了
,它里边的东西就会变复杂了
,远远不是这么简单了
,那么在这里边的东西
,我就把run方法里边的东西全部干掉
,我推倒重写
,来 run方法里面全部干掉
,推倒重写
,这个里边的东西也暂时先把它简化
,来
,那么run方法里边怎么做
,首先来一个try/catch
,我对整个run方法里边的所有东西进行了一个try/catch
,那么这里边如果说出错
,一会我要处理
,一会儿来处理
,来
,
这里边是这样子
,然后在这里边run里边我要做什么东西
,首先
,首先要获取
,首先我原来在构造器里边初始化的
,获取客户端对应的输入流
,输入流在这儿
,我在这里面获取
,对应客户端的Socket套接字的输入流
,然后br等于new一个BufferedReader
,然后在这里边new一个转换流InputStreamReader
,然后在这里边是socket.getInputStream
,当然在这里边最好转换的指明它的GBK编码方式
,编码方式 看到没有
,这里边是获取的输入流
,获取到输入流以后再拿到输出流
,再拿到客户端对应的输出流
,那么这里边就是out
,这里边应该是ps
,我这边前面定义ps没
,前面没有定义ps
,那我这边就要定义ps
,private PrintStream ps 等于null
,好
,然后在这里边ps等于new PrintStream
,然后在这里边显然是我们的socket.getOutputStream
,然后true
,然后这里边GBK
,这些东西都是前面写烂的东西
,我不做多余的解释了
,那么这边拿到输出流以后
,我们就通过这个输入流读数据
,先通过输入流接收数据
,先通过输入流去读数据
,那么这里边我们读的数据放在这个字符串lines里边 等于null
,
好
,然后我们通过while循环
,来 这边while
,while循环里边它不等于空
,null
,谁不等于空 呢
,lines
,lines等于什么
,等于我们的br.readLine
,那么这个读出来的lines不等于空的话
,就客户端有信息过来
,客户端有信息过来
,我就把读到的
,先读什么呢
,先读取客户端发送来的用户名称
,也就是说什么呢
,也就是说一会我在做客户端升级的时候
,同学们
,我们原来是直接在客户端上面输信息
,直接敲回车就发送给服务器端了
,是这样子玩的
,对不
,但现在不行了
,每个客户端先我要输入用户名
,每一个客户端要弹出一个对话框
,先把这个用户名输进来
,然后把用户名给我服务器传过来
,然后我以这个用户名来标记这个客户端
,用户名和这个客户端一对一的一个标记
,那么这样子我的服务器端我才能够区别
,哪个客户端是哪个客户端
,明白不
,不然的话所有进来都是客户端
,我怎么区别
,我就没法区别了
,那么客户端发出的数据是这个用户
,以后就是这个用户对应发出的数据
,