Socket本質(zhì)上就是Java封裝了傳輸層上的TCP協(xié)議(注:UDP用的是DatagramSocket類)炸渡。要實現(xiàn)Socket的傳輸坎拐,需要構(gòu)建客戶端和服務(wù)器端。另外岗喉,傳輸?shù)臄?shù)據(jù)可以是字符串和字節(jié)。
基于TCP
首先看一下通信模型:
簡單概括就是以下幾個步驟:
- 創(chuàng)建ServerSocket和Socket
- 打開連接到Socket的輸入/輸出流
- 按照協(xié)議對Socket進行讀寫操作
- 關(guān)閉輸入輸出流,關(guān)閉Socket
下面會通過實現(xiàn)一個登錄的功能來介紹以上幾個步驟
項目運行的時候服務(wù)器的代碼放在ServerApp里,客戶端的代碼放在ClientApp里,先運行ServerApp,又運行ClientApp.兩個app都在一個手機里面
服務(wù)器
完成以上需求可以遵循如下幾個步驟:
- 創(chuàng)建ServerSocket對象,綁定監(jiān)聽端口
- 通過accept()方法監(jiān)聽客戶端請求
- 建立連接后,通過輸入流讀取客戶端發(fā)送的請求信息
- 通過輸出流向客戶端發(fā)送響應(yīng)信息
- 關(guān)閉相關(guān)資源
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread() {
@Override
public void run() {
ServerSocket serverSocket = null;
Socket socket = null;
try {
//步驟①創(chuàng)建ServerSocket對象,綁定監(jiān)聽端口
//參數(shù)port 我們要指定在1024-65535
//如果端口重復(fù),也就是有其他的應(yīng)用使用,會報異常
serverSocket = new ServerSocket(43211);
} catch (Exception e) {
e.printStackTrace();
}
int count = 0;
System.out.println("服務(wù)器端準備好了鏈接...");
//無線循環(huán)接受客戶端的連接,也就是允許多個客戶端連接,當然可以通過ServerSocket的構(gòu)造方法對連接數(shù)做一個限制
//比如最大連接20,當超過20個連接后會報異常
while (true) {
try {
//步驟②通過accept()方法監(jiān)聽客戶端請求
//這個socket是客戶端與服務(wù)器通信的socket
//執(zhí)行accept方法之后,服務(wù)器處于監(jiān)聽狀態(tài),隨時與客戶端連接
socket = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
ServerThread serverThread = new ServerThread(socket);
//設(shè)置優(yōu)先級為4(默認為5),為了保證程序運行的速度,適當降低該線程的優(yōu)先級
serverThread.setPriority(4);
serverThread.start();
//記錄有多少個連接
count++;
System.out.println("當前連接數(shù)" + count);
}
}
}.start();
}
}
public class ServerThread extends Thread {
Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
try {
String info = null;
StringBuilder stringBuilder = new StringBuilder();
//步驟③建立連接后,通過輸入流讀取客戶端發(fā)送的請求信息
//獲取輸入流
is = socket.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
while ((info = br.readLine()) != null) {
stringBuilder.append(info);
}
socket.shutdownInput();
/* try {
Thread.sleep(6*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
String[] split = stringBuilder.toString().split(":");
String name = split[0];
String password = split[1];
//步驟④通過輸出流向客戶端發(fā)送響應(yīng)信息
os = socket.getOutputStream();
pw = new PrintWriter(os);
//模擬驗證賬號密碼是否正確
if ("admin".equals(name) && "123456".equals(password)) {
pw.write("login success!");
} else {
pw.write("login failed!!!");
}
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//步驟⑤關(guān)閉相關(guān)資源
try {
if (pw != null)
pw.close();
if (os != null)
os.close();
if (br != null)
br.close();
if (isr != null)
isr.close();
if (is != null)
is.close();
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客戶端
客戶端布局:
需要以下幾個步驟:
- 創(chuàng)建Socket對象,指明需要連接的服務(wù)器的地址和端口號
- 連接建立后,通過輸出流向服務(wù)器發(fā)送請求信息
- 通過輸入流獲取服務(wù)器的響應(yīng)信息
- 關(guān)閉相關(guān)資源
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private EditText et_password;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt_con).setOnClickListener(this);
et_name = findViewById(R.id.et_name);
et_password = findViewById(R.id.et_password);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_con:
final String name = et_name.getText().toString();
final String password = et_password.getText().toString();
if (name==null || TextUtils.isEmpty(name)){
Toast.makeText(this,"用戶名不能為空",Toast.LENGTH_SHORT).show();
return;
}
if (password==null || TextUtils.isEmpty(password)){
Toast.makeText(this,"密碼不能為空",Toast.LENGTH_SHORT).show();
return;
}
new Thread(){
@Override
public void run() {
try {
//步驟①創(chuàng)建Socket對象,指明需要連接的服務(wù)器的地址和端口號
//host指的是服務(wù)器的地址,因為服務(wù)器和客戶端裝在一個手機上,
Socket socket=new Socket("localhost", 43211);
//步驟②連接建立后,通過輸出流向服務(wù)器發(fā)送請求信息
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
//寫入用戶名和密碼
pw.write(name);
//用:來分割用戶名和密碼
pw.write(":");
pw.write(password);
pw.flush();
socket.shutdownOutput();
/*//注意!!!!
//上面為了簡單寫用的是字符串,實際開發(fā)中用的對象較多,也就是如下寫法
ObjectOutputStream oos = new ObjectOutputStream(os);
User user = new User(name,password);
oos.writeObject(user);*/
//設(shè)置超時時間,如果在5s沒沒有收到服務(wù)器的返回,則異常
socket.setSoTimeout(1000*5);
//步驟③通過輸入流獲取服務(wù)器的響應(yīng)信息
InputStream is=socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));
String info=null;
while((info=br.readLine())!=null){
System.out.println(info);
}
//步驟④關(guān)閉相關(guān)資源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
break;
}
}
}
下一篇,基于UDP:http://www.reibang.com/p/20b2d7688cb7