本篇博客主要介绍套接字网络编程的相关知识。
UDP网络程序
DatagramSocket类:
方法 | 说明 |
DatagramSocket(int port, InetAddress laddr) | 创建一个数据报套接字,绑定到指定的本地地址 |
DatagramSocket(SocketAddress bindaddr) | 创建一个数据报套接字,绑定到指定的本地套接字地址 |
void bind(SocketAddress addr) | 将此DatagramSocket绑定到特定的地址和端口 |
void connect(InetAddress addres, int port) | 将套接字连接到此套接字的远程地址 |
void receive(DatagramPacket p) | 从此套接字接收数据包 |
void close() | 关闭此数据包套接字 |
void send(DatagramPacket p) | 从此发送数据报包 |
简单的UDP网络程序:
- 实现一个简单的服务端接收客户端输入的信息然后在服务端显示。
服务端代码:
package udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
public class UdpServer {
public static void main(String[] args) {
new UdpServer().start();
}
// 绑定端口号
private static final int PORT = 9999;
// 设置数据报大小
private static final int DATA_LEN = 4096;
private byte[] recvBuffer = new byte[DATA_LEN];
// 用于接收数据
private DatagramPacket in = new DatagramPacket(recvBuffer, recvBuffer.length);
// 用于发送数据
private DatagramPacket out = null;
public void start() {
try {
DatagramSocket socket = new DatagramSocket(PORT);
SocketAddress address = null;
String word = null;
byte[] sendBuffer = null;
System.out.println("服务端启动了...");
while (true) {
socket.receive(in);
address = in.getSocketAddress();
recvBuffer = in.getData();
word = new String(recvBuffer).trim();
System.out.println("客户端:" + word);
if ("down".equals(word)) {
sendBuffer = "服务端已关闭,请重试!".getBytes();
out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
socket.send(out);
break;
}
sendBuffer = ("您输入的内容是:" + word).getBytes();
out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
socket.send(out);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码:
package udp;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpClient {
public static void main(String[] args) {
new UdpClient().start();
}
// 目的端口号
private static final int DEST_PORT = 9999;
// 目的IP
private static final String DEST_IP = "127.0.0.1";
// 数据报长度
private static final int DATA_LEN = 4096;
private byte[] recvBuffer = new byte[DATA_LEN];
// 用于接收数据
private DatagramPacket in = new DatagramPacket(recvBuffer, recvBuffer.length);
// 用于发送数据
private DatagramPacket out = null;
public void start() {
try {
DatagramSocket socket = new DatagramSocket();
InetAddress ip = InetAddress.getByName(DEST_IP);
out = new DatagramPacket(new byte[0], 0, ip, DEST_PORT);
Scanner sc = new Scanner(System.in);
System.out.println("请输入数据:");
String word = null;
byte[] sendBuffer = null;
while (sc.hasNextLine()) {
word = sc.nextLine();
if ("exit".equals(word)) {
break;
}
sendBuffer = word.getBytes();
out.setData(sendBuffer);
socket.send(out);
socket.receive(in);
System.out.println(new String(recvBuffer, 0, in.getLength()));
System.out.println("请输入数据:");
}
System.out.println("客户端退出...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
实现简单的字典服务器
代码如下:
package udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
public class UdpDictServer {
public static void main(String[] args) {
new UdpDictServer().start();
}
private static final int PORT = 9999;
private static final int DATA_LEN = 4096;
private byte[] recvBuffer = new byte[DATA_LEN];
private DatagramPacket in = new DatagramPacket(recvBuffer, recvBuffer.length);
private DatagramPacket out = null;
private static Map<String, String> dict = new HashMap<>();
static {
dict.put("苹果", "apple");
dict.put("香蕉", "banana");
dict.put("草莓", "strawberry");
dict.put("西瓜", "watermelon");
}
public void start() {
try {
DatagramSocket socket = new DatagramSocket(PORT);
SocketAddress address = null;
byte[] sendBuffer = null;
String word = null;
String result = null;
System.out.println("字典服务器启动...");
while (true) {
socket.receive(in);
recvBuffer = in.getData();
word = new String(recvBuffer).trim();
result = dict.get(word);
if (result == null) {
result = "查询失败!";
}
address = in.getSocketAddress();
if ("down".equals(word)) {
sendBuffer = "服务端已关闭!".getBytes();
out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
socket.send(out);
break;
}
sendBuffer = result.getBytes();
out = new DatagramPacket(sendBuffer, sendBuffer.length, address);
socket.send(out);
}
System.out.println("服务端关闭...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果如下:
TCP网络程序
TCP socket API
ServerSocket类:
方法 | 说明 |
ServerSocket(int port) | 创建绑定到指定端口的服务器套接字 |
ServerSocket(int port, int backlog) | 创建服务器套接字并绑定到指定的端口号,并指定了积压 |
Socket accept() | 侦听要连接到此套接字并接受它 |
bind(SocketAddress endpoint) | 将ServerSocket绑定到特定地址(IP和端口号) |
InetAddress getInetAddress() | 返回此服务器套接字的本地地址 |
void close() | 关闭此套接字 |
int getLocalPort() | 返回此套接字正在侦听的端口号 |
Socket类:
方法 | 说明 |
Socket(InetAddress address, int port) | 创建流套接字并将其连接到指定IP地址的指定端口号 |
Socket(String host, int port) | 创建流套接字并将其连接到指定主机的指定端口号 |
void bind(SocketAddress bindpoint) | 将套接字绑定到本地地址 |
void connect(SocketAddress endpoint) | 将次套接字连接到服务器 |
InetAddress getInetAddress() | 返回套接字锁连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
简单的TCP服务端和客户端程序
TCP服务端代码如下:
package tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");
Socket client = server.accept();
BufferedReader br = new BufferedReader(
new InputStreamReader(client.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())
);
String word = null;
while ((word = br.readLine()) != null) {
if (word != null) {
System.out.println("客户端:" + word);
bw.write("您输入的是\"" + word + "\"\n");
bw.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP客户端代码如下:
package tcp;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 9999;
public static void main(String[] args) {
try {
Socket socket = new Socket(HOST, PORT);
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()) {
bw.write(sc.nextLine() + "\n");
bw.flush();
System.out.println("服务端:" + br.readLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果如下:
我们可以看到服务端和客户端能够正常通信,但是这个代码还有点问题。
测试多客户端连接的情况
我们启动一个服务器,然后启动多个客户端去连接这个服务器。
如何启动多个客户端程序呢,步骤如下:
- 点击Run
- 选中其中的Edit Configurations…
- 左边选中我们要重复启动的类
- 勾选右上角的Allow parallel run,然后点击Apply
配置完成,我们来测试一下多客户端连接的情况:
- 首先,我们启动服务端程序
- 然后,我们启动一个客户端,测试一下单个客户端的情况
- 前面可以看到,启动一个客户端一切正常,下面我们再启动一个客户端
从上述运行结果,我们可以看到第二个客户端没反应,这是为什么呢?明明一个客户端的时候能够正常。
我们来仔细读一下服务端的代码:
ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");
Socket client = server.accept();
BufferedReader br = new BufferedReader(
new InputStreamReader(client.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())
);
String word = null;
while ((word = br.readLine()) != null) {
if (word != null) {
System.out.println("客户端:" + word);
bw.write("您输入的是\"" + word + "\"\n");
bw.flush();
}
}
其实原因也很容易理解,因为我们阻塞等待客户端连接的代码只会执行一次,也就是说,这个服务器只能接受跟一个客户端建立连接,因为server.accept()
这个代码只会执行一次,所以就出现了上面的现象。
那是不是说我们把这些代码全部放到while循环中就可以了呢。
我们来试一下:
ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");
while (true) {
Socket client = server.accept();
BufferedReader br = new BufferedReader(
new InputStreamReader(client.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())
);
String word = null;
while ((word = br.readLine()) != null) {
System.out.println("客户端:" + word);
bw.write("您输入的是\"" + word + "\"\n");
bw.flush();
}
}
这个代码也是不行的,因为代码会一直在下面这个while循环中不停的执行,还是没办法去执行上面的server.accept()
这行代码。只有当下面while循环结束的时候才会去执行上面的server.accept()
这行代码,也就是当前客户端断开连接后,才回去建立新的连接,这显然是不行的。
下面我们来看一下改进的方法。
多线程版本的TCP程序
TCP服务端代码:
package tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");
int index = 1;
while (true) {
Socket client = server.accept();
System.out.println("客户端" + index + "已连接...");
new Thread(new ServerThread(client, index)).start();
++index;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package tcp;
import java.io.*;
import java.net.Socket;
public class ServerThread implements Runnable {
private Socket socket = null;
private int index = 0;
public ServerThread(Socket socket, int index) {
this.socket = socket;
this.index = index;
}
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
String word = null;
while ((word = br.readLine()) != null) {
System.out.println("客户端" + index + ":" + word);
bw.write("您输入的是\"" + word + "\"\n");
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们来运行验证一下:
- 首先,我们启动服务端,然后启动三个客户端
- 我们来验证一下三个客户端
线程池版本的TCP程序
服务端代码:
package tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpServer {
private static final ExecutorService EXE = Executors.newCachedThreadPool();
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(9999);
System.out.println("服务器启动...");
int index = 1;
while (true) {
Socket client = server.accept();
System.out.println("客户端" + index + "已连接...");
EXE.submit(new ServerThread(client, index));
++index;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package tcp;
import java.io.*;
import java.net.Socket;
public class ServerThread implements Runnable {
private Socket socket = null;
private int index = 0;
public ServerThread(Socket socket, int index) {
this.socket = socket;
this.index = index;
}
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
String word = null;
while ((word = br.readLine()) != null) {
System.out.println("客户端" + index + ":" + word);
bw.write("您输入的是\"" + word + "\"\n");
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}