前言
本文从最简单的socket网络编程开始,逐渐深入,扩展到复杂情况下网络通信.
一. 单客户端
一个服务器,一个客户端.
服务器端
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
public class DateServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(2017);
while (true){
Socket socket = serverSocket.accept();
OutputStreamWriter outputStreamWriter =
new OutputStreamWriter(socket.getOutputStream());
outputStreamWriter.write(new Date()+"\n");
outputStreamWriter.close();
socket.close();
}
}
}
客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class DateClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 2017);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
System.out.println("The date on the server is: "+
bufferedReader.readLine());
socket.close();
}
}
二. 多客户端
多个客户端与服务器通信,服务器为每个客户端开辟一个线程.
服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
public class DictionaryServer {
public static int LISTENING_PORT = 2017;
public static void main(String[] args) throws IOException {
ServerSocket serverSocket =
new ServerSocket(LISTENING_PORT);
System.out.println("Server started.");
while (true) {
Socket socket = serverSocket.accept();
DictionaryClientThread dictionaryClientThread =
new DictionaryClientThread(socket);
dictionaryClientThread.start();
}
}
}
class DictionaryClientThread extends Thread {
private int CLIENT_REQUEST_TIMEOUT = 15*60*1000; // 15 min.
private Socket mSocket;
private BufferedReader mSocketReader;
private PrintWriter mSocketWriter;
public DictionaryClientThread(Socket aSocket)
throws IOException {
mSocket = aSocket;
mSocket.setSoTimeout(CLIENT_REQUEST_TIMEOUT);
mSocketReader = new BufferedReader(
new InputStreamReader(mSocket.getInputStream()));
mSocketWriter = new PrintWriter(
new OutputStreamWriter(mSocket.getOutputStream()));
}
public void run() {
System.out.println(new Date().toString() + " : " +
"Accepted client : " + mSocket.getInetAddress() +
":" + mSocket.getPort());
try {
mSocketWriter.println("Dictionary server ready.");
mSocketWriter.flush();
while (!isInterrupted()) {
String word = mSocketReader.readLine();
if (word == null)
break; // Client closed the socket
String translation = getTranslation(word);
mSocketWriter.println(translation);
mSocketWriter.flush();
}
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(new Date().toString() + " : " +
"Connection lost : " + mSocket.getInetAddress() +
":" + mSocket.getPort());
}
private String getTranslation(String aWord) {
if (aWord.equalsIgnoreCase("network")) {
return "网络";
} else if (aWord.equalsIgnoreCase("firewall")) {
return "防火墙";
} else {
return "未知单词";
}
}
}
客户端
import java.io.*;
import java.net.Socket;
public class DictionaryClient {
private static int SERVER_RESPONSE_TIMEOUT = 60*1000;
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 2017);
socket.setSoTimeout(SERVER_RESPONSE_TIMEOUT);
BufferedReader socketReader = new BufferedReader(
new InputStreamReader(socket.getInputStream()) );
PrintWriter socketWriter =
new PrintWriter(socket.getOutputStream());
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in) );
String welcomeMessage = socketReader.readLine();
System.out.println(welcomeMessage);
try {
while (true) {
String word = consoleReader.readLine();
socketWriter.println(word);
socketWriter.flush();
String translation = socketReader.readLine();
System.out.println(translation);
}
} finally {
socket.close();
}
}
}
三. 群聊模式
服务器同步接受各个客户端的消息并且将消息分发至各个客户端.
服务器端
/**
*
* Nakov Chat Server is multithreaded chat server. It accepts
* multiple clients simultaneously and serves them. Clients are
* able to send messages to the server. When some client sends
* a message to the server, the message is dispatched to all
* the clients connected to the server.
*
* The server consists of two components - "server core" and
* "client handlers".
*
* The "server core" consists of two threads:
* - NakovChatServer - accepts client connections, creates
* client threads to handle them and starts these threads
* - ServerDispatcher - waits for messages and when some
* message arrive sends it to all the clients connected to
* the server
*
* The "client handlers" consist of two threads:
* - ClientListener - listens for message arrivals from the
* socket and forwards them to the ServerDispatcher thread
* - ClientSender - sends messages to the client
*
* For each accepted client, a ClientListener and ClientSender
* threads are created and started. A Client object is also
* created to maintain the information about the client and is
* added to the ServerDispatcher's clients list. When some
* client is disconnected, is it removed from the clients list
* and both its ClientListener and ClientSender threads are
* interrupted.
*/
import java.net.*;
import java.io.*;
import java.util.Vector;
/**
* NakovChatServer class is the entry point for the server.
* It opens a server socket, starts the dispatcher thread and
* infinitely accepts client connections, creates threads for
* handling them and starts these threads.
*/
public class NakovChatServer {
public static final int LISTENING_PORT = 2017;
public static String KEEP_ALIVE_MESSAGE = "!keep-alive";
public static int CLIENT_READ_TIMEOUT = 5*60*1000;
private static ServerSocket mServerSocket;
private static ServerDispatcher mServerDispatcher;
public static void main(String[] args) {
// Start listening on the server socket
bindServerSocket();
// Start the ServerDispatcher thread
mServerDispatcher = new ServerDispatcher();
mServerDispatcher.start();
// Infinitely accept and handle client connections
handleClientConnections();
}
private static void bindServerSocket() {
try {
mServerSocket = new ServerSocket(LISTENING_PORT);
System.out.println("NakovChatServer started on " +
"port " + LISTENING_PORT);
} catch (IOException ioe) {
System.err.println("Can not start listening on " +
"port " + LISTENING_PORT);
ioe.printStackTrace();
System.exit(-1);
}
}
private static void handleClientConnections() {
while (true) {
try {
Socket socket = mServerSocket.accept();
Client client = new Client();
client.mSocket = socket;
ClientListenerr clientListener = new
ClientListenerr(client, mServerDispatcher);
ClientSender clientSender =
new ClientSender(client, mServerDispatcher);
client.mClientListener = clientListener;
clientListener.start();
client.mClientSender = clientSender;
clientSender.start();
mServerDispatcher.addClient(client);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
/**
* ServerDispatcher class is purposed to listen for messages
* received from the clients and to dispatch them to all the
* clients connected to the chat server.
*/
class ServerDispatcher extends Thread {
private Vector mMessageQueue = new Vector();
private Vector mClients = new Vector();
/**
* Adds given client to the server's client list.
*/
public synchronized void addClient(Client aClient) {
mClients.add(aClient);
}
/**
* Deletes given client from the server's client list if
* the client is in the list.
*/
public synchronized void deleteClient(Client aClient) {
int clientIndex = mClients.indexOf(aClient);
if (clientIndex != -1)
mClients.removeElementAt(clientIndex);
}
/**
* Adds given message to the dispatcher's message queue and
* notifies this thread to wake up the message queue reader
* (getNextMessageFromQueue method). dispatchMessage method
* is called by other threads (ClientListener) when a
* message is arrived.
*/
public synchronized void dispatchMessage(
Client aClient, String aMessage) {
Socket socket = aClient.mSocket;
String senderIP =
socket.getInetAddress().getHostAddress();
String senderPort = "" + socket.getPort();
aMessage = senderIP + ":" + senderPort +
" : " + aMessage;
mMessageQueue.add(aMessage);
notify();
}
/**
* @return and deletes the next message from the message
* queue. If there is no messages in the queue, falls in
* sleep until notified by dispatchMessage method.
*/
private synchronized String getNextMessageFromQueue()
throws InterruptedException {
while (mMessageQueue.size()==0)
wait();
String message = (String) mMessageQueue.get(0);
mMessageQueue.removeElementAt(0);
return message;
}
/**
* Sends given message to all clients in the client list.
* Actually the message is added to the client sender
* thread's message queue and this client sender thread
* is notified to process it.
*/
private void sendMessageToAllClients(
String aMessage) {
for (int i=0; i<mClients.size(); i++) {
Client client = (Client) mClients.get(i);
client.mClientSender.sendMessage(aMessage);
}
}
/**
* Infinitely reads messages from the queue and dispatches
* them to all clients connected to the server.
*/
public void run() {
try {
while (true) {
String message = getNextMessageFromQueue();
sendMessageToAllClients(message);
}
} catch (InterruptedException ie) {
// Thread interrupted. Stop its execution
}
}
}
/**
* Client class contains information about a client,
* connected to the server.
*/
class Client {
public Socket mSocket = null;
public ClientListenerr mClientListener = null;
public ClientSender mClientSender = null;
}
/**
* ClientListener class listens for client messages and
* forwards them to ServerDispatcher.
*/
class ClientListenerr extends Thread {
private ServerDispatcher mServerDispatcher;
private Client mClient;
private BufferedReader mSocketReader;
public ClientListenerr(Client aClient, ServerDispatcher
aSrvDispatcher) throws IOException {
mClient = aClient;
mServerDispatcher = aSrvDispatcher;
Socket socket = aClient.mSocket;
socket.setSoTimeout(
NakovChatServer.CLIENT_READ_TIMEOUT);
mSocketReader = new BufferedReader(
new InputStreamReader(socket.getInputStream()) );
}
/**
* Until interrupted, reads messages from the client
* socket, forwards them to the server dispatcher's
* queue and notifies the server dispatcher.
*/
public void run() {
try {
while (!isInterrupted()) {
try {
String message = mSocketReader.readLine();
if (message == null)
break;
mServerDispatcher.dispatchMessage(
mClient, message);
} catch (SocketTimeoutException ste) {
mClient.mClientSender.sendKeepAlive();
}
}
} catch (IOException ioex) {
// Problem reading from socket (broken connection)
}
// Communication is broken. Interrupt both listener and
// sender threads
mClient.mClientSender.interrupt();
mServerDispatcher.deleteClient(mClient);
}
}
/**
* Sends messages to the client. Messages waiting to be sent
* are stored in a message queue. When the queue is empty,
* ClientSender falls in sleep until a new message is arrived
* in the queue. When the queue is not empty, ClientSender
* sends the messages from the queue to the client socket.
*/
class ClientSender extends Thread {
private Vector mMessageQueue = new Vector();
private ServerDispatcher mServerDispatcher;
private Client mClient;
private PrintWriter mOut;
public ClientSender(Client aClient, ServerDispatcher
aServerDispatcher) throws IOException {
mClient = aClient;
mServerDispatcher = aServerDispatcher;
Socket socket = aClient.mSocket;
mOut = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()) );
}
/**
* Adds given message to the message queue and notifies
* this thread (actually getNextMessageFromQueue method)
* that a message is arrived. sendMessage is always called
* by other threads (ServerDispatcher).
*/
public synchronized void sendMessage(String aMessage) {
mMessageQueue.add(aMessage);
notify();
}
/**
* Sends a keep-alive message to the client to check if
* it is still alive. This method is called when the client
* is inactive too long to prevent serving dead clients.
*/
public void sendKeepAlive() {
sendMessage(NakovChatServer.KEEP_ALIVE_MESSAGE);
}
/**
* @return and deletes the next message from the message
* queue. If the queue is empty, falls in sleep until
* notified for message arrival by sendMessage method.
*/
private synchronized String getNextMessageFromQueue()
throws InterruptedException {
while (mMessageQueue.size()==0)
wait();
String message = (String) mMessageQueue.get(0);
mMessageQueue.removeElementAt(0);
return message;
}
/**
* Sends given message to the client's socket.
*/
private void sendMessageToClient(String aMessage) {
mOut.println(aMessage);
mOut.flush();
}
/**
* Until interrupted, reads messages from the message queue
* and sends them to the client's socket.
*/
public void run() {
try {
while (!isInterrupted()) {
String message = getNextMessageFromQueue();
sendMessageToClient(message);
}
} catch (Exception e) {
// Commuication problem
}
// Communication is broken. Interrupt both listener
// and sender threads
mClient.mClientListener.interrupt();
mServerDispatcher.deleteClient(mClient);
}
}
客户端
import java.io.*;
import java.net.*;
/**
* NakovChatClient is a client for Nakov Chat Server. After
* creating a socket connection to the chat server it starts
* two threads. The first one listens for data comming from
* the socket and transmits it to the console and the second
* one listens for data comming from the console and transmits
* it to the socket. After creating the two threads the main
* program's thread finishes its execution, but the two data
* transmitting threads stay running as long as the socket
* connection is not closed. When the socket connection is
* closed, the thread that reads it terminates the program
* execution. Keep-alive messages are ignored when received.
*/
public class NakovChatClient {
public static final String SERVER_HOSTNAME = "localhost";
public static String KEEP_ALIVE_MESSAGE = "!keep-alive";
public static final int SERVER_PORT = 2017;
private static BufferedReader mSocketReader;
private static PrintWriter mSocketWriter;
public static void main(String[] args) {
// Connect to the chat server
try {
Socket socket =
new Socket(SERVER_HOSTNAME, SERVER_PORT);
mSocketReader = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
mSocketWriter = new PrintWriter(new
OutputStreamWriter(socket.getOutputStream()));
System.out.println("Connected to server " +
SERVER_HOSTNAME + ":" + SERVER_PORT);
} catch (IOException ioe) {
System.err.println("Can not connect to " +
SERVER_HOSTNAME + ":" + SERVER_PORT);
ioe.printStackTrace();
System.exit(-1);
}
// Start socket --> console transmitter thread
PrintWriter consoleWriter = new PrintWriter(System.out);
TextDataTransmitter socketToConsoleTransmitter = new
TextDataTransmitter(mSocketReader, consoleWriter);
socketToConsoleTransmitter.setDaemon(false);
socketToConsoleTransmitter.start();
// Start console --> socket transmitter thread
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in));
TextDataTransmitter consoleToSocketTransmitter = new
TextDataTransmitter(consoleReader, mSocketWriter);
consoleToSocketTransmitter.setDaemon(false);
consoleToSocketTransmitter.start();
}
}
/**
* Transmits text data from the given reader to given writer
* and runs as a separete thread.
*/
class TextDataTransmitter extends Thread {
private BufferedReader mReader;
private PrintWriter mWriter;
public TextDataTransmitter(BufferedReader aReader,
PrintWriter aWriter) {
mReader = aReader;
mWriter = aWriter;
}
/**
* Until interrupted reads a text line from the reader
* and sends it to the writer.
*/
public void run() {
try {
while (!isInterrupted()) {
String data = mReader.readLine();
if (! data.equals(NakovChatClient.
KEEP_ALIVE_MESSAGE)) {
mWriter.println(data);
mWriter.flush();
}
}
} catch (IOException ioe) {
System.err.println("Lost connection to server.");
System.exit(-1);
}
}
}
四. 文件传输
客户端向服务器端传送文件,服务端可获取文件名用于保存,获取文件大小计算传输进度.
服务器端
import java.io.*;
import java.net.*;
import java.util.Date;
public class FileServer {
public static int LISTENING_PORT = 2017;
public static void main(String[] args) throws IOException {
ServerSocket serverSocket =
new ServerSocket(LISTENING_PORT);
System.out.println("Server started.");
while (true) {
Socket socket = serverSocket.accept();
FileThread fileThread =
new FileThread(socket);
fileThread.start();
}
}
}
class FileThread extends Thread{
private int CLIENT_REQUEST_TIMEOUT = 15*60*1000; // 15 min.
private Socket fSocket;
private DataInputStream dataInputStream;
private FileOutputStream fileOutputStream;
private String fileName;
private long fileLength;
public FileThread(Socket socket) throws IOException {
fSocket = socket;
fSocket.setSoTimeout(CLIENT_REQUEST_TIMEOUT);
dataInputStream = new DataInputStream(fSocket.getInputStream());
//文件名和长度
fileName = dataInputStream.readUTF();
fileLength = dataInputStream.readLong();
fileOutputStream = new FileOutputStream(new File("E:/"+fileName));
}
@Override
public void run(){
System.out.println(new Date().toString() + " : " +
"Accepted client : " + fSocket.getInetAddress() +
":" + fSocket.getPort());
try {
byte[] sendBytes = new byte[1024];
int transLen = 0; //当前传输文件大小
System.out.println("--开始接收文件<" + fileName +">,文件大小为<" + fileLength +">--");
while (!isInterrupted()) {
int read = 0;
read = dataInputStream.read(sendBytes);
if (read == -1){
break;
}
transLen += read;
System.out.println("接收文件进度" +100 * transLen/fileLength +"%...");
fileOutputStream.write(sendBytes,0,read);
fileOutputStream.flush();
}
System.out.println("--接收文件<" + fileName +">成功--");
fSocket.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (dataInputStream != null){
dataInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
客户端
import java.io.*;
import java.net.Socket;
public class FileClient {
private static FileInputStream fileInputStream;
private static DataOutputStream dataOutputStream;
private static int SERVER_RESPONSE_TIMEOUT = 60*1000; //1h
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 2017);
socket.setSoTimeout(SERVER_RESPONSE_TIMEOUT);
try {
File file = new File("D:/test.doc");
fileInputStream = new FileInputStream(file);
dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeUTF(file.getName());
dataOutputStream.flush();
dataOutputStream.writeLong(file.length());
dataOutputStream.flush();
//传输文件
byte[] sendBytes = new byte[1024];
int read = 0;
while((read = fileInputStream.read(sendBytes,0, sendBytes.length)) >0){
dataOutputStream.write(sendBytes,0,read);
dataOutputStream.flush();
}
}catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (dataOutputStream != null){
dataOutputStream.close();
}
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
socket.close();
}
}
}
总结
本文涵盖的主要内容有 单客户端,多客户端,文件传输,群聊模式,从socket单线程着手,逐渐深入,通过实验探索了socket 编程的美妙之处.
至繁归于至简,复杂的东西简单化了,也就清晰明了了.