前言
网络编程,顾名思义就是编写通过网络通信的计算机程序。提到网络编程,一般指 socket 编程,之前我写过两篇相关的文章,分别是:浅谈 socket 编程 和 Socket编程实践(Java & Python实现),主要侧重于 socket 编程的理解,而这一篇侧重于使用 Java 进行 socket 编程的要点,作为简明笔记,以备后续用到时方便查阅。
使用Java进行 TCP socket 编程
服务器
socket编程涉及到多台计算机的连接,一般我们习惯分为服务器和客户端以区分服务提供者和使用者。服务器端首先要建立起一个socket连接服务,之后等待客户端来连接,当连接建立后,两方都用输入输出流来发送和接收数据,就像双向流水管道一样自然。
如何建立连接
对于服务器来说,要创建一个服务socket非常简单:
1 2
| int SERVER_PORT = 7706; ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
|
这样就创建了一个服务socket,之后用accept方法开始等待客户端来连接:
1
| Socket connectSocket = serverSocket.accept();
|
在没有客户端连接时,accept方法是一直阻塞的。直到有一个客户端进行连接,connectSocket对象才生成。注意,这里的connectSocket跟刚刚的serverSocket是两个概念,serverSocket用来欢迎并等待客户端的连接,connectSocket则是专门服务于某个连接的客户端的。
连接建立后,我们得到了一个Socket对象(不是ServerSocket对象)。之后我们开一个线程,专门处理与这个客户端的数据传输。
1 2 3 4
| while (true){ Socket connectSocket = serverSocket.accept(); new sender(connectSocket).start(); }
|
而主线程的ServerSocket对象则在 while 循环内继续等待欢迎其他客户端来连接。
server.java 完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class server {
private static final int SERVER_PORT = 7767;
public static void main(String[] args) throws IOException {
boolean STOP = false;
ServerSocket serverSocket = new ServerSocket(SERVER_PORT); System.out.println("服务已启动,监听端口:" + SERVER_PORT);
while (!STOP){ Socket connectSocket = serverSocket.accept();
new sender(connectSocket).start(); } } }
|
如何发送和接收数据
接下来关心一下,sender类是如何处理数据的。首先,sender构造时传进了一个 connectSocket 对象,然后在线程的 run 方法里面编写如何跟客户端交互的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class sender extends Thread {
private Socket connectSocket;
public sender(Socket connectSocket) { this.connectSocket = connectSocket; }
@Override public void run(){ } }
|
首先,用connectSocket.getInputStream()
获取来自客户端的输入流,然后封装到 DataInputStream 对象中,最后用readUTF()
方法来读取。输出流也是同理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Override public void run(){
System.out.println("远程计算机 " + connectSocket.getRemoteSocketAddress() + "已连接");
try { InputStream in = connectSocket.getInputStream(); DataInputStream dataIn = new DataInputStream(in);
OutputStream out = connectSocket.getOutputStream() DataOutputStream dataOut = new DataOutputStream(out);
while (true){
String recv = dataIn.readUTF(); System.out.println("成功接收来自 " + connectSocket.getRemoteSocketAddress() + "的数据:" + recv);
String send = recv.toUpperCase();
dataOut.writeUTF(send); System.out.println("向" + connectSocket.getRemoteSocketAddress() + "发送转换后的数据:" + send);
} } catch (IOException e) { System.out.println("远程计算机 " + connectSocket.getRemoteSocketAddress() + "已断开"); }
}
|
客户端
客户端相对来说就简单一些。先创建一个 Socket 对象,然后用同样的方法封装到输入流和输出流中进行处理即可。
创建socket对象
1 2 3
| String serverNmae = "192.168.1.187"; Socket client = new Socket(serverName, 7767);
|
封装输入输出流
1 2 3 4
| DataOutputStream dataOut = new DataOutputStream(client.getOutputStream());
DataInputStream dataIn = new DataInputStream(client.getInputStream());
|
数据发送和接收
1 2 3 4 5 6
| dataOut.writeUTF(send); System.out.println("向服务器 " + client.getRemoteSocketAddress() + "发送了:" + send + "\n");
String recv = dataIn.readUTF(); System.out.println("接收来自服务器的数据:" + recv + '\n');
|
可以使用 Scanner 从键盘多次获取输入。
client.java 完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class client {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); System.out.println("请输入服务器地址,如 192.168.1.1"); String serverName = scanner.nextLine();
try { Socket client = new Socket(serverName, 7767);
DataOutputStream dataOut = new DataOutputStream(client.getOutputStream()); DataInputStream dataIn = new DataInputStream(client.getInputStream());
while (true){ System.out.println("\n请输入要发送的数据:(输入exit退出)"); String send = scanner.next();
if ("exit".equals(send)) break;
dataOut.writeUTF(send); System.out.println("向服务器 " + client.getRemoteSocketAddress() + "发送了:" + send);
String recv = dataIn.readUTF(); System.out.println("接收来自服务器的数据:" + recv + '\n'); }
} catch (IOException e){ System.out.println("服务器连接中断"); }
} }
|
使用Java进行 UDP socket 编程
发送端
UDP socket 无需建立连接,但是在发送数据前需要先准备数据报包(packet)。类似于TCP里面我们把输入流封装到 DataInputStream 里,在 UDP 中我们把数据放在数据报包 DatagramPacket 当中,数据报包是载体。65508是每个数据报包可以容纳的最大数据量。
1 2 3 4 5 6 7 8 9
| byte[] buffer = new byte[65508];
InetAddress address = InetAddress.getByName("192.168.1.187");
DatagramPacket packet = new DatagramPacket( buffer, buffer.length, address, 9797);
|
有了数据报包之后,我们用 datagramSocket 对象来发送数据。可以这样理解,DatagramPacket 是包裹,用来装数据,而 datagramSocket 是车,用来把包裹运送出去。
1 2 3 4 5
| DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.send(packet);
|
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public class client {
public static void main(String[] args) throws UnknownHostException, SocketException {
byte[] buffer = "0123456789".getBytes();
InetAddress address = InetAddress.getByName("192.168.1.187");
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 9797);
DatagramSocket datagramSocket = new DatagramSocket();
try { datagramSocket.send(packet); } catch (IOException e) { e.printStackTrace(); } }
}
|
接收端
接收端一开始需要先创建一个 datagramSocket 对象,用来准备接收数据:
1 2
| DatagramSocket datagramSocket = new DatagramSocket(9797);
|
然后声明即将接收的数据报包格式:
1 2 3
| byte[] buffer = new byte[10]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
接收:
1 2 3 4 5 6 7
| datagramSocket.receive(packet);
byte[] recv = packet.getData(); String s = new String(recv); System.out.println("接收到:" + s);
|
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public class server {
public static void main(String[] args) throws SocketException {
DatagramSocket datagramSocket = new DatagramSocket(9797);
byte[] buffer = new byte[10]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try { datagramSocket.receive(packet); } catch (IOException e) { e.printStackTrace(); }
byte[] recv = packet.getData(); String s = new String(recv); System.out.println("接收到:" + s); }
}
|