网络编程
网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据交换的复杂。虽然通过IP地址和端口可以找到网络上运行的一个程序,但是如果需要进行网络编程,则还需要了解网络通讯的过程。
网络通讯方式
在现有的网络中,网络通讯的方式主要有两种:
TCP(传输控制协议)方式
UDP(用户数据报协议)方式
在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。
这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。
由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。
Socket编程
socket可以使一个应用从网络中读取和写入数据,不同计算机上的两个应用可以通过连接发送和接受字节流,当发送消息时,你需要知道对方的ip和端口,在java中,socket指的是java.net.Socket类。
以public Socket(String host, int port)为例,host为远程机器名称或ip地址,port为端口号。若连接本地的Server,其端口号为8080,可以写成如下格式
new Socket(“localhost”, 8080);
一旦成功创建一个Socket类的实例,可以用它来发送和接收字节流,发送时调用getOutputStream方法获取一个java.io.OutputStream对象,接收远程对象发送来的信息可以调用getInputStream方法来返回一个java.io.InputStream对象。
ServerSocket
Socket类代表一个客户端套接字,即任何时候连接到一个远程服务器应用时构建所需的socket。现在,要实现一个服务器应用,需要不同的做法。服务器需随时待命,因为不知道客户端什么时候会发来请求,此时,我们需要使用ServerSocket,对应的是java.net.ServerSocket类。
ServerSocket与Socket不同,ServerSocket是等待客户端的请求,一旦获得一个连接请求,就创建一个Socket示例来与客户端进行通信。
TCP三次握手
TCP 的三次握手除了建立连接外,主要还是为了沟通 TCP 包的序号问题。
刚开始的时候,客户端和服务器都处于 CLOSED 状态,先是服务端主动监听某个端口,处于 LISTEN 状态。然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。服务端接收了发起的连接,返回 SYN,并且 ACK ( 确认 ) 客户端的 SYN,之后处于 SYN-SENT 状态。客户端接收到服务端发送的 SYN 和 ACK 之后,发送 ACK 的 ACK,之后就处于 ESTAVLISHED 状态,因为它一发一收成功了。服务端收到 ACK 的 ACK 之后,也处于 ESTABLISHED 状态,因为它也一发一收了。
TCP四次挥手
断开的时候,当 A 说不玩了,就进入 FIN_WAIT_1 的状态,B 收到 A 不玩了的消息后,进入 CLOSE_WAIT 的状态。
A 收到 B 说知道了,就进入 FIN_WAIT_2 的状态,如果 B 直接跑路,则 A 永远处与这个状态。TCP 协议里面并没有对这个状态的处理,但 Linux 有,可以调整 tcp_fin_timeout 这个参数,设置一个超时时间。
如果 B 没有跑路,A 接收到 B 的不玩了请求之后,从 FIN_WAIT_2 状态结束,按说 A 可以跑路了,但是如果 B 没有接收到 A 跑路的 ACK 呢,就再也接收不到了,所以这时候 A 需要等待一段时间,因为如果 B 没接收到 A 的 ACK 的话会重新发送给 A,所以 A 的等待时间需要足够长。
TCP的Socket编程代码:
客户端 :
public class TestClient {
public static void main(String[] args) throws IOException {
while (true) {
Socket socket = new Socket ("127.0.0.1", 6000);
OutputStream outputStream = socket.getOutputStream ();
DataOutputStream dataOutputStream = new DataOutputStream (outputStream);
Scanner scanner = new Scanner (System.in);
String s = scanner.nextLine ();
dataOutputStream.writeUTF (s);
}
}
}
服务器:
public class TestServer {
public static void main(String[] args) throws IOException {
//开启一个监听端口
ServerSocket socket = new ServerSocket (6000);
while (true) {
//等待客户端连接
Socket accept = socket.accept ();
System.out.println ("有一个人连接了");
//包装成数据流
DataInputStream dataInputStream = new DataInputStream (accept.getInputStream ());
//通过流读取消息
String s = dataInputStream.readUTF ();
System.out.println (s);
dataInputStream.close ();
}
}
}
UDP的Socket编程代码:
people1:
public class People1 {
public static void main(String[] args) throws IOException {
//1.指定端口 DatagramSocket
//2.准备一个容器,封包 DatagramPacket
//3.等待接受包 receive()
//4.验证包
//5.释放资源
DatagramSocket datagramSocket = new DatagramSocket (5000);
byte[] receive = new byte[1024];
DatagramPacket packet = new DatagramPacket (receive, receive.length);
System.out.println ("正在等待.....");
while (true){
datagramSocket.receive (packet);
String s=new String (packet.getData (),0,packet.getLength ());
System.out.println (s);
if (s.equals ("exit")){
break;
}
}
datagramSocket.close ();
}
}
people2:
public class People2 {
public static void main(String[] args) throws IOException {
//1.指定端口
//2.准备一个数据
//3.封装包裹
//4.发送包
//5.释放资源
DatagramSocket socket = new DatagramSocket ();
InetAddress byName = InetAddress.getByName ("127.0.0.1");
int port =5000;
byte[] bytes;
Scanner scanner = new Scanner (System.in);
System.out.println ("你要发什么");
while (true){
String s = scanner.nextLine ();
bytes=s.getBytes ();
DatagramPacket packet = new DatagramPacket (bytes, bytes.length,byName,port);
socket.send (packet);
if (s.equals ("exit")){
break;
}
}
socket.close ();
}
}