java 网络编程
java 网络编程
1.0 基本通信架构
基本的通信架构有两种,一种是CS架构,即客户端服务端架构,另一种是BS架构,即浏览器服务端架构
1.1 ip地址
java 中使用InetAddress 代表ip地址,常用的方法有:

下面是方法使用
package com.net.test;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IpTest {
public static void main(String[] args) throws Exception {
InetAddress ip=InetAddress.getLocalHost();
System.out.println(ip.getHostAddress());//获得ip地址
System.out.println(ip.getHostName());//获得主机名
InetAddress.getByName("baidu.com");//根据ip地址或者域名获得InetAddress对象
System.out.println();
//判断当前书籍是否可以和当前对象对应的服务器联通,传入的时间为连接尝试时间,单位为毫秒
System.out.println(ip.isReachable(1000));
System.out.println();
}
}
1.2 端口
端口是程序进行网络通信的出入口,被规定为一个16位的二进制,范围是0-65535
分类:
周知端口:0-1023 被预先定义的指明应用占用
注册端口:1024-49151 分配给用户进程或者某些应用程序
动态端口: 49152 到65535 动态进行分配
我们自己的程序一般使用注册端口
一个设备中不能出现两个使用同一端口的程序
1.3 UDP通信
在java中,使用DatagramSocket进行udp通信



下面是UDP通信中创建客户端并发送数据的例子
客户端
package com.net.test;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class Client {
public static void main(String[] args) throws Exception {
DatagramSocket client=new DatagramSocket();
byte [] data= "hello".getBytes();
DatagramPacket packet=new DatagramPacket(data,data.length,
InetAddress.getLocalHost(),6666);
client.send(packet);
System.out.println("发送完毕");
client.close();
}
}
其中DatagramPacket 是我们要存储数据的数据包对象 其中有参构造函数的几个参数在这里进行解释:
第一个参数是一个字节数组,存储着我们要发送的数据
第二个参数是数据发送长度,代表着我们要将第一个字节数组的多少数据发送给对方
第三个参数是一个InetAddress对象,代表我们发送数据的对象,我们可以用InetAddress.getByName方法获得
目标对象
第四个参数是我们发送的端口,有了对象我们要指明发送给对象的哪个端口
定义完成后使用send方法发送数据包
服务端:
package com.net.test;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws Exception {
DatagramSocket server = new DatagramSocket(6666);
byte [] data= new byte[1024*64];//UDP发送数据一次最大64KB
DatagramPacket packet = new DatagramPacket(data,data.length);
server.receive(packet);
String rs=new String(data,0,packet.getLength());
System.out.println(rs);
System.out.println(packet.getAddress());
System.out.println(packet.getPort());
server.close();
}
}
这是一个构建服务端的简单程序,现在进行解释,首先通过DatagramSoket 对象代表服务端,不过这时候要指定
端口了,不能再随机分配,因为之前客户端的端口是指定了发送给目标ip的指定端口了,接着创建字节数组来接受
数据,创建DatagramPacket来接受数据包,字节数组用来接受传输的数据,DatagramPacket不光需要接受传输
的数据,还需要接受发送过来的附加信息,比如发送客户端的ip地址,端口等信息,我们再客户端发送
DatagramPacket也是一样的原因,可以看到我们后续的就用到了附加的信息
当我们启动服务端时,在遇到 server.receive(packet); 时产生阻塞,直到接受到数据后才继续执行
执行的输出结果
hello
/198.18.0.1
51573
需要注意我们缓冲区大小设置的问题,如果我们传入的数据包大于我们接收时设置的缓冲区大小,那么超出的数据
就会丢失
1.4 多发多收
服务端:
package com.net.test;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws Exception {
DatagramSocket server = new DatagramSocket(6666);
while (true) {
byte [] data= new byte[1024*64];//UDP发送数据一次最大64KB
DatagramPacket packet = new DatagramPacket(data,data.length);
server.receive(packet);
String rs=new String(data,0,packet.getLength());
if(rs.equals("quit")){
System.out.println("接收完毕,退出");
break;
}
else {
System.out.println(rs);
}
}
server.close();
}
}
客户端:
package com.net.test;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
DatagramSocket client=new DatagramSocket();
Scanner sc =new Scanner(System.in);
while (true) {
System.out.println("输入:");
String msg=sc.nextLine();
byte [] data= msg.getBytes();
DatagramPacket packet=new DatagramPacket(data,data.length,
InetAddress.getLocalHost(),6666);
client.send(packet);
if(msg.equals("quit")){
System.out.println("所有数据已发送完成");
break;
}
System.out.println("已发送");
}
client.close();
}
}
1.5 TCP通信
在java中,使用Socket类作为客户端进行tcp通信

