如何用Java编写一个简单的服务器和客户机
今天我要向大家介绍的是自己编写的一个比较简单的服务器和客户机程序,注意一下哦,比较简单。好了,闲话休提,砸门直入主题。
小编先从客户机和服务器的模型开始讲解。简单来说,我们实现的这种模型呢,我们每一个用户称为一个客户机,用户之间的通信之间需要一个中转,所有客户机的通信都依托于这个中转,很明显,这个中转,就是砸门的服务器了。整个模型比较简单明了,那么,接下来我们就直接进入实现阶段。
我们从实现服务器开始。Java提供了这样的一个类,ServerSocket。我们通过实例化它的方式来创建一个服务器。注意一下,创建服务器需要一个参数port。port指的是端口号。我们知道一台计算机上运行着许多程序,那么,对于另一台计算机来说,它想要连接到这台服务器的这个程序,该如何区分呢?首先当然是根据IP连接到这台计算机,那么,然后呢?对的,你没有想错,就是通过我们的port端口号了。正常情况下,计算机会自动我们创建的程序分配一个port,但是,因为我们创建的是提供给别的计算机连接的服务器,所以我们需要指定一个端口号供给外部计算机连接。注意一下,计算机有的端口号已经被其他程序占用了,要想查看,你可以通过在cmd命令行输入netstat -an来查询。在这里,我们给定我们的端口号为空闲的9999。
1 public static void main(String[] args) {
2 new Server().CreateServer(9999);
3 }
4 public void CreateServer(int port){
5 try {
6 ServerSocket serversocket = new ServerSocket(port);
7 } catch (IOException e) {
8 e.printStackTrace();
9 }
10 }
接下来,我们让服务器进入等待客户机连接的状态。通过查看ServerSocket源码我们会发现,它里面有一个accept方法,其返回值是Socket类型。
1 Socket socket = serversocket.accept();
Socket是Java中的另一个用于通信的类, 我们称它为套接字。事实上,小编是这样看的,Socket相当于建立在服务器和客户端之间的一根通道。我们可以从socket获取它的输入输出流对象,也就是我们的InputStream和OutputStream。这两个输入输出都是采用字节传输的方法,如果你想要读取一行数据可以通过BufferReader bufferreader = new BufferedReader(new InputStreamReader(is))的方式,将InputStream转化成bufferRead之后调用readLine方法就可以了。需要注意的是对于readLine这个方法,/r/n表示消息的读取结束。因此需要对消息进行+“/r/n”的操作来保证读取的正常进行。
因为在等待客户机连接的时候,程序进入阻塞状态,这个时候程序无法进行下去,而且,这样的写法也只能够供给一个客户机连接。为了能够实现简单客户机和服务器的对话,我们需要采用多线程的方式去实现它,并且将实例化socket对象以及之后的这部分代码放入到while循环当中。为了能够实现客户机与客户机的通信,我们建立了一个消息处理类,并且实例化了一个数组队列去存储它,然后将这个队列作为消息处理类的一个属性。这样所有的问题就得到了完美的解决。
1 public void creatserver(int port) {
2 try {
3 ServerSocket serversocket = new ServerSocket(port);
4 System.out.println("服务器已经开启,正在等待客户机连接...");
5 while (true) {
6 Socket sk = serversocket.accept();
7 chatMessage cm = new chatMessage(sk, list);
8 list.add(cm);
9 cm.start();
10 }
11 } catch (IOException e) {
12 e.printStackTrace();
13 }
14 }
消息处理类当中的内容,主要是实现对消息的封装处理等等,以及对消息的鉴别。诸如消息是群发还是私发,如果是群发,那么遍历整个消息处理类的队列,如果是私发,那么同样需要遍历整个队列去寻找我们需要私发的用户名,然后将消息传给它。一些其他的注意就不再赘述了,小编在代码中也给出了相应的注释,特别提出一点,当socket关闭,连接断开,我们同样需要关闭我们的IO流,否则占用资源是比较严重的。
1 public class chatMessage extends Thread {
2 // 客户机的账号
3 public String username;
4 // 客户机的密码
5 public String password;
6 // // 客户机的开启状态
7 // public volatile boolean state;
8 // 客户机对象
9 public Socket s;
10 // 客户机的输入流
11 public InputStream is;
12 // 客户机的输出流
13 public OutputStream os;
14 // 缓冲(读一行数据)
15 public BufferedReader br;
16 // 存储所有客户机对象的数组队列
17 public ArrayList<chatMessage> list;
18
19 /**
20 * chatMessage的构造方法,包括IO流的建立
21 *
22 * @param s
23 * Socket对象
24 * @param list
25 * 存储chatMessage对象的数组队列
26 * @throws IOException
27 * 抛出IO异常
28 */
29 public chatMessage(Socket s, ArrayList<chatMessage> list)
30 throws IOException {
31 this.s = s;
32 this.list = list;
33 is = s.getInputStream();
34 os = s.getOutputStream();
35 br = new BufferedReader(new InputStreamReader(is));
36 }
37
38 /**
39 * 服务器转发消息
40 *
41 * @param str
42 * 转发的消息内容
43 * @throws IOException
44 * 抛出IO异常
45 */
46 public void sendMessage(String str) throws IOException {
47 os.write((str + "\r\n").getBytes());
48 }
49
50 /**
51 * 服务器把消息广播到所有的客户机
52 *
53 * @param str
54 * 广播的消息内容
55 * @param code
56 * 消息类型的判断,1表示为提示成员登录,2表示为广播消息
57 * @throws IOException
58 * 抛出的IO异常
59 */
60 public void sendAllMessage(String str, int code) throws IOException {
61 for (int index = 0; index < list.size(); index++) {
62 if (list.get(index) != this && code == 1) {
63 list.get(index).sendMessage(str);
64 } else if (code == 2) {
65 list.get(index).sendMessage(str);
66 }
67 }
68 System.out.print(str + "\r\n");
69 }
70
71 /**
72 * 读取来自客户端的消息
73 *
74 * @return 返回一个字符串
75 * @throws IOException
76 * 抛出IO异常
77 */
78 public String readMessage() throws IOException {
79 return br.readLine();
80 }
81
82 /**
83 * 服务器把消息转发给某人
84 *
85 * @param str1
86 * 消息内容
87 * @param str2
88 * 消息的接收人
89 */
90 public void