服务器之新手入门

时间:2022-07-12 20:30:52

在我的理解中,服务器的作用就是接收客户端发来的,并进行数据处理,然后向客户端发送相应的数据。双方的数据交互基于传输层协议,其中包括TCP和UDP。

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的可靠的基于字节流的传输层通信协议。

  1. 面向连接:我们的数据通信建立在一条确定的数据通道,双方共同建立和维护这条通路。
  2. 可靠的: 说明数据的完整性和正确性不需要我们考虑,可以认为拿到的数据在传输过程中没有出错。
  3. 基于字节流:无论在客户端或是服务器端,数据都是以字节流的形式输入输出。

这里分享一篇文章:如何写一个web服务器 其中简述了几种简单的服务器的逻辑结构以及分析。
我顺着这个思路实现了两种服务器(java)——单线程和多线程(非线程池)的简单示例,在此主要分析后者的代码结构以及编写中遇到的问题。

服务器端

SimpleServer.java

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/** * Created by admin on 2016/4/24. */
public class SimpleServer extends Frame implements ActionListener, Runnable {

    private Label label = new Label("INPUT:");
    private Panel panel = new Panel();
    private TextField tf = new TextField(10);
    private TextArea ta = new TextArea();
    private Socket socket;
    private ServerSocket serverSocket;

    public SimpleServer() throws HeadlessException {
        //设置显示窗口
        super("简单服务器");
        setSize(400, 300);
        panel.add(label);
        panel.add(tf);
        add("West", panel);
        add("Center", ta);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    }


    public void run(){

        this.setVisible(true);

        try {
            //绑定端口,启动服务器
            serverSocket = new ServerSocket(4000);

        } catch (IOException e) {
            e.printStackTrace();
        }

        if (serverSocket == null)
        {
            System.out.println("Can not set up Server Socket");
        }
        else
        {
            while(true)
            {
                try {
                    //获取客户请求(从请求队列中),建立连接
                    socket = serverSocket.accept();
                    //创建一个线程来处理该链接上的业务
                    new ServerThread(socket, ta).start();

                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    public static void main(String args[]){
        SimpleServer simpleServer = new SimpleServer();
        simpleServer.run();

    }
}

ServerThread.java

import java.awt.*;
import java.io.*;
import java.net.Socket;

/** * Created by admin on 2016/4/24. */
public class ServerThread extends Thread {

    private Socket socket;
    private TextArea area;          //显示信息的区域
    private String serverName;      //服务器线程名称(唯一)
    private static int count;         

    public ServerThread(Socket socket, TextArea area) {
        this.socket = socket;
        this.area = area;
        getServerName();
    }

    @Override
    public void run() {
        super.run();
        try {
            //显示 新的客户端已连接
            area.append("客户端:" + socket.getInetAddress().getHostName() + " (from:" + serverName + ")\n");
            //输入流 BufferedReader与 InputStreamReader配合,可以很方便的将输入流中的byte字节转化成字符串类型
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //输出流 PrintWriter与 OutputStreamWriter配合,与上面相反
            PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
            while (true){
                //获取输入流中的数据(byte),转换成String类型
                String input = in.readLine();
                area.append("C:" + input + " (from:" + serverName + ")\n");
                String output = "get(" + input + ")";
                //将String类型的字符串转化为字节流写入输出流
                out.println(output);
                if (input.equals("bye"))
                {
                    //当客户端发来‘bye’时关闭该连接,随后该线程结束
                    break;
                }

            }
            out.println("bye-bye");
            in.close();
            out.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private synchronized void getServerName(){
        //为每个线程唯一命名。便于显示区分
        serverName = "Serv_" + count;
        ++count;
    }
}

客户端

SimpleClient.java

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/** * Created by admin on 2016/4/24. */
public class SimpleClient extends Frame implements ActionListener,Runnable{
    private Label label = new Label("Input: ");
    private Panel panel = new Panel();
    private TextField tf = new TextField(10);
    private TextArea ta = new TextArea();
    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;

    public SimpleClient(String title) throws HeadlessException {
        super(title);
        setSize(400, 300);
        panel.add(label);
        panel.add(tf);
        tf.addActionListener(this);
        add("West", panel);
        add("Center", ta);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    }


    public void run(){
        this.setVisible(true);
        connect();
        try {
            while (true){

                String str = in.readLine();
                ta.append("S: " + str + "\n");
                if (str.equals("bye-bye")){
                    break;
                }
            }
            in.close();
            out.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    @Override
    public void actionPerformed(ActionEvent e) {
        //如发现连接不存在或已关闭,则重新连接
        if (socket == null || socket.isClosed())
        {
            connect();
        }
        String str = tf.getText();
        byte[] buf = str.getBytes();
        tf.setText(null);
        out.println(str);
        ta.append("C: " + str + "\n");
    }

    private void connect(){
        try {
            //使用socket向服务器端发送连接请求
            socket = new Socket(InetAddress.getLocalHost(), 4000);
            ta.append("服务器:" + socket.getInetAddress().getHostName() + "\n");
            InputStreamReader reader = new InputStreamReader(socket.getInputStream());
            OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
            in = new BufferedReader(reader);
            out = new PrintWriter(writer, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args){

        Thread t1 = new Thread(new SimpleClient("客户端-01"));
        Thread t2 = new Thread(new SimpleClient("客户端-02"));
        Thread t3 = new Thread(new SimpleClient("客户端-03"));
// Thread t4 = new Thread(new SimpleClient("客户端-04"));
// Thread t5 = new Thread(new SimpleClient("客户端-05"));
// Thread t6 = new Thread(new SimpleClient("客户端-06"));
// Thread t7 = new Thread(new SimpleClient("客户端-07"));
// Thread t8 = new Thread(new SimpleClient("客户端-08"));
        t1.start();
        t2.start();
        t3.start();
// t4.start();
// t5.start();
// t6.start();
// t7.start();
// t8.start();
    }
}

效果图如下:
服务器之新手入门


其中遇到了很多问题,有的已经解决,有的依然困扰
1. 在BufferedReader和PrintWriter之前我使用的是基本的输入输出流,具体操作是使用InputStream.read()方法来读取流中的数据存放在byte数组中,再由byte数组转成String,利用String的“+”来组合信息,最后显示到TextArea上。问题显示如下:
服务器之新手入门
String类型的output显示为char[],而且在字符串拼接时会丢失信息,从图中标记的地方可以看出debug对于output给出了不同的值,对此非常不理解。
同时在客户端,从输入流的read函数在读取完信息后(不完整),会顺着循环再读取一次(读取内容为空)再阻塞,而不是立即阻塞。
2. 在输入流没有信息时,read函数会自动的阻塞,但当服务器端强行关闭socket(socket.close() )后,客户端的阻塞消失,线程会重复的读取信息(虽然信息为空)。