目录
一、TCP编程:(可靠)
1、Socket:
在开发网络应用程序的时候,会遇到Socket这个概念。Socket是一个抽象概念,一个应用程序通过一个Socket来建立一个远程连接,而Socket内部通过TCP/IP协议把数据传输到网络。Socket、TCP和部分IP的功能都是由操作系统提供的,不同的编程语言只是提供了对操作系统调用的简单的封装。例如:Java提供的几个Socket相关的类就封装了操作系统提供的接口:ServerSocket类、Socket类。
使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。因此,当Socket连接成功地在服务器端和客户端之间建立后:
- 对服务器端来说:它的
Socket是指定的IP地址和指定的端口号; - 对客户端来说:它的
Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。
2、服务器端:
要使用Socket编程,我们首先要编写服务器端程序。Java标准库提供了ServerSocket来实现对指定IP和指定端口的监听。ServerSocket的典型实现代码如下
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666); // 监听指定端口
System.out.println("server is running...");
while (true) {
Socket sock = ss.accept();
// 使用Socket流进行网络通信
// ...
System.out.println("connected from " + sock.getRemoteSocketAddress());
}
}
}
服务器端通过下述代码,在指定端口6666监听。这里我们没有指定IP地址,表示在计算机的所有网络接口上进行监听。
ServerSocket ss = new ServerSocket(6666);
如果ServerSocket监听成功,我们就使用一个无限循环来处理客户端的连接,注意到代码ss.accept()表示每当有新的客户端连接进来后,就返回一个Socket实例,这个Socket实例就是用来和刚连接的客户端进行通信的。
while (true) {
Socket sock = ss.accept();
System.out.println("connected from " + sock.getRemoteSocketAddress());
}
如果没有客户端连接进来,accept()方法会阻塞并一直等待。如果有多个客户端同时连接进来,ServerSocket会把连接扔到队列里,然后一个一个处理。对于Java程序而言,只需要通过循环不断调用accept()就可以获取新的连接。
3、客户端:
相比服务器端,客户端程序就要简单很多。一个典型的客户端程序如下:
public class Client {
public static void main(String[] args) throws IOException {
// 连接指定服务器和端口
Socket sock = new Socket("localhost", 6666);
// 使用Socket流进行网络通信
// ...
// 关闭
sock.close();
System.out.println("disconnected.");
}
}
客户端程序通过下述代码,连接到服务器端,注意上述代码的服务器地址是"localhost",表示本机地址,端口号是6666。如果连接成功,将返回一个Socket实例,用于后续通信。
Socket sock = new Socket("localhost", 6666);
4、Scoket流:
当Socket连接创建成功后,无论是服务器端,还是客户端,我们都使用Socket实例进行网络通信。因为TCP是一种基于流的协议,因此,Java标准库使用InputStream和OutputStream来封装Socket的数据流,这样我们使用Socket的流,和普通IO流类似:
// 用于读取网络数据:
InputStream in = sock.getInputStream();
// 用于写入网络数据:
OutputStream out = sock.getOutputStream();
写入网络数据时,必须要调用flush()方法。如果不调用flush(),我们很可能会发现,客户端和服务器都收不到数据,这并不是Java标准库的设计问题,而是我们以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()强制把缓冲区数据发送出去。
二、UDP编程:(不可靠)
和TCP编程相比,UDP编程就简单得多,因为UDP没有创建连接,数据包也是一次收发一个,所以没有流的概念。在Java中使用UDP编程,仍然需要使用Socket,因为应用程序在使用UDP时必须指定网络接口(IP地址)和端口号。注意:UDP端口和TCP端口虽然都使用0~65535,但他们是两套独立的端口,即一个应用程序用TCP协议占用了端口1234,不影响另一个应用程序用UDP协议占用端口1234。
1、服务器端:
DatagramSocket ds = new DatagramSocket(6666); // 监听指定端口
while (true) { // 无限循环
// 数据缓冲区:
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet); // 收取一个UDP数据包
// 收取到的数据存储在buffer中,由packet.getOffset(), packet.getLength()指定起始位置和长度
// 将其按UTF-8编码转换为String:
String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
// 发送数据:
byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
packet.setData(data);
ds.send(packet);
}
服务器端首先使用如下语句在指定的端口监听UDP数据包:
DatagramSocket ds = new DatagramSocket(6666);
如果没有其他应用程序占据这个端口,那么代表监听成功。为了能够反复处理数据,我们使用一个死循环来处理收到的UDP数据包:
while (true) { // 死循环
}
要接收一个UDP数据包,需要准备一个byte[]缓冲区,并通过DatagramPacket实现接收:
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
假设我们收取到的是一个String,那么,通过DatagramPacket返回的packet.getOffset()和packet.getLength()确定数据在缓冲区的起止位置:
String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
当服务器收到一个DatagramPacket后,通常必须立刻回复一个或多个UDP包,因为客户端地址在DatagramPacket中,每次收到的DatagramPacket可能是不同的客户端,如果不回复,客户端就收不到任何UDP包。
发送UDP包也是通过DatagramPacket实现的:
byte[] data = ...
packet.setData(data);
ds.send(packet);
2、客户端:
和服务器端相比,客户端使用UDP时,只需要直接向服务器端发送UDP包,然后接收返回的UDP包:
DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666); // 连接指定服务器和端口
// 发送:
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
ds.send(packet);
// 接收:
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
ds.disconnect();
客户端打开一个DatagramSocket使用以下代码:
DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666);
162

被折叠的 条评论
为什么被折叠?



