代码
这是一个带有UI界面的JAVA网络聊天程序,使用Socket连接完成通信。
JAVA服务端程序
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* Server
*/
public class Server {
private static final int PORT = 4000; // 指定通信端口
public final String END_OF_MESSAGE = "$$";
public void launch() throws IOException {
ServerSocket server = new ServerSocket(PORT);
Socket requSocket = server.accept();
InputStream inStream = requSocket.getInputStream();
OutputStream outputStream = requSocket.getOutputStream();
Scanner in = new Scanner(inStream);
PrintWriter out = new PrintWriter(outputStream, true /* autoFlush */);
out.println("Welcome! Let's talk.");
while (in.hasNextLine()) {
String line = in.nextLine();
if (line.equals("Hello")) {
out.println("Hi");
} else if (line.equals("Bye")) {
out.println("Bye");
out.println(END_OF_MESSAGE);
break;
} else {
out.println("Can't understand what you said!");
}
}
in.close();
out.close();
server.close();
}
public static void main(String[] args) throws IOException {
new Server().launch();
}
}
客户端程序
import java.io.*;
import java.awt.event.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class Client {
private static final String IP = "127.0.0.1"; // 使用本机地址
private static final int PORT = 4000;
private final String LINK_FAIL = "====================
Connection build failed!
";
private final String UNKOWN_HOST = "====================
Unkown host!
";
private final String IO_EXCEPTION = "====================
I/O wrong!
";
public static final String END_OF_MESSAGE = "$$";
private Socket userSocket;
private String userName;
private InputStream inStream;
private Scanner in;
private OutputStream outStream;
private PrintWriter out;
protected boolean serverStop;
public Client(String name) {
userName = name;
}
public void launch() {
UserInterface.creatUI(userName); // 创建一个用户界面
boolean error = false;
try {
creatConnection();
} catch (ConnectException e) {
UserInterface.chatWindow.append(LINK_FAIL);
error = true;
} catch (UnknownHostException e) {
UserInterface.chatWindow.append(UNKOWN_HOST);
error = true;
} catch (IOException e) {
UserInterface.chatWindow.append(IO_EXCEPTION);
error = true;
}
if (error) {
shutDown();
return;
}
String greeting;
while ((greeting = in.nextLine()) == null)
;
serverStop = false;
UserInterface.chatWindow
.append(new SimpleDateFormat("MM-dd HH:mm").format(new Date()) + "
" + "Server: " + greeting + "
");
UserInterface.sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String message = UserInterface.inputText.getText();
if (serverStop || message.equals(""))
return;
out.println(message);
String currentTime = new SimpleDateFormat("MM-dd HH:mm").format(new Date());
UserInterface.chatWindow.append(currentTime + "
" + userName + ": " + message + "
");
UserInterface.inputText.setText("");
UserInterface.inputText.requestFocus();
String line = in.nextLine();
if (line.equals(END_OF_MESSAGE)) {
serverStop = true;
shutDown();
UserInterface.sendButton.setEnabled(false);
} else {
currentTime = new SimpleDateFormat("MM-dd HH:mm").format(new Date());
UserInterface.chatWindow.append(currentTime + "
" + "Server: " + line + "
");
}
}
});
}
private void creatConnection() throws ConnectException, NullPointerException, IOException {
try {
userSocket = new Socket(IP, PORT);
inStream = userSocket.getInputStream();
in = new Scanner(inStream);
outStream = userSocket.getOutputStream();
out = new PrintWriter(outStream, true /* autoFlush */);
} catch(ConnectException connectException) {
throw connectException;
} catch(NullPointerException nullPointerException) {
throw nullPointerException;
} catch(IOException ioException) {
throw ioException;
}
}
private void shutDown() {
in.close();
out.close();
try {
userSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Client client = new Client("Tinshine");
client.launch();
}
}
用户界面
使用JAVA的swing组件实现一个简单的聊天窗口
import javax.swing.*;
import java.awt.*;
/**
* UserInteface
*/
public class UserInterface {
private final static String APP_NAME = "MSN";
public static String nameOfUser;
private static JFrame frame;
private static JPanel background;
private static JPanel sendPanel;
public static JTextArea chatWindow;
public static JTextField inputText;
public static JButton sendButton;
public static JScrollPane chatWinJScrollPane;
public static JScrollBar chatWinJScrollBar;
public static Point chatWinJScrollPoint;
public static void creatUI(String userName) {
nameOfUser = userName;
frame = new JFrame(APP_NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
background = new JPanel();
background.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
background.setLayout(new BoxLayout(background, BoxLayout.Y_AXIS));
chatWindow = new JTextArea(10, 10);
chatWindow.setLineWrap(true);
chatWinJScrollPane = new JScrollPane(chatWindow);
chatWinJScrollBar = chatWinJScrollPane.getVerticalScrollBar();
chatWinJScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
chatWinJScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
background.add(chatWinJScrollPane);
sendPanel = new JPanel();
sendPanel.add(new JLabel(userName + ": "));
inputText = new JTextField(20);
inputText.requestFocus();
sendPanel.add(inputText);
sendButton = new JButton("Send");
sendPanel.add(sendButton);
background.add(sendPanel);
frame.getContentPane().add(background);
frame.pack();
frame.setVisible(true);
}
}
设计过程
- 服务端使用
ServerSocket()
类创建一个Socket通信连接,使用accept()
方法等待客户端请求。 - 客户端使用
Socket()
类,根据指定的IP地址和端口号创建一个到服务端的Socket连接,随后就可以与服务端进行通信。 - UI界面主要负责打印客户端和服务端之间发送的消息,显示客户端用户姓名以及消息的发送时间。
效果如下:
Step1: 启动客户端
Step2: 发送问候语句,得到服务器答复
Step3:发送告别语句,结束Socket连接
API调用栈追踪
以服务端为例,设置断点,追踪调用栈,得到如下所示的调用关系:
在实例化ServerSocket
类的时候,构造函数会调用ServerSocket
类的bind()
方法,后者会调用继承自抽象类AbstractPlainSocketImpl
的PlainSocketImpl
的socketBind()
方法。在socketBind()
中会调用用native
关键字标注的bind0()
方法。从而实现将一个socket连接绑定到指定的本地IP地址和端口号。
native
关键字标注的方法为原生方法,一般是用其他语言写成的函数,常用来实现java语言对操作系统底层接口的访问。JAVA语言本身不能直接对操作系统底层进行操作,但是JAVA允许程序通过JAVA本机接口JNI,使用C/C++等其他语言实现这种操作。
接着,同样的步骤,从ServerSocket
类的listen()
一直追溯到PlainSocketImpl
的socketListen()
方法的listen0()
。该方法主要为了设置允许的最大连接请求队列长度,当请求队列满时,拒绝后来的连接请求。
最后,同样,从ServerSocket
类的accept()
追溯到accept0()
,等待连接请求的到来。
JAVA Socket接口与LINUX Socket接口对比
作为对比,LINUX提供的相应Socket API分别为:
// 创建一个套接字
int socket(itn domain, int type, int protocol);
// 将套接字绑定到地址上
int bind(int sockfd, const struct sockaddr *addr, socklen_t addelen);
// 监听连接
int listen(int sockfd, int backlog);
// 接收一个连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
JAVA通过调用这些系统API来实现它的底层功能。不同之处在于,JAVA将这一切全都封装起来,这使得面向网络的编程对于JAVA程序员来说变得十分简单,我们只需要知道使用哪一个类(实际上就是ServerSocket
和Socket
两个类),为它们传入必要的地址参数,就能够轻松实现Socket通信。
- 在window操作系统中,使用
native
标注的本地方法在编译时会生成一个动态链接库(.dll文件)为JAVA语言提供相应的本地服务。