网络通信
网络
ip地址
域名与端口号
通信协议
TCP与UDP
InetAddress类
Socket
TCP网络通信编程
注:当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP 来分配的,是不确定且随机的。
编程举例
普通版
客户端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(),9999);//连接本机的9999端口,连接成功返回socket对象
System.out.println("客户端socket返回: " + socket.getClass());
OutputStream outputStream = socket.getOutputStream(); //通过socket.getOutputStream()获得输出流
outputStream.write("hello,server".getBytes()); //写入数据(需要调用getBytes方法)
outputStream.close(); //关闭流和socket
socket.close();
}
}
服务端
class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999); // 本机的9999端口开始监听(要求本机没有其它服务在监听9999)
System.out.println("服务端,9999端口监听,等待连接..."); // 这个ServerSocket可以通过accept()方法 返回多个Socket,允许多个客户端连接服务器的并发
//当没有客户端连接9999端口时,程序会阻塞,等待连接
Socket socket = serverSocket.accept(); //如果有客户端连接,则会返回Socket对象,程序继续
InputStream inputStream = socket.getInputStream(); //连接上后,通过 socket.getInputStream得到输入流对象
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(buf))!=-1){
System.out.println(new String(buf,0,readLen));
} //参见IO流,这里每次打印一个字符串
inputStream.close(); //记得最后关闭流对象和socket,否则会占用资源
socket.close();
}
}
进阶版
这次需要客户端做出回应,那么就需要考虑一个问题:什么时候服务端才算接收完了。就比如说两个聊天,A必须等B说完才能回应。我们需要加上对应的shutdownOutput() / shutdownInput()
总体的思路就是:让客户端先用OutputStream,服务端接收。然后用shutdown断开客户端的输出流。接着服务端用OutputStream,客户端接收。
客户端:
public class Client {
public static void main(String[] args) throws IOException {
//连接本机的9999端口,连接成功返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端socket返回: " + socket.getClass());
//通过socket.getOutputStream()获得输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,server".getBytes());
socket.shutdownOutput(); //停止输出流(告诉服务端我停止输入了)
InputStream inputStream = socket.getInputStream();
int readLen = 0;
byte[] bytes = new byte[1024];
while((readLen = inputStream.read(bytes))!=-1){
System.out.println(new String(bytes,0,readLen));
}
inputStream.close();
outputStream.close(); //关闭流和socket
socket.close();
}
服务端:
class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,9999端口监听,等待连接...");
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream(); //输入流
int readLen = 0;
byte[] bytes = new byte[1024];
while((readLen = inputStream.read(bytes))!=-1){
System.out.println(new String(bytes,0,readLen));
} //读入数据三部曲
OutputStream outputStream = socket.getOutputStream(); //开始反输出
outputStream.write("hello,client".getBytes());
socket.shutdownOutput(); //停止输出流
outputStream.close(); //断开
inputStream.close();
socket.close();
}
}
把字节流改成字符流版本:
修改输出:
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); //转换流
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); //用转换流为中介,把字节流转换成字符流
bufferedWriter.write("hello,server");
bufferedWriter.newLine(); //插入一个换行符,表示写入的内容结束,注意:要求对方使用 readLine方法接收
bufferedWriter.flush(); //刷新
修改输入:
//用转换流作为中介创建 BufferedReader
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine(); //用readLine读入
System.out.println(s);
收发文件案例
大致思路:把磁盘上图片输入到文件字节数组,然后通过Socket发送到服务端的Socket,重新解码为文件数据,然后放到某个目录上。服务端发送收到图片的消息,客户端显示收到的消息。
解释一下StreamUtils:这是一个自己定义的库,里面有各种各样的方法。我们要用到的是下面这几个:
首先是客户端:
1. 连接本机的8888端口。
//连接本机的8888端口,连接成功返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),8888);
2. 把要传输的文件先转化为byte数组,存储数据。
String filePath = "d:\\tupian1.png"; //要传输的文件路径
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
byte[] bytes = StreamUtils.streamToArray(bis); //把文件存到byte数组中
3. 客户端传输数据到服务器。
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); bos.write(bytes); //输出到服务器 socket.shutdownOutput();
//暂停输出,让服务器回应
4. 接收服务器的回应。
InputStream inputStream = socket.getInputStream(); //接收服务器回应
String s = StreamUtils.streamToString(inputStream); //转化成字符串
System.out.println(s);
5. 关闭各种流与socket。
inputStream.close();
bis.close();
bos.close();
socket.close();
然后是服务器:
1. 创建端口等待连接。
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,8888端口监听,等待连接...");
Socket socket = serverSocket.accept(); //返回端口对应的socket,等待连接
2. 接收客户端发来的数据,保存在byte数组里。
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//因为图片是二进制文件,因此用InputStream
byte[] bytes = StreamUtils.streamToByteArray(bis);
//接收客户端数据,bytes数组存储的就是图片的内容
3. 把byte数组里的数据还原到指定文件中。
String desFilePath = "src:\\tupian2.png"; //存放文件的位置
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(desFilePath));
//输出到指定文件中,用输出流
bos.write(bytes);//输出,此时文件已复制完成
4. 告诉客户端自己已经收到数据。
//接下来就是告诉客户端自己收到信息了(用字符流输出汉字信息)
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//用OutputStreamWriter修改为字符流输出
writer.write("收到图片");
writer.flush(); //注意刷新
socket.shutdownOutput(); //暂停输出
5. 关闭各种流与socket。
bos.close();
bis.close();
socket.close();
serverSocket.close();
netstat指令
UDP网络编程
基本介绍
基本流程
应用案例
接收端A:
public class Receiver {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9999); // 1.创建一个 DatagramSocket对象,准备在9999接收数据
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf,buf.length); // 2.构建一个 DatagramPacket 对象,准备存放数据 (一个数据包最大64k)
socket.receive(packet);//3.调用接收方法,将传输的数据填充到 packet中
//4. 可以把packet进行拆包,取出数据并显示。
int length = packet.getLength(); //实际接收到的数据字节长度
byte[] data = packet.getData(); // 接收到的数据(以字节数组形式保存)
String s = new String(data,0,length);
System.out.println(s);
//5.关闭资源
socket.close();
}
}
接收端B:
public class Sender {
public static void main(String[] args) throws IOException {
//1.创建 DatagramSocket对象,准备在9998端口接收数据
DatagramSocket socket = new DatagramSocket(9998);
//注意:在UDP中,即使是发送端也是可以接收数据的。
//2.将需要发送的数据,封装到 DatagramPacket对象
byte[] data = "hello,明天吃火锅".getBytes();
//3. 有内容的DatagramPacket构造器:内容字节数组 数组长度 主机IP 端口
DatagramPacket packet =
new DatagramPacket(data,data.length, InetAddress.getByName("192.168.43.8"),9999);
socket.send(packet);//4.发送内容
socket.close(); //关闭资源
}
}
想要反过来发信息,只需要角色对换重新写一遍send和receive即可。注意 receive和 send方法都是socket的,即使是在9998端口监听的socket,也可以向9999端口发送数据。