功能
当某一玩家登陆进游戏时,服务器会对在线所有玩家发一条公告,告诉所有在线玩家该玩家上线了;
当某一玩家退出游戏时,服务器也会发公告告诉所有在线玩家该玩家下线了;
每一个玩家可以发公告,也可以和某一玩家进行私聊。
涉及的技术
Scoket编程、多线程
实现的思路
那客户端如何与服务器端进行通信呢?
它们可以通过Socket对象的写入和读取来进行通信。
首先,我们得在服务器实例化⼀个 ServerSocket 对象,表示通过服务器上的端口通信
ServerSocket serverSocket = new ServerSocket(port)
然后服务器再调用ServerSocket 类的 accept() 方法,该方法将⼀直等待,直到客户端连接到服务器上给定的端口
Socket client = serverSocket.accept();
服务器在等待的时候,⼀个客户端实例化⼀个 Socket 对象,指定服务器名称和端口号来请求连接
Socket client = new Socket(ip,port);
Socket 类的构造函数试图将客户端连接到指定端口号的服务器。如果通信建立成功,则在客户端创建⼀个 Socket 对象能够与服务器进行通信。
在服务器端,accept() 方法返回服务器上⼀个新的 socket 引用,该 socket 连接到客户端的
socket。
连接建立后,可以通过I/O流进行通信,每个socket都有一个输入流,一个输出流。
客户端的输出流连接到服务器端的输入流,客户端的输入流连接到服务器的输出流。这里可能有点绕,但只要理解了,也就很简单,不会搞混。
为什么客户端的输出流要连接服务器的输入流?
服务器接收客户端的消息肯定是要通过自己的输入流来接收,那它需要的是客户端给它发的消息,客户端的消息要发出来肯定是需要通过自己的输出流才能把消息发出来,所以客户端的输出流要连接的是服务器的输入流。
为什么客户端的输入流要连接服务器的输出流?
同理,服务器要把消息发给客户端,消息发出就需要用到自己的输出流,客户端要接收消息,就需要用客户端的输入流才能把信息读入进去。
这个项目里还有用到这个函数:
public int getPort()-----返回此套接字连接到的远程端口号
其实socket也并不难理解,它就相当于是一根线,一旦这个连接建立成功,就相当于服务器在线的一头,客户端在线的另一头。
这里的getPort()
也不难理解,比如在客户端使用这个函数,获得的是服务器的端口号(线的另一头),如果在服务器使用这个函数,获得的就算客户端的端口号。
客户端采用的是读写分离
读取服务器发来的信息是一个线程
给服务器发消息是一个线程,读写互不干扰
读线程:
class ReadFormServerThread implements Runnable{
private Socket client;
Scanner in = null;
public ReadFormServerThread(Socket cilent) {
this.client = cilent;
}
@Override
public void run() {
try {
in = new Scanner(client.getInputStream());
in.useDelimiter("\n");
while(true){
if(in.hasNextLine()){
System.out.println("公告:"+in.nextLine());
}
if(client.isClosed()){
//System.out.println("客户端已关闭");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
in.close();
}
}
}
写线程:
class WriteToServerThread implements Runnable {
private Socket client;
Scanner scanner = null;
PrintStream write = null;
public WriteToServerThread(Socket cilent) {
this.client = cilent;
}
@Override
public void run() {
scanner = new Scanner(System.in);
scanner.useDelimiter("\n");
try {
write = new PrintStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
while(true){
String str = null;
System.out.println("请输入要发送的信息....");
if(scanner.hasNextLine()){
str = scanner.nextLine();
write.println(str);
}
if(str.equals("bye")){
try {
System.out.println("您下线了");
client.close();
scanner.close();
write.close();
break;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
项目中遇到的困难
刚开始时感觉到无从下手,不知道用哪些知识把这个项目搭起来,后来查了一些资料,看了一些博客,也向周围的学长请教了一下,才慢慢有了思路,万事开头难,只要有了开始,后边就算有问题也是可以解决的。
因为是自己才开始做,所以先写了一个单线程的,调试成功后才改成多线程版本的。
尽管先做的是单线程比较简单的版本,但我还是遇到了困难,在调试的时候,我发现socket根本就没连接成功,客户端一直显示的等待连接,我反复检查代码,没有发现错误,后来我就重新检查了一下逻辑,发现服务器是要等客户端发来一个信息,这个连接才能建立成功,可是客户端的代码也是先等服务器发来一条信息,才会给服务器发信息,所以就进入了死等状态,谁都不肯先发,连接不能建立成功。
解决方案
在客户端,让客户端先给服务器发送一条消息,然后再读取服务器发来的消息
在多线程里,先启动写线程,再启动读线程,为了保证写线程一定先启动,在写线程启动后,休眠1秒钟,再启动读线程。
总结
给各位小白程序猿强烈建议,某个知识学完了,一定要会使用,不但要单独会使用,更重要的是把它和其他知识结合起来使用,发挥出它最大的作用,而和其他知识结合其他使用,最能体现的就是做项目,一个项目它肯定要用的知识很广泛,不单单是某个知识点。
如果刚开始做,觉得很难,没关系,你可以找一个小项目练练手,不懂的多问问老师或者自己找博客,网上有很多资源的。
万事开头难,肯定刚开始会觉得无从下手,但不要还没开始就放弃了,没开始你怎么知道你不行呢?相信你自己,没什么是不可以的,人的潜力是无限的,只要你肯挖掘。
就算真的不会,你可以先把这个项目分块,然后一块一块来做,先挑你会的做,就算写完全是错的也没关系,删掉继续来,删着删着你就找到感觉了,你就会举得思路很清晰,啪啪啪几下就敲完了(我就算这样),虽然刚开始前几天进度很慢,但只要开始了,后边会越来越快,越写越爽(笑哭),当你自己完成这个项目时,你内心的喜悦是无以言表的,这种自豪感是无法压制的,这样对自己以后做项目的积极性也有很大的帮助。
So,动起来吧,不要望而止步,其实并没有那么难,你都不试试,怎么知道自己不行。或许你离成功之差跨出那一步。
项目源码:
https://github.com/XGDD/exercise2/tree/master/game-notice