网络编程基础
网络
将不同区域的电脑连接到一起,组成区域网(LAN),城域网(MAN)或广域网(WAN)。把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大,功能强的网络系统,从而使众多的计算机可以互相传递信息
(1)网络特性
1.资源共享
2.快速传输信息
3.提高系统可靠性
4.易于进行分布式处理:在网络中,还可以将一个比较大的问题或任务分解为若干子问题或任务,分散到网络中不同的计算机上进行处理计算
5.综合信息服务:当今是信息化时代,每时每刻都在产生并处理大量的信息。通过网络就能收集,处理这些信息,并进行信息的传送。
(2)通讯协议
计算机网络中实现通信必须有一些约定即通信协议。对速率,传输代码,代码结构,传输控制步骤,出错控制等制定标准。
(3)通讯接口
为使两个结点之间进行对话,必须在它们之间建立通信工具(接口),使彼此之间能进行信息交换。
接口包括两部分
硬件装置:实现结点之间的信息传送
软件装置:规定双方进行通信的约定协议
(4)网络分层
节点之间联系复杂,在制定协议时,把复杂成份分解成一些简单的成分,再将它们复合起来。
最常用的复合方式是层次方式,同层间可以通信,上一层可以调用下一层,而与再下一层不发生关系
上图是osi网络结构
TCP/IP是一个协议族,也是按照层次划分,共四层
应用层,传输层,互连网络层,网络接口层
OSI网络通信协议模型,是一个参考模型,而TCP/IP协议是事实上的标准。TCP/IP协议
参考了OSI 模型。
(5)数据封装
指将协议数据单元(PDU)封装在一组协议头和协议尾中的过程。
在OSI七层参考模型中,每层主要负责与其它机器上的对等层进行通信。该过程是在协议数据单元(PDU)中实现的,其中每层的PDU一般由本层的协议头、协议尾和数据封装构成。
由于用户传输的数据一般都比较大,有的可以达到MB字节,一次性发送出去十分困难,于是就需要把数据分成许多片段,再按照一定的次序发送出去。这个过程就需要对数据进行封装
应用层:准备数据
传输层:接收应用层数据添加上TCP的控制信息(称为TCP头部),这个数据单元称为段(Segment),加入控制信息的过程称为封装。由此,将段交给网络层。
网络层:接收到段,再添加上IP头部,这个数据单元称为包(Packet)。然后,将包交给数据链路层。
数据链路层:将包再添加上MAC头部和尾部,这个数据单元称为帧(Frame)。然后,将帧交给物理层。
物理层:将接收到的数据转化为比特流(Bits),然后在网线中传送。
从高层到底层,逐层进行数据封装
(6)数据解封
指将接收到的数据进行拆包,每一层只把对该层有意义的数据拿走。每一层只能处理发送方同等层的数据,然后把其余的部分传递给上一层。
物理层:接收到比特流,经过处理后将数据交给数据链路层。
数据链路层:将接收到的数据转化为数据帧,再除去MAC头部和尾部,这个除去控制信息的过程称为解封,然后将包交给网络层。
网络层:接收到包,再除去IP头部,然后将段交给传输层。
传输层:接收到段,再除去TCP头部,然后将数据交给应用层。
应用层:处理数据
局域网与因特网
实现两台计算机之间的通信,就需要一个网络线路来连接两台计算机
局域网(LAN):
有多个计算机连接而成组成的封闭式计算机组。局域网规模小,容易小,传送数据快。局域网是结构复杂程度最低的计算机网络。
广域网(WAN):
将LAN延伸到更大的范围,组成广域网。WAN由两个以上的LAN构成。广域网的搭建非常复杂。
.
网络协议
(1)TCP协议
TCP协议是一种以固接连线为基础的协议,它提供两台计算机间可靠的数据传送。TCP可以保证从一端数据送至另一端时,数据能够确实送达,而且抵达的数据的排列顺序和送出时的顺序相同。
TCP协议就好比打电话,拨通号码后,得等到对方接通之后,才能通话,否则无法通话。
(2)UDP协议
UDP协议是无连接通信协议,不保证数据的可靠传输,但能够向若干个目标发送数据,或接受来自若干个源的数据。就想一个大喇叭喊话,有的人听见,有的人没听见,管你听没听见反正我发出了。
TCP与UDP
TCP协议 | UDP协议 |
---|---|
反馈数据是否送达 | 无任何反馈 |
需要创建连接请求,连接成功后发数据,效率低 | 不创建连接请求,直接发数据,效率高 |
端口
比如售票大厅,不同的业务有不同的窗口,如买票窗口,退票窗口。
端口好比窗口,是设备与外界通讯的出口,所有的数据都通过端口发出。
计算机提供了很多端口
可能一个服务占着一个端口,可能一个服务占着多个端口。一个软件占着多个端口,这样一个软件的不同网络请求就有不同的出口,一人一个位置。
端口被规定在0~ 65535,0~1023之间的端口用于一些知名的网络服务和应用,用户的普通网络应用程序应使用1024以上的端口
套接字
socket提供给程序可以对外进行连接的接口
套接字是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合
所谓套接字,实际上是一个通信端点,每个套接字都有一个套接字序号,包括主机的IP地址与一个16位的主机端口号,即形如(主机IP地址:端口号)
IP地址
(1)InetAddress类
定位一个节点:计算机、路由、通讯设备等
InetAddress类没有构造方法,通过它的两个静态方法可以获得InetAddress对象
InetAddress addr = InetAddress.getLocalHost(); //使用getLocalHost方法创建InetAddress对象
InetAddress addr = InetAddress.getByName(“www.163.com”); //根据域名得到InetAddress对象
InetAddress addr = InetAddress.getByName(“192.168.150.11”); //根据ip得到InetAddress对象
InetAddress类的方法
方法 | 返回值 | 说明 |
---|---|---|
getLocalHost() | static InetAddress | 返回本地主机的InentAddress对象 |
getHostName() | String | |
getAddress() | byte[] | 获取字节数组形式的ip地址 |
getHostAddress() | String | 以文本表示形式返回IP地址字符串。 |
getByName(String host) | static InetAddress | 获取与Host相对应的InetAddress对象 |
import java.net.*;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
try {
InetAddress address1=InetAddress.getLocalHost();
System.out.println("主机名:"+address1.getHostName());
System.out.println("IP地址:"+address1.getHostAddress());
byte b[]=address1.getAddress();
System.out.println("字节数组形式的IP地址:"+Arrays.toString(b));
System.out.println(address1);
InetAddress address2=InetAddress.getByName("DESKTOP-BI4H6OF"); //通过主机名获取InetAddress对象
System.out.println("主机名:"+address1.getHostName());
System.out.println("IP地址:"+address1.getHostAddress());
InetAddress address3=InetAddress.getByName("111.114.43.124"); //通过IP地址获取InetAddress对象
System.out.println("主机名:"+address1.getHostName());
System.out.println("IP地址:"+address1.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
输出结果:
主机名:DESKTOP-BI4H6OF
IP地址:111.114.43.124
字节数组形式的IP地址:[111, 114, 43, 124]
DESKTOP-BI4H6OF/111.114.43.124
主机名:DESKTOP-BI4H6OF
IP地址:111.114.43.124
主机名:DESKTOP-BI4H6OF
IP地址:111.114.43.124
(2)端口
端口的表示是一个16位的二进制整数,2个字节,对应十进制的0-65535。
公认端口 0—1023 比如80端口分配给WWW,21端口分配给FTP,8080:tomcat,1521:oracle,3306:MySQL
注册端口 1024—49151 分配给用户进程或应用程序
动态/私有端口 49152–65535
TCP:0-65535
UDP:0-65535
同一个协议端口不能冲突,不同协议端口可以冲突。如TCP的端口80和UDP的端口80就不冲突,但是不好区分,所以不建议
查看端口,cmd命令。
查看所有端口:netstat -ano
查看指定端口:netstat -aon|findstr “808” 找到对应进程的
查看指定进程:tasklist|findstr “909”
查看具体程序:使用任务管理器查看PID
InetSocketAddress
构造方法
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",8080); //ip地址+端口号
InetSocketAddress socketAddress = new InetSocketAddress(地址|域名,端口);
public class PortTest {
public static void main(String[] args) {
//包含端口
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",8080);
InetSocketAddress socketAddress2 = new InetSocketAddress("localhost",9000);
System.out.println(socketAddress.getHostName());
System.out.println(socketAddress.getAddress());
System.out.println(socketAddress2.getAddress());
System.out.println(socketAddress2.getPort());
}
}
输出结果:
127.0.0.1
127.0.0.1/127.0.0.1
localhost/127.0.0.1
9000
URL类
package com.INTERNET;
import java.net.MalformedURLException;
import java.net.URL;
public class Test {
public static void main(String[] args) {
try {
URL address = new URL("http://www.baidu.com");
URL url=new URL(address,"/index.html?username=to#test"); //?表示参数 #后面表示锚点
System.out.println("协议:"+url.getProtocol());
System.out.println("主机:"+url.getHost());
System.out.println("端口:"+url.getPort()); //如果未指定端口号,则getPort()将返回-1
System.out.println("文件路径:"+url.getPath());
System.out.println("文件名:"+url.getFile());
System.out.println("锚点:"+url.getRef());
System.out.println("查询字符串:"+url.getQuery()); //参数
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
输出结果:
协议:http
主机:www.baidu.com
端口:-1
文件路径:/index.html
文件名:/index.html?username=to
锚点:test
查询字符串:username=to
通过URL对象读取网页内容
1.通过URL对象的openStream()方法可以得到指定资源的输入流
2.通过输入流可以读取,访问网络上的数据
package com.INTERNET;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
public class URLTest {
public static void main(String[] args) {
URL url= null;
InputStream is=null;
InputStreamReader isr=null;
BufferedReader br=null;
try {
url = new URL("http://www.baidu.com");
is=url.openStream();
isr=new InputStreamReader(is);
br=new BufferedReader(isr);
String data=br.readLine();
while(data!=null){
System.out.println(data);
data=br.readLine();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(isr!=null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
得到的是百度的网页编码,可以将控制台的输出复制到文本文档中
有些网址无法通过openstream获取,可以通过模拟浏览器
public class Spider {
public static void main(String[] args) throws Exception {
//获取URL
URL url =new URL("https://www.dianping.com");
//下载资源
HttpURLConnection conn =(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36"); //配置一下,模拟服务器
BufferedReader br =new BufferedReader(new InputStreamReader(conn.getInputStream(),"UTF-8"));
String msg =null;
while(null!=(msg=br.readLine())) {
System.out.println(msg);
}
br.close();
}
}
TCP程序设计基础
TCP协议是面向连接,可靠的,有序的,以字节流的方式发送数据
基于TCP协议实现网络通信的类
客户端的Socker类
服务器端的SeverSocket类
通信步骤:
1.建立服务器端监听,等待并接收连接请求
2.客户端创建连接socket,向服务器发送请求
3.服务器端接收请求后,创建连接socket
4.服务器端和客户端之间的数据传输以字节流的Outputstream和Inputstream实现
客户端通过输出流向服务器端发送信息
服务器端通过输入流读取客户端发送的消息
服务器端通过输出流向客户端发送响应
客户端通过输入流读取服务器端的响应
5.关闭相关资源
(1)SeverSocker类
SeverSocket类表示服务器套接字,其功能是等待来自网络上的“请求",它可通过指定的端口来等待连接的套接字。服务器套接字一次可以与一个套接字连接。如果多台客户机同时提出连接请求,服务器套接字会将请求连接的客户机存入列队中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数大于最大容纳数,则多出的连接请求被拒绝。队列的默认大小是50
ServerSocket类的构造方法通常会抛出IOException异常
ServerSocket(); //创建非绑定服务器套接字
SeverSocket(int port); //创建绑定到特定端口的服务器套接字
Socket(String host, int port) ; //创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket(InetAddress address, int port); // 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) ; //创建一个套接字并将其连接到指定远程地址上的指定远程端口。
Socket(String host, int port, InetAddress localAddr, int localPort) ; //创建一个套接字并将其连接到指定远程主机上的指定远程端口。
方法 | 返回值 | 说明 |
---|---|---|
accept() | Socket | 等待客户机的连接。若连接创建一个套接字(Socket) |
bind() | void | 将 ServerSocket绑定到特定地址(IP地址和端口号)。 |
isBound() | boolean | 返回ServerSocket的绑定状态 |
isClosed() | boolean | 返回ServerSocket的关闭状态。 |
getInetAddress() | InetAddress | 返回此服务器套接字的本地地址。 |
getLocalPort() | int | 返回此套接字正在侦听的端口号。 |
close() | void | 关闭此套接字。 |
SeverSocket sever=new SeverSocket("loacl",8888);
Socket so=sever.accept();
System.out.println("服务器等待客户端连接");
如果没有客户呼叫服务器,那么System.out那句就不会执行。服务器一直处于阻塞状态
SeverSocket通过accept()方法返回的Socket,会和此Socket创建连接。通过Socket的getInputstream()和getOutputstream()获得字节流信息
(2)Socket类
构造方法
Socket(); // 创建一个未连接的套接字,并使用系统默认类型的SocketImpl。
Socket(String host, int port); // 创建流套接字并将其连接到指定主机上的指定端口号。
客户端使用构造方法创建Socket(String host, int port)与服务器建立连接
服务器端使用ServerSocket的accept()方法获得Socket
常用方法
方法 | 说明 |
---|---|
getInputStream() | 返回此套接字的输入流 |
getOutputStream() | 返回此套接字的输出流。 |
close() | 关闭此套接字。 |
常用getInputStream()和getOutputStream()做数据操作
TCP基本流程
下面是一个登录的例子,登录时客户端向服务器端发送请求,服务器端接收请求之后向客户端发送响应,客户端接收响应
服务器端代码
package com.INTERNET;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) {
try {
ServerSocket serverSocket=new ServerSocket(8888); //创建服务器socjer,指定端口
System.out.println("服务器等待客户端连接");
Socket socket=serverSocket.accept(); //调用accept()方法监听,等待客户端连接
//服务器接收数据
InputStream is=socket.getInputStream(); //获取输入流,并读取客户端信息
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
String str=null;
while((str=br.readLine())!=null){
System.out.println("客户端信息:"+str);
}
socket.shutdownInput();
//服务端响应服务端
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
pw.write("欢迎登录");
pw.flush();
//关闭资源
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码
package com.INTERNET;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket=new Socket("localhost",8888); //创建客户端socket,指定服务器地址和端口
//客户端上传数据
OutputStream os=socket.getOutputStream(); //输出流
PrintWriter pw=new PrintWriter(os);
pw.write("用户名:tom 密码:1");
pw.flush();
socket.shutdownOutput(); //关闭输出流
//客户端接收服务端的响应
InputStream is=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
String str=null;
while((str=br.readLine())!=null){
System.out.println("服务端信息:"+str);
}
socket.shutdownInput();
//关闭资源
br.close();
isr.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行时先运行服务器端,开启服务器,再运行客户端,开启客户端。
1.运行服务端
2.运行客户端,服务器端接收请求
3.客户端接收的响应
TCP多线程
上面只是服务器接收一个客户的请求,如果是多用户发生请求,就需要多线程
线程类,在run方法里写接收请求的过程
package com.INTERNET;
import java.io.*;
import java.net.Socket;
public class SeverThread extends Thread {
//和本线程相关的socket
Socket socket=null;
public SeverThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
InputStream is= null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
PrintWriter pw=null;
//获取输入流,并读取客户端信息
try {
is = socket.getInputStream();
isr=new InputStreamReader(is);
br=new BufferedReader(isr);
String str=null;
while((str=br.readLine())!=null){
System.out.println("客户端信息:"+str);
}
socket.shutdownInput();
//服务端响应服务端
os=socket.getOutputStream();
pw=new PrintWriter(os);
pw.write("欢迎登录");
pw.flush();
//关闭资源
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端,调用多线程
package com.INTERNET;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class DuoServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket=new ServerSocket(8888); //创建服务器socjer,指定端口
Socket socket=null;
System.out.println("服务器等待客户端连接");
int count=0; //客户端数量
while (true){
socket=serverSocket.accept(); //调用accept()方法监听,等待客户端连接
SeverThread severThread=new SeverThread(socket);
severThread.start();
count++;
System.out.println("客户端数量:"+count);
InetAddress address=socket.getInetAddress();
System.out.println("客户端IP:"+address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
package com.INTERNET;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket=new Socket("localhost",8888); //创建客户端socket,指定服务器地址和端口
OutputStream os=socket.getOutputStream(); //输出流
PrintWriter pw=new PrintWriter(os);
pw.write("用户名:tom 密码:1");
pw.flush();
socket.shutdownOutput(); //关闭输出流
//客户端接收服务端的响应
InputStream is=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
String str=null;
while((str=br.readLine())!=null){
System.out.println("服务端信息:"+str);
}
socket.shutdownInput();
//关闭资源
br.close();
isr.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.运行服务器端
2.运行客户端
3.此时服务器端
4.改变客户端里的代码,将用户名和密码修改,再运行客户端
5.两次客户端运行时服务器端
TCP文件上传
TCP文件的大小没有限制
import java.io.*;
import java.net.Socket;
public class TcpFileCilent {
public static void main(String[] args) throws IOException {
//1.建立连接 使用Socker创建,指定服务器的地址和端口
System.out.println("客户端");
Socket client=new Socket("localhost",8888);
//2.上传
InputStream is=new BufferedInputStream(new FileInputStream("marvel.png"));
OutputStream os=new BufferedOutputStream(client.getOutputStream());
byte[] data=new byte[1024*60];
int len=-1;
while((len=is.read(data))!=-1){
os.write(data,0,len);
}
os.flush();
//3.释放资源
os.close();
is.close();
client.close();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpFileServer {
public static void main(String[] args) throws IOException {
//1.指定端口,建立ServerSocket
System.out.println("服务器 ");
ServerSocket server=new ServerSocket(8888);
//2.等待连接
Socket client=server.accept();
System.out.println("服务器等待连接");
//3.处理文件
InputStream is=new BufferedInputStream(client.getInputStream());
OutputStream os=new BufferedOutputStream(new FileOutputStream("tcp.png"));
byte[] data=new byte[1024*60];
int len=-1;
while((len=is.read(data))!=-1){
os.write(data,0,len);
}
os.flush();
System.out.println("传输文件完毕");
//4.释放资源
os.close();
is.close();
client.close();
server.close();
}
}
UDP编程基础
用户数据报协议(UDP)是网络信息传输的另一种形式。是无连接,不可靠的,无序的。基于UDP的信息传递更快,但不提供可靠的保证。使用UDP传递数据时,用户无法知道数据能否正确地到达主机,也不能确定到达目的地的顺序是否和发送顺序相同。虽然UDP是一种不可靠的协议,但如果需要较快地传送信息,并能容忍小的错误,可以考虑使用UDP,如小视频软件
UDP以数据报作为数据传输的载体
进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socker(主机地址和端口号),然后再将数据发送出去
基于UD通信的基本模式如下:
将数据打包,然后将数据包发往目的地
接收别人发来的数据包,然后查看数据包
相关操作类
DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类
发送数据包的步骤
1.使用DatagramSocket(int port)创建一个数据包套接字 指定端口
2.使用DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)创建要发送的数据报包
3.使用DatagramSocket()类的send()方法发送数据包
接收数据包步骤
1.使用DatagramSocket(int port)创建数据包套接字,绑定到指定的端口
2.使用DatagramPacket(byte[] buf,int length)创建字节数组来接收数据报包
3.使用DatagramPacket类的receive()方法接受UDP包
DatagramPacket类
DatagramPacket的构造函数有
DatagramPacket(byte[] buf,int length); //指定数据报包的内存空间和大小
DatagramPacket(byte[] buf,int length,InetAddress address,int port); //指定数据报包的内存空间,大小,目标地址和端口
buf - 分组数据。
offset - 分组数据偏移。
length - 分组数据长度。
address - 目标套接字地址。
在发送数据时,需要指定接受方的Socket地址和端口号,所以使用第二种构造函数可创建发生数据的DatagramPacket
方法 | 返回值 | 说明 |
---|---|---|
getAddress() | InetAddress | 返回该数据报发送或接收数据报的计算机的IP地址。 |
getData() | byte[] | 返回数据缓冲区。 |
getLength() | int | 返回要发送的数据的长度或接收到的数据的长度 |
getPort() | int | 返回发送数据报的远程主机上的端口号,或从中接收数据报的端口号。 |
getSocketAddress() | SocketAddress | 获取该数据包发送到或正在从其发送的远程主机的SocketAddress(通常为IP地址+端口号)。 |
setAddress(InetAddress iaddr) | void | 设置该数据报发送到的机器的IP地址。 |
setData(byte[] buf) | void | 设置此数据包的数据缓冲区 |
setData(byte[] buf, int offset, int length) | void | 设置此数据包的数据缓冲区。 |
setLength(int length) | void | 设置此数据包的长度。 |
setPort(int iport) | void | 设置发送此数据报的远程主机上的端口号。 |
setSocketAddress(SocketAddress address) | void | 设置该数据报发送到的远程主机的SocketAddress(通常是IP地址+端口号)。 |
DatagramSocket类
DatagramSocket构造方法
DatagramSocket(); //构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket(int port); //构造数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr); //创建一个数据报套接字,绑定到指定的本地地址。
第三种构造方法适用于有多块网卡和多个IP地址的情况
在接收程序时,必须指定一个端口号,不允许系统随机产生,此时可以使用第二种构造函数。
发送程序时通常使用第一种构造函数,不指定端口号,由系统分配一个端口号。就好比快递,必须要有收件的地址,但是我用谁家的快递无所谓,能到就行。
方法 | 返回值 | 说明 |
---|---|---|
send(DatagramPacket p) | void | 从此套接字发送数据报包。 |
receive(DatagramPacket p) | void | 从此套接字接收数据报包。 |
connect(InetAddress address, int port) | void | 将套接字连接到此套接字的远程地址。 |
connect(SocketAddress addr) | void | 将此套接字连接到远程套接字地址(IP地址+端口号)。 |
close() | void | 关闭此数据报套接字 |
getInetAddress() | InetAddress | 获取套接字所绑定的本地地址。 |
getPort() | int | 返回此套接字连接到的端口号 |
(1)基本UDP流程
下面是UDP的登陆
服务器端
package com.INTERNET;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPserver {
public static void main(String[] args) {
try {
DatagramSocket socket=new DatagramSocket(8800); //创建服务器DatagramSocket,指定接口
byte[] container=new byte[1024]; //准备容器
DatagramPacket packet=new DatagramPacket(container,container.length); //创建数据报
System.out.println("服务器端已启动,等待客户端");
socket.receive(packet);
byte[] data=new byte[1024];
String info=new String(data,0,packet.getLength()); //读取数据
System.out.println("客户端信息:"+info);
//服务器响应
InetAddress address=packet.getAddress();
int port=packet.getPort();
byte[] data2="欢迎登陆".getBytes();
DatagramPacket packet1=new DatagramPacket(data2,data2.length,address,port);
socket.send(packet1);
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.INTERNET;
import java.io.IOException;
import java.net.*;
public class UDPclient {
public static void main(String[] args) {
try {
//定义服务器的地址,端口号,数据
InetAddress address=InetAddress.getByName("localhost");
int port=8800;
byte[] data="用户名:admin 密码:123".getBytes();
DatagramPacket packet=new DatagramPacket(data,data.length,address,port);
DatagramSocket socket=new DatagramSocket();
socket.send(packet);
//接收服务器端的响应
byte[] data2=new byte[1024];
DatagramPacket packet1=new DatagramPacket(data2,data2.length);
socket.receive(packet1);
String replay=new String(data2,0,packet1.getLength());
System.out.println("服务器响应:"+replay);
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)加入IO流的基本UDP流程
服务器端
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* DatagramSocket指定端口
* DatagramPacket接收
* receive
*/
public class UdpTypeServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器端启动");
//1.服务器指定端口
DatagramSocket socket=new DatagramSocket(6666);
//2.准备容器接收
byte[] container=new byte[1024]; //接收容器
DatagramPacket packet=new DatagramPacket(container,0,container.length);
socket.receive(packet); //将容器传进去接收
//3.从容器中获得数据
byte[] datas=packet.getData(); //从容器获得数据
//4.分析数据
int len=packet.getLength();
DataInputStream dis=new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(datas)));
String msg=dis.readUTF();
int age=dis.readInt();
boolean flag=dis.readBoolean();
char ch=dis.readChar();
System.out.println(msg);
//5.释放资源
socket.close();
}
}
客户端
import java.io.*;
import java.net.*;
/**
* Udp使用DatagramSocket
* 数据封装成DatagramPacket
* 发送
*/
public class UdpTypeClient {
public static void main(String[] args) throws IOException {
//1.指定端口
System.out.println("客户端发送");
DatagramSocket socket=new DatagramSocket(8888);
//2.准备数据
ByteArrayOutputStream baos=new ByteArrayOutputStream();
DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(baos));
dos.writeUTF("这里是客户端");
dos.writeInt(18);
dos.writeBoolean(true);
dos.writeChar('a');
dos.flush();
byte[] data=baos.toByteArray(); //转成字节数组
//3.封装成DatagramPacket,指定目的地
DatagramPacket packet=new DatagramPacket(data,0,data.length,new InetSocketAddress("localhost",6666));
//4.发送
socket.send(packet);
//5.释放资源
socket.close();
}
}
(3)多次交流UDP
上面都只是客户端发送一次请求,服务器端接收。
下面是客户端发送多次请求,服务器端接收
服务器端
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpTalkServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器端启动 ");
//1.服务器指定端口
DatagramSocket socket=new DatagramSocket(6666);
//2.准备容器接收
byte[] container=new byte[1024*60]; //接收容器
while(true) {
DatagramPacket packet = new DatagramPacket(container, 0, container.length);
socket.receive(packet); //将容器传进去接收
//3.从容器中获得数据
byte[] datas = packet.getData(); //从容器获得数据
String data=new String(datas,0,packet.getLength());
//4.分析数据
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
bw.write(data);
bw.newLine();
bw.flush();
// System.out.println(data);
if(data.equals("bye")){
break;
}
}
//5.释放资源
socket.close();
}
}
客户端
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class UdpTalkCilent {
public static void main(String[] args) throws IOException {
System.out.println("客户端发送");
DatagramSocket socket=new DatagramSocket(8888);
BufferedReader br=new BufferedReader(new InputStreamReader(System.in)) ;
while(true){
String data=br.readLine();
byte[] container=data.getBytes();
DatagramPacket packet=new DatagramPacket(container,0,container.length,new InetSocketAddress("localhost",6666));
socket.send(packet);
if(data.equals("bye")){
break;
}
}
socket.close();
}
}
(4)UDP文件上传
UDP文件的大小有限制,文件太大上传不了会报错
写一个工具类,实现文件与字节数组的互转
import java.io.*;
public class IOFile {
public static byte[] fileToByte(String path){
File src=new File(path);
InputStream in=null;
ByteArrayOutputStream baos=null;
try {
in=new FileInputStream(src);
baos=new ByteArrayOutputStream();
byte[] data=new byte[1024*10];
int len=-1;
while((len=in.read(data))!=-1){
baos.write(data,0,len);
}
baos.flush();
return baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public static void byteToFile(byte[] data,String filePath){
File dest=new File(filePath);
OutputStream out=null;
try {
out=new FileOutputStream(dest);
out.write(data,0,data.length);
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpFileServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器端启动");
DatagramSocket socket=new DatagramSocket(6666);
byte[] container=new byte[1024*60];
DatagramPacket packet=new DatagramPacket(container,0,container.length);
socket.receive(packet);
IOFile.byteToFile(packet.getData(),"dest.png");
socket.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class UdpFileCilent {
public static void main(String[] args) throws IOException {
System.out.println("客户端启动");
DatagramSocket socket=new DatagramSocket(8888);
byte[] data=IOFile.fileToByte("src/logo.png");
DatagramPacket packet=new DatagramPacket(data,0,data.length,new InetSocketAddress("localhost",6666));
socket.send(packet);
socket.close();
}
}
(5)UDP互相多次交流
加入多线程
接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TalkReceive implements Runnable{
private DatagramSocket socket;
private String from;
public TalkReceive(int port,String from){
this.from = from ;
try {
socket=new DatagramSocket(port);
} catch (SocketException e) {
e.printStackTrace();
}
}
public TalkReceive(DatagramSocket socket, String from) {
this.socket = socket;
this.from = from;
}
@Override
public void run() {
byte[] container=new byte[1024*60];
while(true){
DatagramPacket packet=new DatagramPacket(container,0,container.length);
try {
socket.receive(packet);
byte[] datas=packet.getData();
String data=new String(datas,0,packet.getLength());
System.out.println(from+":"+data);
if(data.equals("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
socket.close();
}
}
发送端
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class TalkSend implements Runnable{
private DatagramSocket socket;
private String toIP ;
private int toPort ;
public TalkSend(DatagramSocket socket) {
this.socket = socket;
}
public TalkSend(int port,String toIp,int toPort){
try {
socket=new DatagramSocket(port);
} catch (SocketException e) {
e.printStackTrace();
}
this.toIP=toIp;
this.toPort=toPort;
}
@Override
public void run() {
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
while(true){
try {
String info=br.readLine();
byte[] data=info.getBytes();
//ByteArrayInputStream bout=new ByteArrayInputStream(data,0,data.length);
DatagramPacket packet=new DatagramPacket(data,0,data.length,new InetSocketAddress(this.toIP,this.toPort));
socket.send(packet);
if(info.equals("bye")){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
socket.close();
}
}
public class Teacher {
public static void main(String[] args) {
new Thread(new TalkReceive(8888,"学生")).start();
new Thread(new TalkSend(7777,"localhost",9999)).start();
}
}
public class Student {
public static void main(String[] args) {
new Thread(new TalkSend(6666,"localhost",8888)).start();
new Thread(new TalkReceive(9999,"老师")).start();
}
}
多线程问题
在多线程的传输时,服务器端一直在循环,可以设置多线程的优先级,未设置优先级可能会导致运行速度非常慢,可降低优先级
关闭流问题
对于同一个socket,如果关闭了输出流,则与该输出流关联的socker也会被关闭,所以一般不用关闭流,直接关闭socket即可
Socket socket=new Socket("localhost",8888); //创建客户端socket,指定服务器地址和端口
OutputStream os=socket.getOutputStream(); //输出流
PrintWriter pw=new PrintWriter(os);
pw.write("用户名:tom 密码:1");
pw.flush();
//pw.close() 不能关闭输出流,会导致socket也会被关闭
InputStream is=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
String str=null;
while((str=br.readLine())!=null){
System.out.println("服务端信息:"+str);
}
socket.close() //直接关闭socket即可
传输对象
一般项目中不会传输字符串,而传输对象,可以使用ObjectOutputStream对象序列化流,传输对象
传输文件
使用文件传输流传递文件