网络编程
看了狂神说Java的网络编程课程后进行总结。
但是关于 网络编程的知识不止这些,各种I/O的深入了解,不同需要的具体实现等等都需要后续的深入学习。
基本概念
- 基于TCP协议实现C/S架构的通信。(打电话)
- 基于UDP协议实现B/S架构的通信。(发短信)
**计算机网络:**把地理位置不同的多台设备,通过通信协议实现资源共享。
目的:数据交换、通信
需要解决的问题:
- 如何定位电脑主机? —IP+port
- 如何传输数据 ? — socket + I/O 流+ 通信协议
下面就基于这两个问题进行学习。
网络通信的要素
通过分析:
- 需要知道通信双方的IP地址以及提供通信的端口。
- 需要明确通信协议 (TCP、UDP)
我们选择的编程语言为Java,需要理解 **Java“万物皆对象”**的特征。
IP
Java 提供了 一个 InetAddress类,表示Internet协议(IP)地址。
从官方文档中,InetAddress类并没有构造器,因此无法使用new 创建对象。(但是又静态方法。)
以下为 构造InetAddress对象以及其常用方法。
1
2
3
4
5
6
7
8
9
10
11
12
|
public class IpTest {
public static void main(String[] args) throws UnknownHostException {
// 使用静态方法 getByName() 构造对象,参数为 域名 或 IP地址
InetAddress inetaddress1 = InetAddress.getByName("www.baidu.com");
System.out.println(inetaddress1);
//打印主机地址
System.out.println(inetaddress1.getHostAddress());
//获取主机名
System.out.println(inetaddress1.getHostName());
}
|
port
端口可以认为是设备与外界通讯交流的出口。也表示进程占用。
大致分类:
- 0-1023 公有端口。 如:
- HTTP 80
- HTTPS 443
- FTP 21
- Telent 23
- SSH 22
- 1024-19151 程序
- 19152-65535 动态私有
查看相关端口的命令:
1
2
3
4
|
//查看端口占用
netsata -ano |find "xxx"
//任务管理器
tasklist
|
通信协议:
为传输层协议,网络编程中使用实时连接TCP和无连接UDP。
详细的就不在此赘述了。
实现TCP实时连接
需要构造客户端、服务端。
涉及到的类:
InetAddress获取IP对象。
socket套接字:双方进行通信的一个抽象端口。
I/O流:用于数据传输。
getBytes() 方法:将字符串转化为Byte序列,供以进行流传输。
客户端
- 明确服务端的IP及端口
- 创建一个套接字地址
- 通过I/O传输数据(byte)
- 关闭资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//客户端
public class tcpclinet {
public static void main(String[] args) throws IOException {
// id
InetAddress inet = InetAddress.getByName("127.0.0.1");
//port
int port = 9999;
//创建一个scoket地址
Socket socket = new Socket(inet, port);
//发送信息,输出流
OutputStream os = socket.getOutputStream();
//这是是写出 btye 数据
os.write("你好".getBytes());
os.close();
}
}
|
服务端
- 创建serverSocket套接字
- 等待客户端连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class Tcpserver {
public static void main(String[] args) throws IOException {
// serversocket
ServerSocket serverSocket = new ServerSocket(9999);
// 等待连接
Socket accept = serverSocket.accept();
System.out.println("连接成功");
// 读取客户端信息
InputStream is = accept.getInputStream();
// 管道流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len ;
while((len= is.read(buffer))!= -1 )
{
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
}
}
|
实现TCP 发送文件
客户端:
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
|
public class ClientTest {
public static void main(String[] args) throws IOException {
Socket s = new Socket("127.0.0.1",8888);
System.out.println("客户端启动!准备接受文件!");
//数据输入流
DataInputStream dis = new DataInputStream(s.getInputStream());
//接收文件数量
int fileCount = dis.readInt();
System.out.println("需要接收的文件个数:"+fileCount);
//下载多少个文件
for (int i = 0; i < fileCount; i++) {
//名字和长度
String fileName = dis.readUTF();
long fileSize = dis.readLong();
//利用文件输出流,创建文件
FileOutputStream fos = new FileOutputStream("received_" + fileName);
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesRead = 0;
while ((bytesRead = dis.read(buffer)) != -1)
{
fos.write(buffer,0,bytesRead);
totalBytesRead +=bytesRead;
if(totalBytesRead == fileSize)
{
break;
}
}
System.out.println("已接收文件:"+fileName);
fos.close();
}
dis.close();
s.close();
}
}
|
服务端:
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public class ServerTest {
public static void main(String[] args) throws IOException {
//开放8888端口
ServerSocket ss = new ServerSocket(8888);
System.out.println("服务端等待连接。。。。");
//客服端连接
Socket s = ss.accept();
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
System.out.println("连接成功!传输文件");
//读取本地的多个文件 ,存放在一个数组里。
File File = new File("./files");
File[] files = File.listFiles();
assert files != null;
int len = files.length;
//需要先发送文件的信息,文件数量
dos.writeInt(len);
dos.flush();
//发送每个文件
for (java.io.File file : files) {
//文件输入流。
FileInputStream fis = new FileInputStream(file);
//获取文件的名字和长度
String fileName = file.getName();
long fileSize = file.length();
//把名字和长度传过去
dos.writeUTF(fileName);
dos.writeLong(fileSize);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
dos.write(buffer, 0, bytesRead);
}
fis.close();
System.out.println("已发送文件:"+fileName);
}
//现在有思路是服务端就这样写,然后客户端创建对应的名字进行存储,应该考虑大小问题。
dos.close();
//多个文件传输
//FileInputStream file = new FileInputStream(files[0]);
}
}
|
实现UDP发送信息
UDP 协议则需要使用到 DatagramSocket 套接字 和 DatagramPacket 类。
基本框架是:
- 创建DatagramSocket套接字
- 创建packet
- 发送packet
发送端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Udpclient {
public static void main(String[] args) throws IOException {
// socket
DatagramSocket socket = new DatagramSocket();
// creat packet
String msg = "你好啊,服务器!";
InetAddress serverip = InetAddress.getByName("127.0.0.1");
int port = 9999;
//数据 ,发送给谁 5个参数
//将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, serverip, port);
// 发送包
socket.send(packet);
socket.close();
}
}
|
接受端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class udpserver {
public static void main(String[] args) throws IOException {
// 开放端口
DatagramSocket socket = new DatagramSocket(9999);
// 接受数据
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(new String(packet.getData(), 0, packet.getLength()));
}
}
|
至此实现了基础的发送信息。也可以优化成连续不断发送信息直到 ,发送方停止。
只需要把对应的代码放入 while循环中,再通过添加终止条件,达到目的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
ublic class UdpSender {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(6666);
BufferedReader render = new BufferedReader(new InputStreamReader(System.in));
while(true)
{
String data = render.readLine();
//处理字符串封装成包
byte[] datas = data.getBytes();
DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress("127.0.0.1",8888));
socket.send(packet);
if(data.equals("bye"))
{
break;
}
}
//发送的数据,从键盘获取
socket.close();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class UdpReceive {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8888);
while(true)
{
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
byte[] data = packet.getData();
String recevedata = new String(data,0, packet.getLength());
System.out.println(recevedata);
if (recevedata.equals("bye"))
{
break;
}
}
}
}
|
在实现该功能时,遇到了一些问题:
Q:在接受端,接受的数据长度很长。
1
2
3
4
5
6
7
|
//错误代码
byte[] data = packet.getData();
String recevedata = new String(data,0, data.Lenth);
//但是 data.Lenth的值为 1024 (发送端的数据长度),并不是发送过来的实际长度。
//实际长度应为 packet.getLenth()
String recevedata = new String(data,0, packet.getLength());
|
实现UDP聊天实现
要实现聊天的实现,就得学习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
42
|
public class TalkSent implements Runnable{
// 成员
DatagramSocket socket =null;
BufferedReader render = null;
private int fromprot;
private String toip;
private int toport;
// 初始化进程
public TalkSent(int fromprot, String toip, int toport) {
this.fromprot = fromprot;
this.toip = toip;
this.toport = toport;
}
// 子线程运行的内容
@Override
public void run() {
try {
socket = new DatagramSocket(fromprot);
render = new BufferedReader(new InputStreamReader(System.in));
while(true)
{
String data = render.readLine();
//处理字符串封装成包
byte[] datas = data.getBytes();
DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress(this.toip,this.toport));
socket.send(packet);
if(data.equals("bye"))
{
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
socket.close();
}
}
|
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
|
public class TalkReceive implements Runnable{
DatagramSocket socket=null;
private int fromPort;
private String msgFrom;
public TalkReceive(int fromPort,String msgFrom) throws SocketException {
this.fromPort = fromPort;
this.msgFrom = msgFrom;
socket = new DatagramSocket(this.fromPort);
}
@Override
public void run() {
try {
while(true)
{
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
byte[] data = packet.getData();
// 为什么这里出错了,长度为为 1024 .
String recevedata = new String(data,0, packet.getLength());
System.out.println(msgFrom+":"+recevedata);
if (recevedata.equals("bye"))
{
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
|
实现了这个对于 通信双方就容易了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class TalkStudent {
public static void main(String[] args) throws SocketException {
//开启两个线程
//发送到目标主机的9999端口
new Thread(new TalkSent(7777,"localhost",9999)).start();
//接受来自 8888 端口的信息 。
new Thread(new TalkReceive(8888,"老师")).start();
}
}
public class TalkTecher {
public static void main(String[] args) throws SocketException {
//发送到目标主机的8888端口
new Thread(new TalkSent(5555,"127.0.0.1",8888)).start();
//接收9999端口的信息
new Thread(new TalkReceive(9999,"学生")).start();
}
}
|
URL资源下载
需要用到 URL 类 以及 进行HttpURLconnection 网络连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class urlTest {
public static void main(String[] args) throws IOException {
URL url = new URL("https://img3.doubanio.com/dae/accounts/resources/ded47ae/movie/assets/annual_2023.png");
// 进行 http连接
URLConnection connection = url.openConnection();
//HttpURLconnection urlconnection =(HttpURLconnection)url.openconnection();
// 输入流
InputStream is = connection.getInputStream();
System.out.println(connection);
// 文件输出流,把文件保留在本地
FileOutputStream fos = new FileOutputStream("a.png");
byte[] buffer = new byte[1024];
int len ;
while((len=is.read(buffer))!= -1)
{
fos.write(buffer,0,len);
}
fos.close();
is.close();
}
}
|