Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络传输控制层中的TCP和UDP协议。
TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性。
而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。
在性能上,UDP具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。
TCP协议中的三次握手和四次挥手:
建立TCP需要三次握手才能完成连接,而断开连接需要四次挥手。
连接过程:
1.客户端发送连接请求报文
2.服务端接收到连接请求后回复ACK报文,并为这次连接分配资源
3.客户端接收到ACK报文后也向服务端发送ACK报文,并分配资源
经过上面三次握手,TCP连接就建立成功了。
断开过程:断开连接请求可以是客户端发起,也可以是服务端发起,这里以客户端发起为例
1.客户方发送FIN报文
2.服务端接收到FIN报文后,发送ACK给客户端,客户端接收到消息后进入FIN_WAIT状态等待服务端的FIN报文
3.服务端确定数据已经发送完成则向客户端发送FIN报文
4.客户端收到FIN报文后发送ACK报文给服务端,并进入TIME_WAIT状态,如果服务端没有收到ACK报文则可以重传。服务端收到ACK后就知道可以断开连接了。客户端等待2MSL(最大报文段生存时间)后依然没有收到回复,则证明服务端已正常关闭,客户端也可以关闭连接了。
经过上面四次挥手,连接就断开了。
为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭Socket,所以只能先回复一个ACK报文,告诉客户端收到了客户端发送的FIN报文。只有等服务端所有的报文都发送完了,服务端才能发送FIN报文,因此不能一起发送。故需要四次握手。
为什么TIME_WAIT状态需要经过2MSL才能返回CLOSE状态?
答:网络是不可靠的,有可能造成最后一个ACK丢失,所有TIME_WAIT状态就是用来重发可能丢失的ACK报文的。
例子:聊天工具,服务端每收到一个连接请求后就创建一个Socket连接,这样服务端就可以和不同的客户端通信了。服务端每收到一条消息就自动回复一条消息。
服务端代码:
public class SocketTcpService extends Service {
private boolean mIsServerRunning;
private String[] mDefaultMsgs = new String[] { "hehe", "haha", "heihei" };
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mIsServerRunning = true;
new Thread(){
public void run() {
ServerSocket serverSocket = null;
try {
// 监听本地8688接口
serverSocket = new ServerSocket(8688);
} catch (Exception e) {
e.printStackTrace();
}
while(mIsServerRunning) {
try {
// 接收客户端请求
final Socket socket = serverSocket.accept();
System.out.println("accept");
new Thread() {
public void run() {
try {
responseClient(socket);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
};
}.start();
}
private void responseClient(Socket socket) throws IOException {
// 接收客户端消息
BufferedReader in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// 向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
System.out.println("欢迎来到聊天室");
while (mIsServerRunning) {
// 读取客户端发来的消息
String str = in.readLine();
// 判断客户端断开连接的方法很多,这里用输入流是否为null判断
if (str == null) {
break;
}
int i = new Random().nextInt(mDefaultMsgs.length);
String msg = mDefaultMsgs[i];
// 服务端发送消息
out.println("服务端:" + msg);
}
System.out.println("客户端退出");
out.close();
in.close();
socket.close();
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServerRunning = false;
}
}
客户端代码:客户端和服务端是在同一个应用中的不同进程
public class MainActivity extends Activity {
private Socket mSocket;
private Button mButton;
private TextView mText;
private PrintWriter mWrite;
private String[] mDefaultMsgs = new String[] { "xixi", "houhou", "aa" };
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
mButton.setEnabled(true);
break;
case 1:
mText.setText((String)msg.obj);
break;
}
super.handleMessage(msg);
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.btn);
mText = (TextView) findViewById(R.id.text);
mButton.setOnClickListener(mClickListener);
Intent service = new Intent(this, SocketTcpService.class);
startService(service);
startConnect();
}
private OnClickListener mClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
int i = new Random().nextInt(mDefaultMsgs.length);
String msg = "客户端:" + mDefaultMsgs[i];
if (!TextUtils.isEmpty(msg) && mWrite != null) {
// 客户端发消息
mWrite.println(msg);
mText.setText(mText.getText() + msg + "\n");
}
}
};
private void startConnect() {
new Thread() {
public void run() {
while (mSocket == null) {
try {
mSocket = new Socket("localhost", 8688);
mWrite = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(
mSocket.getOutputStream())), true);
mHandler.sendEmptyMessage(0);
System.out.println("连接成功");
} catch (Exception e) {
SystemClock.sleep(1000);
System.out.println("连接失败,重新连接");
}
}
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(mSocket.getInputStream()));
while (!MainActivity.this.isFinishing()) {
// 读取服务端发送来的消息
String msg = reader.readLine();
msg = mText.getText() + msg + "\n";
if (msg != null) {
mHandler.obtainMessage(1,msg).sendToTarget();
}
}
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mSocket != null) {
try {
mSocket.shutdownInput();
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}