JAVA Socket程式設計基礎(轉載)
Java Socket程式設計
對於Java Socket程式設計而言,有兩個概念,一個是ServerSocket,一個是Socket。服務端和客戶端之間通過Socket建立連線,之後它們就可以進行通訊了。首先ServerSocket將在服務端監聽某個埠,當發現客戶端有Socket來試圖連線它時,它會accept該Socket的連線請求,同時在服務端建立一個對應的Socket與之進行通訊。這樣就有兩個Socket了,客戶端和服務端各一個。
對於Socket之間的通訊其實很簡單,服務端往Socket的輸出流裡面寫東西,客戶端就可以通過Socket的輸入流讀取對應的內容。Socket與Socket
1、客戶端寫服務端讀
服務端程式碼
Java程式碼- publicclass Server {
- publicstaticvoid main(String args[]) throws IOException {
- //為了簡單起見,所有的異常資訊都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在埠8899上
- ServerSocket server = new
- //server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuilder sb = new
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from client: " + sb);
- reader.close();
- socket.close();
- server.close();
- }
- }
public class Server {
public static void main(String args[]) throws IOException {
//為了簡單起見,所有的異常資訊都往外拋
int port = 8899;
//定義一個ServerSocket監聽在埠8899上
ServerSocket server = new ServerSocket(port);
//server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
Socket socket = server.accept();
//跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
while ((len=reader.read(chars)) != -1) {
sb.append(new String(chars, 0, len));
}
System.out.println("from client: " + sb);
reader.close();
socket.close();
server.close();
}
}
服務端從Socket的InputStream中讀取資料的操作也是阻塞式的,如果從輸入流中沒有讀取到資料程式會一直在那裡不動,直到客戶端往Socket的輸出流中寫入了資料,或關閉了Socket的輸出流。當然,對於客戶端的Socket也是同樣如此。在操作完以後,整個程式結束前記得關閉對應的資源,即關閉對應的IO流和Socket。
客戶端程式碼
Java程式碼- publicclass Client {
- publicstaticvoid main(String args[]) throws Exception {
- //為了簡單起見,所有的異常都直接往外拋
- String host = "127.0.0.1"; //要連線的服務端IP地址
- int port = 8899; //要連線的服務端對應的監聽埠
- //與服務端建立連線
- Socket client = new Socket(host, port);
- //建立連線後就可以往服務端寫資料了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.flush();//寫完後要記得flush
- writer.close();
- client.close();
- }
- }
public class Client {
public static void main(String args[]) throws Exception {
//為了簡單起見,所有的異常都直接往外拋
String host = "127.0.0.1"; //要連線的服務端IP地址
int port = 8899; //要連線的服務端對應的監聽埠
//與服務端建立連線
Socket client = new Socket(host, port);
//建立連線後就可以往服務端寫資料了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.flush();//寫完後要記得flush
writer.close();
client.close();
}
}
對於客戶端往Socket的輸出流裡面寫資料傳遞給服務端要注意一點,如果寫操作之後程式不是對應著輸出流的關閉,而是進行其他阻塞式的操作(比如從輸入流裡面讀資料),記住要flush一下,只有這樣服務端才能收到客戶端傳送的資料,否則可能會引起兩邊無限的互相等待。在稍後講到客戶端和服務端同時讀和寫的時候會說到這個問題。
2、客戶端和服務端同時讀和寫
前面已經說了Socket之間是雙向通訊的,它既可以接收資料,同時也可以傳送資料。
服務端程式碼
Java程式碼- publicclass Server {
- publicstaticvoid main(String args[]) throws IOException {
- //為了簡單起見,所有的異常資訊都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在埠8899上
- ServerSocket server = new ServerSocket(port);
- //server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuilder sb = new StringBuilder();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- server.close();
- }
- }
public class Server {
public static void main(String args[]) throws IOException {
//為了簡單起見,所有的異常資訊都往外拋
int port = 8899;
//定義一個ServerSocket監聽在埠8899上
ServerSocket server = new ServerSocket(port);
//server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
Socket socket = server.accept();
//跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
while ((len=reader.read(chars)) != -1) {
sb.append(new String(chars, 0, len));
}
System.out.println("from client: " + sb);
//讀完後寫一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.close();
reader.close();
socket.close();
server.close();
}
}
在上述程式碼中首先我們從輸入流中讀取客戶端傳送過來的資料,接下來我們再往輸出流裡面寫入資料給客戶端,接下來關閉對應的資原始檔。而實際上上述程式碼可能並不會按照我們預先設想的方式執行,因為從輸入流中讀取資料是一個阻塞式操作,在上述的while迴圈中當讀到資料的時候就會執行迴圈體,否則就會阻塞,這樣後面的寫操作就永遠都執行不了了。除非客戶端對應的Socket關閉了阻塞才會停止,while迴圈也會跳出。針對這種可能永遠無法執行下去的情況的解決方法是while迴圈需要在裡面有條件的跳出來,縱觀上述程式碼,在不斷變化的也只有取到的長度len和讀到的資料了,len已經是不能用的了,唯一能用的就是讀到的資料了。針對這種情況,通常我們都會約定一個結束標記,當客戶端傳送過來的資料包含某個結束標記時就說明當前的資料已經發送完畢了,這個時候我們就可以進行迴圈的跳出了。那麼改進後的程式碼會是這個樣子:
Java程式碼- publicclass Server {
- publicstaticvoid main(String args[]) throws IOException {
- //為了簡單起見,所有的異常資訊都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在埠8899上
- ServerSocket server = new ServerSocket(port);
- //server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- server.close();
- }
- }
public class Server {
public static void main(String args[]) throws IOException {
//為了簡單起見,所有的異常資訊都往外拋
int port = 8899;
//定義一個ServerSocket監聽在埠8899上
ServerSocket server = new ServerSocket(port);
//server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
Socket socket = server.accept();
//跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//讀完後寫一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.close();
reader.close();
socket.close();
server.close();
}
}
在上述程式碼中,當服務端讀取到客戶端傳送的結束標記,即“eof”時就會結束資料的接收,終止迴圈,這樣後續的程式碼又可以繼續進行了。
客戶端程式碼
Java程式碼- publicclass Client {
- publicstaticvoid main(String args[]) throws Exception {
- //為了簡單起見,所有的異常都直接往外拋
- String host = "127.0.0.1"; //要連線的服務端IP地址
- int port = 8899; //要連線的服務端對應的監聽埠
- //與服務端建立連線
- Socket client = new Socket(host, port);
- //建立連線後就可以往服務端寫資料了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.flush();
- //寫完以後進行讀操作
- Reader reader = new InputStreamReader(client.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuffer sb = new StringBuffer();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from server: " + sb);
- writer.close();
- reader.close();
- client.close();
- }
- }
public class Client {
public static void main(String args[]) throws Exception {
//為了簡單起見,所有的異常都直接往外拋
String host = "127.0.0.1"; //要連線的服務端IP地址
int port = 8899; //要連線的服務端對應的監聽埠
//與服務端建立連線
Socket client = new Socket(host, port);
//建立連線後就可以往服務端寫資料了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.flush();
//寫完以後進行讀操作
Reader reader = new InputStreamReader(client.getInputStream());
char chars[] = new char[64];
int len;
StringBuffer sb = new StringBuffer();
while ((len=reader.read(chars)) != -1) {
sb.append(new String(chars, 0, len));
}
System.out.println("from server: " + sb);
writer.close();
reader.close();
client.close();
}
}
在上述程式碼中我們先是給服務端傳送了一段資料,之後讀取服務端返回來的資料,跟之前的服務端一樣在讀的過程中有可能導致程式一直掛在那裡,永遠跳不出while迴圈。這段程式碼配合服務端的第一段程式碼就正好讓我們分析服務端永遠在那裡接收資料,永遠跳不出while迴圈,也就沒有之後的服務端返回資料給客戶端,客戶端也就不可能接收到服務端返回的資料。解決方法如服務端第二段程式碼所示,在客戶端傳送資料完畢後,往輸出流裡面寫入結束標記告訴服務端資料已經發送完畢了,同樣服務端返回資料完畢後也發一個標記告訴客戶端。那麼修改後的客戶端程式碼就應該是這個樣子:
Java程式碼
- publicclass Client {
- publicstaticvoid main(String args[]) throws Exception {
- //為了簡單起見,所有的異常都直接往外拋
- String host = "127.0.0.1"; //要連線的服務端IP地址
- int port = 8899; //要連線的服務端對應的監聽埠
- //與服務端建立連線
- Socket client = new Socket(host, port);
- //建立連線後就可以往服務端寫資料了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.write("eof");
- writer.flush();
- //寫完以後進行讀操作
- Reader reader = new InputStreamReader(client.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuffer sb = new StringBuffer();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from server: " + sb);
- writer.close();
- reader.close();
- client.close();
- }
- }
public class Client {
public static void main(String args[]) throws Exception {
//為了簡單起見,所有的異常都直接往外拋
String host = "127.0.0.1"; //要連線的服務端IP地址
int port = 8899; //要連線的服務端對應的監聽埠
//與服務端建立連線
Socket client = new Socket(host, port);
//建立連線後就可以往服務端寫資料了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.write("eof");
writer.flush();
//寫完以後進行讀操作
Reader reader = new InputStreamReader(client.getInputStream());
char chars[] = new char[64];
int len;
StringBuffer sb = new StringBuffer();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {
sb.append(temp.substring(0, index));
break;
}
sb.append(new String(chars, 0, len));
}
System.out.println("from server: " + sb);
writer.close();
reader.close();
client.close();
}
}
我們日常使用的比較多的都是這種客戶端傳送資料給服務端,服務端接收資料後再返回相應的結果給客戶端這種形式。只是客戶端和服務端之間不再是這種一對一的關係,而是下面要講到的多個客戶端對應同一個服務端的情況。
3、多個客戶端連線同一個服務端
像前面講的兩個例子都是服務端接收一個客戶端的請求之後就結束了,不能再接收其他客戶端的請求了,這往往是不能滿足我們的要求的。通常我們會這樣做:
Java程式碼- publicclass Server {
- publicstaticvoid main(String args[]) throws IOException {
- //為了簡單起見,所有的異常資訊都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在埠8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- }
- }
- }
public class Server {
public static void main(String args[]) throws IOException {
//為了簡單起見,所有的異常資訊都往外拋
int port = 8899;
//定義一個ServerSocket監聽在埠8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
Socket socket = server.accept();
//跟客戶端建立好連線之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過來的資訊了。
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//讀完後寫一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.close();
reader.close();
socket.close();
}
}
}
在上面程式碼中我們用了一個死迴圈,在迴圈體裡面ServerSocket呼叫其accept方法試圖接收來自客戶端的連線請求。當沒有接收到請求的時候,程式會在這裡阻塞直到接收到來自客戶端的連線請求,之後會跟當前建立好連線的客戶端進行通訊,完了後會接著執行迴圈體再次嘗試接收新的連線請求。這樣我們的ServerSocket就能接收來自所有客戶端的連線請求了,並且與它們進行通訊了。這就實現了一個簡單的一個服務端與多個客戶端進行通訊的模式。
上述例子中雖然實現了一個服務端跟多個客戶端進行通訊,但是還存在一個問題。在上述例子中,我們的服務端處理客戶端的連線請求是同步進行的,每次接收到來自客戶端的連線請求後,都要先跟當前的客戶端通訊完之後才能再處理下一個連線請求。這在併發比較多的情況下會嚴重影響程式的效能,為此,我們可以把它改為如下這種非同步處理與客戶端通訊的方式:
Java程式碼- publicclass Server {
- publicstaticvoid main(String args[]) throws IOException {
- //為了簡單起見,所有的異常資訊都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在埠8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //每接收到一個Socket就建立一個新的執行緒來處理它
- new Thread(new Task(socket)).start();
- }
- }
- /**
- * 用來處理Socket請求的
- */
- staticclass Task implements Runnable {
- private Socket socket;
- public Task(Socket socket) {
- this.socket = socket;
- }
- publicvoid run() {
- try {
- handleSocket();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 跟客戶端Socket進行通訊
- * @throws Exception
- */
- privatevoid handleSocket() throws Exception {
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = newchar[64];
- int len;
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- }
- }
- }
public class Server {
public static void main(String args[]) throws IOException {
//為了簡單起見,所有的異常資訊都往外拋
int port = 8899;
//定義一個ServerSocket監聽在埠8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
Socket socket = server.accept();
//每接收到一個Socket就建立一個新的執行緒來處理它
new Thread(new Task(socket)).start();
}
}
/**
* 用來處理Socket請求的
*/
static class Task implements Runnable {
private Socket socket;
public Task(Socket socket) {
this.socket = socket;
}
public void run() {
try {
handleSocket();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 跟客戶端Socket進行通訊
* @throws Exception
*/
private void handleSocket() throws Exception {
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//讀完後寫一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.close();
reader.close();
socket.close();
}
}
}
在上面程式碼中,每次ServerSocket接收到一個新的Socket連線請求後都會新起一個執行緒來跟當前Socket進行通訊,這樣就達到了非同步處理與客戶端Socket進行通訊的情況。
在從Socket的InputStream中接收資料時,像上面那樣一點點的讀就太複雜了,有時候我們就會換成使用BufferedReader來一次讀一行,如:
Java程式碼- publicclass Server {
- publicstaticvoid main(String args[]) throws IOException {
- //為了簡單起見,所有的異常資訊都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在埠8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //每接收到一個Socket就建立一個新的執行緒來處理它
- new Thread(new Task(socket)).start();
- }
- }
- /**
- * 用來處理Socket請求的
- */
- staticclass Task implements Runnable {
- private Socket socket;
- public Task(Socket socket) {
- this.socket = socket;
- }
- publicvoid run() {
- try {
- handleSocket();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 跟客戶端Socket進行通訊
- * @throws Exception
- */
- privatevoid handleSocket() throws Exception {
- BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((temp=br.readLine()) != null) {
- System.out.println(temp);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就結束接收
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.write("eof\n");
- writer.flush();
- writer.close();
- br.close();
- socket.close();
- }
- }
- }
public class Server {
public static void main(String args[]) throws IOException {
//為了簡單起見,所有的異常資訊都往外拋
int port = 8899;
//定義一個ServerSocket監聽在埠8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server嘗試接收其他Socket的連線請求,server的accept方法是阻塞式的
Socket socket = server.accept();
//每接收到一個Socket就建立一個新的執行緒來處理它
new Thread(new Task(socket)).start();
}
}
/**
* 用來處理Socket請求的
*/
static class Task implements Runnable {
private Socket socket;
public Task(Socket socket) {
this.socket = socket;
}
public void run() {
try {
handleSocket();
} catch (Exception e) {
e.printStackTrace();
}
}
/**