從本篇博客開始,後面幾篇博客會着重介紹Java網絡編程相關方面的知識,主要涉及Socket編程,Http協議編程。
在網絡通訊中,我們把主動發起通信請求的程序稱爲客戶端,而在通訊中等待客戶端發起請求建立連接的程序稱爲服務端。因而網絡編程最重要的就是分別開發客戶端程序和服務端程序。
對於請求建立連接客戶端,Java提供了Socket類用於客戶端開發,主要完成以下四個基本操作:連接遠程主機,發送數據,接收數據,關閉連接。
對於接收連接的服務端,Java提供了ServerSocket類表示服務器Socket,主要完成以下三個基本操作:綁定端口,監聽入站數據,在綁定端口上接受來自遠程機器的連接。
Java利用socket實現了客戶端-服務端全雙工及時通訊,即客戶端和服務端可以同時發送和接收數據。
一、客戶端Socket
1、利用Socket構造器構造套接字
2、Socket嘗試連接服務器主機
3、建立連接後,利用socket獲得輸入輸出流,實現相互交互數據
4、通信結束後,關閉輸入輸出流和Socket連接
下面看一個具體的Client端示例:
package com.wygu.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
public class TestClient {
public static void main(String[] args) {
Socket socket = null;
OutputStream outputStream = null;
InputStream inputStream = null;
try {
//創建Socket套接字,建立和服務端:127.0.0.1,端口號:8080的連接
socket = new Socket("127.0.0.1",8080);
//設置Socket響應超時時間,超時時間按照毫秒度量,下面設置爲10秒
//socket.setSoTimeout(100000);
//返回輸出流,向服務端寫入數據
outputStream = socket.getOutputStream();
//包裝輸出流OutputStream爲Writer,向服務端寫入數據
Writer writer = new OutputStreamWriter(outputStream);
String sendData = "Hello,Server!!";
System.out.println("Client send:"+sendData);
writer.write(sendData);
//強制輸出
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(null!=socket){
//關閉socket連接
socket.close();
}
if(null!=outputStream){
//關閉輸出流
outputStream.close();
}
if(null!=inputStream){
//關閉輸入流
inputStream.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
二、服務端ServerSocket
1、利用ServerSocket()構造器在一個特定端口創建ServerSocket實例
2、ServerSocket使用accept()方法監聽特定端口的入站連接。其中,accept()會一直阻塞,直到一個客戶端嘗試建立連接後,accept()會返回一個客戶端和服務端的Socket實例
3、利用socket獲得輸入輸出流,實現相互交互數據
4、交互完畢後,關閉連接
5、服務端返回到步驟2,等待下一次的連接
下面看一個具體的Server端實例:
package com.wygu.client;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class TestServer {
public static void main(String[] args) {
ServerSocket server = null;
try {
//創建端口號爲8080的ServerSocket實例
server = new ServerSocket(8080);
while(true){
Socket connection = null;
InputStreamReader reader = null;
Writer writer = null;
try {
//阻塞式等待客戶端連接
connection = server.accept();
//創建輸入流,並讀取客戶端發送的數據
reader= new InputStreamReader(connection.getInputStream());
StringBuilder receiveData = new StringBuilder();
for(int c=reader.read();c!=-1;c=reader.read()){
receiveData.append((char)c);
}
System.out.println("Server receive:"+receiveData.toString());
Thread.sleep(1000);
}catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
if(null!=connection){
connection.close();
}
if(null!=reader){
reader.close();
}
if(null!=writer){
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null!=server){
try {
server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
三、Socket通信插件項目
項目中涉及到的功能點:長連接,多線程,消息隊列。
1、客戶端
客戶端的啓動可以通過main()方法啓動,不管被啓動多少次,保證只建立一條鏈路
1)Client
package com.wygu.client;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import com.wygu.queue.MsgInQueue;
public class Client implements Runnable{
//將Socket連接Client設置爲單例模式
private volatile static Client clientInsance = null;
public static Client getInstance(){
if(null==clientInsance){
synchronized (Client.class) {
if(null==clientInsance){
clientInsance = new Client();
}
}
}
return clientInsance;
}
private String serverHost = null;
private String serverPort = null;
private Socket socketClient = null;
private InputStream inputStream = null;
private OutputStream outputStream = null;
private long systemCurTime;
//設置線程沉睡時間
private long minSleepTime = 2;
private long maxSleepTime = 20;
private long sleepTime;
private long heartBeatTime = 60;
private boolean running = true;
public void startClient(String host,String port){
initSocket(host, port);
Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void initSocket(String host, String port){
this.serverHost = host;
this.serverPort = port;
}
private void connect() throws NumberFormatException, IOException{
this.socketClient = new Socket();
this.socketClient.connect(new InetSocketAddress(serverHost, Integer.valueOf(serverPort)));
this.socketClient.setSoTimeout(10000);
this.inputStream = new DataInputStream(socketClient.getInputStream());
this.outputStream = new DataOutputStream(socketClient.getOutputStream());
this.sleepTime = this.minSleepTime;
}
@Override
public void run() {
try {
connect();
} catch (IOException e) {
e.printStackTrace();
}
setActiveTime();
while(running){
try {
Thread.sleep(sleepTime);
//從發送隊列中取出消息,然後發送
if(sendMessage()) {
setActiveTime();
}
//從接收隊列中取出消息,然後轉換
if(inputStream.available() > 0){
onMessage();
setActiveTime();
sleepTime = this.minSleepTime;
}
else{
sleepTime = this.maxSleepTime;
}
if((this.systemCurTime + this.heartBeatTime * 1000L) < System.currentTimeMillis()){
activeTest();
setActiveTime();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void onMessage() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String receiveData = bufferedReader.readLine();
System.out.println("Client receive:"+receiveData);
}
private boolean sendMessage() {
try {
String mString = MsgInQueue.getInstance().getMsg();
if(null!=mString){
System.out.println("Client send:"+mString);
Writer writer = new OutputStreamWriter(outputStream);
writer.write(mString+'\n');//加入換行符,按行讀取
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
//心跳包測試
private void activeTest() throws IOException {
Writer writer = new OutputStreamWriter(outputStream);
String heatBeatStr = "00"+'\n';//加入換行符,按行讀取
writer.write(heatBeatStr);
writer.flush();
}
private void setActiveTime() {
this.systemCurTime = System.currentTimeMillis();
}
}
2)消息隊列
package com.wygu.queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class MsgInQueue {
private volatile static MsgInQueue msgInQueue = null;
//單例模式,保證整個機制中只有一個消息隊列實例
public static MsgInQueue getInstance(){
if(null==msgInQueue){
synchronized (MsgInQueue.class) {
if(null==msgInQueue){
msgInQueue = new MsgInQueue();
}
}
}
return msgInQueue;
}
private ConcurrentLinkedQueue<String> msgQueue = new ConcurrentLinkedQueue<String>();
public String getMsg(){
return msgQueue.poll();
}
public void putMsg(String inputMsg){
msgQueue.add(inputMsg);
}
}
3)Main程序
package com.wygu.client;
import java.util.Scanner;
import com.wygu.queue.MsgInQueue;
public class Main {
@SuppressWarnings("resource")
public static void main(String[] args) {
String clientIp = "127.0.0.1";
String clientport = "10800";
Client.getInstance().startClient(clientIp, clientport);
Scanner in=new Scanner(System.in);
String inputStr = null;
//只建立一條鏈路,實現多線程客戶端訪問服務端
while(!"quit".equals(inputStr=in.nextLine())){
MsgInQueue.getInstance().putMsg(inputStr);
}
}
}
代碼可以實現客戶端和服務端之間只需要建立一條鏈路,通過消息隊列機制實現多線程同時發送消息,爲保證不同的線程收到屬於自身的服務端應答,可以加入消息ID機制實現。
2、服務端
服務端接收到一個客戶端連接後,會創建一個線程處理該連接的消息機制
1)Server端
package com.wygu.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server implements Runnable{
private String serverPort;
private Server(String port){
this.setServerPort(port);
}
public static void startServer(String port){
Thread thread = new Thread(new Server(port));
thread.start();
}
@Override
public void run() {
try {
ServerSocket _server = new ServerSocket(Integer.valueOf(this.getServerPort()));
System.out.println(String.format("Server [%s] startup success!", _server.getLocalSocketAddress()));
while (true) {
try {
Socket socket = _server.accept();
Connection connection = new Connection(socket);
System.out.println("Accept socket from " + socket.getInetAddress().getHostAddress() + " " + socket.getPort());
new Thread(connection).start();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public String getServerPort() {
return serverPort;
}
public void setServerPort(String serverPort) {
this.serverPort = serverPort;
}
}
2)消息處理線程
package com.wygu.server;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
public class Connection implements Runnable{
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
public Connection(Socket socket) {
this.socket = socket;
}
private void init() throws IOException{
this.inputStream = socket.getInputStream();
this.outputStream = socket.getOutputStream();
}
@Override
public void run() {
try {
init();
Writer writer = new OutputStreamWriter(outputStream);
while (true) {
try {
String receiveData = null;
if (inputStream.available() > 0) {
receiveData = readMsg(inputStream);
if(null!=receiveData){
System.out.println("Server receive:"+receiveData);
String sendData = "I have received!"+'\n'; //服務端給予客戶端應答
System.out.println("Server send:"+sendData);
writer.write(sendData);
writer.flush();
}
}
}catch (Exception e){
e.printStackTrace();
}
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private String readMsg(InputStream in) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
String receiveData = bufferedReader.readLine();
//檢查心跳包
if ("00".equals(receiveData)) {
System.out.println("Server heart beat!");
return null;
}
return receiveData;
}
}
3)Main程序
package com.wygu.server;
public class Main {
public static void main(String[] args) {
String serverPort = "10800";
Server.startServer(serverPort);
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}