知识储备
模型
- 通信模型主要是基于CS结构。通信一方作为服务器(Server)先运行(一般作为守护进程始终运行)、监听端口、并等待客户端提出请求。
- 难点:通信双方建立连接;通信双方进行可靠高效数据传输(TCP/UDP)。在TCP/IP 协议中,IP层负责网络主机定位,TCP提供面向应用的可靠(tcp)的或非可靠(udp)的数据传输机制。
引在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
TCP是Transmission Control Protocol的 简称,是一种面向连接的可靠传输的协议。通过TCP协议传输。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
TCP原理
基本概念
- 位码:TCP标志位。6种:SYN(synchronous建立联机) 、ACK(acknowledgement 确认) 、PSH(push传送) FIN(finish结束)、 RST(reset重置) 、URG(urgent紧急)
- seq:sequence number。数据包本身的序列号
- ack:Acknowledge number。确认序号:期望对方继续发送的那个数据包的序列号
三次握手
- 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
- 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
- 第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
四次挥手
http://blog.chinaunix.net/uid-7411781-id-3812206.html
客户端和服务端总共发送4个包以确认连接的断开。
于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了
但是在这个TCP连接上仍然能够发送数据,直到另一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
四挥过程
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
注意
为什么连接的时候是三次握手,关闭的时候却是四次握手?
A:因为建立连接时:当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但关闭连接时:当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
socket编程
概念
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。
Socket通常用来实现客户方和服务方的连接。
Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
类
涉及2个类,均在java.net包下。
1. Socket:
2. ServerSocket:
交互过程
图解如下:
代码
服务器端程序
Server.java
package xianggen.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
* @author xianggen
*
*/
public class Server {
public static void main(String[] args) {
Server manager = new Server();
manager.doListen();
}
/**
* 服务器监听
* 多线程支持多客户端
*/
public void doListen() {
ServerSocket server;
try {
// 创建一个ServerSocket在端口9991监听客户请求
server = new ServerSocket(9991);
while (true) {
// 使用accept()阻塞等待客户请求,有客户请求到来就产生一个socket对象,并继续执行
Socket client = server.accept();
Thread thread=new Thread(new SSocket(client));
thread.start();
System.out.println("thread name:"+thread.getName());
}
} catch (IOException e) {
e.printStackTrace();
}
}
//服务器进程
class SSocket implements Runnable {
Socket client;
public SSocket(Socket client) {
this.client = client;
// System.out.println("client port:"+client.getPort());
}
public void run() {
DataInputStream input;
DataOutputStream output;
try {
input = new DataInputStream(client.getInputStream());
output = new DataOutputStream(client.getOutputStream());
while(true){
String listMsg = input.readUTF();
System.out.println(listMsg);
output.writeUTF(new DataInputStream(System.in).readLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端程序
Client2.java
/**
* @author xianggen
*
*/
package xianggen.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client2 {
public static void main(String[] args) {
Socket socket = null;
try {
// 向本机(我的本机ip为:10.18.34.133)的9991端口发出客户请求
socket = new Socket("10.18.34.113", 9991);
OutputStream netOut = socket.getOutputStream();
DataOutputStream doc = new DataOutputStream(netOut);
DataInputStream in = new DataInputStream(System.in);
// 循环发送信息
while(true)
doc.writeUTF(in.readLine());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
{
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
}
}