package com.net.test;
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
Socket client =new Socket("127.0.0.1",6666);
OutputStream ou=client.getOutputStream();
DataOutputStream dos=new DataOutputStream(ou);
dos.writeUTF("你好你好");
dos.close();
client.close();
}
}
使用ServerSocket作为服务端

package com.net.test;
import java.io.DataInputStream;
import java.net.*;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("启动成功");
ServerSocket server=new ServerSocket(8888);
Socket serversocket=server.accept();
DataInputStream din=new DataInputStream(serversocket.getInputStream());
String str=din.readUTF();
System.out.println(str);
din.close();
server.close();
}
}
我们使用高级管道DataInputStream 和DataOutputStream进行消息的接受和发送,在服务端中,我们通过
ServerSocket类来设置端口并接受连接请求,当连接成功后也就是server.accept();顺利执行后会返回一个
Socket对象,作为管道的一端,然后利用Socket对象进行数据传输
1.6 TCP通信实现多发多收
服务端:
package com.net.test;
import java.io.DataInputStream;
import java.net.*;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("启动成功");
ServerSocket server=new ServerSocket(8888);
Socket serversocket=server.accept();
DataInputStream din=new DataInputStream(serversocket.getInputStream());
while (true) {
String str=din.readUTF();
if(str.equals("quit")){
System.out.println("传输结束,关闭管道");
break;
}
System.out.println(str);
}
din.close();
server.close();
}
}
客户端:
package com.net.test;
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
Socket client =new Socket("127.0.0.1",8888);
OutputStream ou=client.getOutputStream();
DataOutputStream dos=new DataOutputStream(ou);
Scanner sc=new Scanner(System.in);
while (true) {
System.out.print("请输入:");
String input=sc.nextLine();
dos.writeUTF(input);
if(input.equals("quit")){
System.out.println("传输结束,关闭管道");
break;
}
}
dos.close();
client.close();
}
}
1.7 与多个客户端同时通信
我们上面写的代码,服务端没办法同时与多个客户端进行通信,接下来学习可以与多个客户端进行通信的方法
使用多线程去解决
package com.net.test;
import java.io.DataInputStream;
import java.net.*;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("启动成功");
ServerSocket server=new ServerSocket(8888);
Socket serversocket= null;
while (true) {
serversocket = server.accept();
Thread t=new ServerReaderThread(serversocket);
t.start();
}
}
}
package com.net.test;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
public class ServerReaderThread extends Thread{
Socket socket;
ServerReaderThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try(DataInputStream din=new DataInputStream(socket.getInputStream())){
while (true) {
String input= din.readUTF();
if(input.equals("quit")){
break;
}
System.out.println(input);
}
socket.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
package com.net.test;
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
Socket client =new Socket("127.0.0.1",8888);
OutputStream ou=client.getOutputStream();
DataOutputStream dos=new DataOutputStream(ou);
Scanner sc=new Scanner(System.in);
while (true) {
System.out.print("请输入:");
String input=sc.nextLine();
dos.writeUTF(input);
if(input.equals("quit")){
System.out.println("传输结束,关闭管道");
break;
}
}
dos.close();
client.close();
}
}
案例 群聊
package com.net.test;
import java.io.DataInputStream;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
public class Server {
public static List<Socket> sockets=new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("启动成功");
ServerSocket server=new ServerSocket(8888);
Socket serversocket= null;
while (true) {
serversocket = server.accept();
sockets.add(serversocket);
Thread t=new ServerReaderThread(serversocket);
t.start();
}
}
}
package com.net.test;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class ServerReaderThread extends Thread{
Socket socket;
private static Map<Socket, DataOutputStream> clientOutputStreams = new HashMap<>();
ServerReaderThread(Socket socket){
this.socket=socket;
try {
// 为每个客户端创建并保存输出流
clientOutputStreams.put(socket, new DataOutputStream(socket.getOutputStream()));
} catch (IOException e) {
System.out.println("创建客户端输出流失败: " + e.getMessage());
}
}
@Override
public void run() {
try(DataInputStream din=new DataInputStream(socket.getInputStream())){
while (true) {
try {
String input= din.readUTF();
// 如果客户端发送退出消息,则断开连接
if ("quit".equals(input)) {
System.out.println(socket.getInetAddress()+"下线");
Server.sockets.remove(socket);
clientOutputStreams.remove(socket);
socket.close();
break;
}
sendMessageToAll(input);
}
catch (Exception e){
System.out.println(socket.getInetAddress()+"下线");
Server.sockets.remove(socket);
clientOutputStreams.remove(socket);
try {
if (!socket.isClosed()) {
socket.close();
}
} catch (IOException ioException) {
// 忽略关闭异常
}
break;
}
}
}
catch (Exception e) {
System.out.println("处理客户端消息时出错: " + e.getMessage());
}
}
private void sendMessageToAll(String input) {
// 创建副本以避免并发修改异常
for(Socket s: Server.sockets.toArray(new Socket[0])){
try {
if (s.isClosed() || !s.isConnected()) {
// 移除已关闭的连接
Server.sockets.remove(s);
clientOutputStreams.remove(s);
continue;
}
DataOutputStream out = clientOutputStreams.get(s);
if (out != null) {
out.writeUTF(input);
out.flush();
}
} catch (IOException e) {
System.out.println("向客户端 " + s.getInetAddress() + " 发送消息失败");
// 移除发送失败的客户端
Server.sockets.remove(s);
clientOutputStreams.remove(s);
try {
if (!s.isClosed()) {
s.close();
}
} catch (IOException ioException) {
// 忽略关闭异常
}
}
}
}
}
package com.net.test;
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
Socket client = null;
DataOutputStream dos = null;
Scanner sc = null;
try {
System.out.println("正在连接服务器...");
client = new Socket("127.0.0.1", 8888);
System.out.println("连接服务器成功");
dos = new DataOutputStream(client.getOutputStream());
// 启动客户端读取线程
Thread t = new ClientThread(client);
t.setDaemon(true);
t.start();
sc = new Scanner(System.in);
System.out.println("输入消息(输入quit退出):");
while (!client.isClosed() && client.isConnected()) {
String input = sc.nextLine();
if (input == null || input.equals("quit")) {
System.out.println("传输结束,关闭连接");
// 发送退出消息给服务器
try {
dos.writeUTF("quit");
dos.flush();
} catch (IOException e) {
// 忽略发送退出消息时的异常
}
break;
}
try {
dos.writeUTF(input);
dos.flush();
// System.out.println("消息已发送"); // 不再打印,因为会收到转发的消息
} catch (IOException e) {
System.err.println("发送消息失败: " + e.getMessage());
break;
}
}
} catch (ConnectException e) {
System.err.println("连接被拒绝:请确保服务器已启动");
} catch (IOException e) {
System.err.println("IO异常: " + e.getMessage());
} finally {
// 关闭资源
if (sc != null) {
sc.close();
}
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
System.err.println("关闭输出流时出错: " + e.getMessage());
}
}
if (client != null && !client.isClosed()) {
try {
client.close();
System.out.println("客户端已关闭");
} catch (IOException e) {
System.err.println("关闭客户端时出错: " + e.getMessage());
}
}
}
}
}
package com.net.test;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
public class ClientThread extends Thread{
Socket socket;
ClientThread(Socket socket){
this.socket=socket;
}
@Override
public void run(){
DataInputStream in = null;
try {
in = new DataInputStream(socket.getInputStream());
while (!socket.isClosed() && socket.isConnected()) {
try {
String output = in.readUTF();
System.out.println("聊天记录:"+output);
} catch (IOException e) {
// 连接断开时退出循环
if (!socket.isClosed()) {
System.out.println("与服务器的连接已断开");
}
break;
}
}
} catch (Exception e) {
if (!socket.isClosed()) {
System.out.println("客户端读取线程异常: " + e.getMessage());
}
} finally {
// 不要在这里关闭socket,让主线程关闭
if (in != null) {
try {
in.close();
} catch (IOException e) {
// 忽略关闭异常
}
}
}
}
}
需要注意的是,我们的数据输入输出流和Socket是绑定的,如果我们将其关闭也会带着关闭Socket 所以我们要对
输入输出流进行复用,而不是用完就关闭,在最后断开连接的时候再进行关闭

浙公网安备 33010602011771号