在我的理解中,服务器的作用就是接收客户端发来的,并进行数据处理,然后向客户端发送相应的数据。双方的数据交互基于传输层协议,其中包括TCP和UDP。
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:我们的数据通信建立在一条确定的数据通道,双方共同建立和维护这条通路。
- 可靠的: 说明数据的完整性和正确性不需要我们考虑,可以认为拿到的数据在传输过程中没有出错。
- 基于字节流:无论在客户端或是服务器端,数据都是以字节流的形式输入输出。
这里分享一篇文章:如何写一个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()
)后,客户端的阻塞消失,线程会重复的读取信息(虽然信息为空)。