网络编程
网络编程要素
网络编程:在网络间完成数据的传输,相比于IO流在本地完成数据的传输
网络编程要素
- 如何准确地定位网络上一台或多台主机【ip】
- 找到主机后如何可靠高效地进行数据传输。【协议】
java.net.InetAddress
- InetAddress.getLocalHost() 获取本地ip对应的InetAddress实例
- String getHostAddress() 返回该InetAddress实例对应的ip地址字符串
//获取本机ip
public static void main(String[] args) {
try {
//打印流 打印本机ip
System.out.println(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
唯一标识 Internet 上的计算机,InetAddress类主要表示IP地址
- InetAddress.getByName() 根据主机名获取对应的InetAddress实例
- String getHostAddress() 获取InetAddress实例对应的ip地址字符串
public static void main(String[] args) throws UnknownHostException {
//根据主机名获取对应的InetAddress实例
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
//获取InetAddress实例域名
System.out.println(inetAddress.getHostName());
//获取InetAddress实例ip地址
System.out.println(inetAddress.getHostAddress());
//获取InetAddress实例ip地址的全限定域名
System.out.println(inetAddress.getCanonicalHostName());
}
ip地址的全限定域名??
DatagramSocket与DatagramPacket
java.net.DatagramPacket
系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
public class FileTest {
//UDP发送端
@Test
public void client() throws IOException {
//使用UDP协议传输
DatagramSocket datagramSocket = new DatagramSocket();
byte[] bytes="我在精神病院学斩神".getBytes(StandardCharsets.UTF_8);
//DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
//构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
DatagramPacket p=new DatagramPacket(bytes,0,bytes.length, InetAddress.getByName("127.0.0.1"),9898);
datagramSocket.send(p);
datagramSocket.close();
}
//UDP接收端
@Test
public void server() throws IOException {
//使用UDP协议传输
DatagramSocket datagramSocket = new DatagramSocket(9898);
byte[] bytes=new byte[1024];
//DatagramPacket(byte[] buf, int length)
//构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket p=new DatagramPacket(bytes,bytes.length);
datagramSocket.receive(p);
System.out.println(new String(p.getData(),0,p.getData().length));
datagramSocket.close();
}
}
java.net.URL
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>
例如: http://192.168.1.100:8080/helloworld/index.jsp
构造器:
URL(String spec)
根据 String 表示形式创建 URL 对象。
方法:
public final InputStream openStream() throws IOException
打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
此方法是下面方法的缩写: openConnection().getInputStream()public URLConnection openConnection() throws IOException
返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
//获取整个url结构
一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性
public String getProtocol( ) ;//URL的协议名
public String getHost( ) ;//该URL的主机名
public String getPort( ) ;// 获取该URL的端口号
public String getPath( ) 获取该URL的文件路径
public String getFile( ) 获取该URL的文件名
public String getRef( ) 获取该URL在文件中的相对位置
public String getQuery( ) 获取该URL的查询名
@Test
public void test03() throws Exception {
// 通过一个表示URL地址的字符串可以构造一个URL对象
URL url = new URL("http://192.168.15.55/atguigu/hello.txt");
// InputStream in = url.openStream(); //openStream():能从网络上读取数据,获取指定站点的资源
URLConnection connection = url.openConnection(); //不仅可以获取指定站点资源,还可以发送资源到指定站点
InputStream in = connection.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b)) != -1){
System.out.println(new String(b, 0, len));
}
in.close();
}
HttpClient
HttpClient HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包。
HttpClient基本上是java所有网络请求发起的底层的框架,比如说微服务之间的远程调用、阿里云oss、发送短信。。。
1、执行HTTP方法 //1、创建对象 HttpClient httpClient = new DefaultHttpClient(); //2.创建请求配置:请求方式+地址+参数配置 HttpGet httpGet = new HttpGet("http://www.baidu.com"); //3.发送请求 HttpResponse response = httpClient.execute(httpGet); //4.解析结果 System.out.println(response.getStatusLine().getStatusCode()); String jsonString = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(jsonString);
Socket和ServerSocket
通信基本概念
短连接:socket连接,发送数据,接收数据后马上断开
长连接:建立socket连接后,不管是否使用,保持连接
半包:接收方没有接收到一个完整的包,只接收到包的一部分
粘包:发送方发送的多个包数据到接收方接收时粘成一个包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
分包:出现粘包情况下,需要分包处理或一个数据包被分成多次接收。
Socket通信步骤
不考虑本地的进程间通信。问网络中进程之间如何通信?
1.在本地可以通过进程PID来唯一标识一个进程
2.端口标识了应用层中不同的进程(ip地址,协议,端口)唯一标识网络进程
端口号与IP地址的组合得出一个网络套接字。套接字允许应用程序将 I/O 应用于网络中,并与其他应用程序进行通信
- 通信的两端都要有Socket,是两台机器间通信的端点,网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 客户端和服务器现在可以通过对
Socket
对象的写入和读取来进行进行通信。
端口号标识正在计算机上运行的进程, 被规定为一个 16 位的整数 0~65535。
其中,0~1023被预先定义的服务通信占用(如MySql占用端口3306,http占用端口80等)。除非我们需要访问这些特定服务,否则,就应该使用 1024~65535 这些端口中的某一个进行通信,以免发生端口冲突。
通信步骤
建立连接
1、服务器端先初始化Socket(ServerSocket),然后与端口绑定,对端口进行监听(listen),调用accept阻塞,等待客户端连接
2、客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。
数据交互
客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
java.net.Socket
客户端 要获取一个Socket
对象通过实例化
setSoTimeout public void setSoTimeout(int timeout) throws SocketException
这个用来设置与socket的inputStream相关的read操作阻塞的等待时间,以毫秒为单位。超过设置的时间了,假如还是阻塞状态,会抛出异常java.net.SocketTimeoutException: Read timed out
这里的阻塞不是指read的时间长短,可以理解为没有数据可读,线程一直在这等待
//客户端
public class ClientDemo {
public static void main(String[] args) throws Exception {
String str = "《我在精神病院斩神》";
//指定要连接的 IP地址和端口号
Socket s = new Socket("127.0.0.1", 1314);
//读取数据时阻塞链路的超时时间
s.setSoTimeout(5000);//以毫秒为单位
//发送数据给服务端
OutputStream os = s.getOutputStream();
os.write(str.getBytes());
//发送完毕 禁用此套接字的输出流
s.shutdownOutput();
//接收服务端的反馈
InputStream in = s.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while ((len = in.read(b)) != -1) {
System.out.println(new String(b, 0, len));
}
in.close();
os.close();
s.close();
}
}
Socket 相关API
打开连接到 Socket 的输入/出流
获取与Socket相关联的字节输入流,用于从Socket中读数据。
InputStream getInputStream() throws IOException
-------------------
获取与Socket相关联的字节输出流,用于向Socket中写数据。OutputStream getOutputStream() throws IOException
-------------
关闭监听Socket
void close()throws IOException
isBound(); 如果Socket已经与本地一个端口绑定,返回true;否则返回false
isClosed(); //连接是否已关闭,若关闭,返回true;否则返回false
isConnected;//曾经是否连接成功过。
要判断当前的Socket对象是否处于连接状态
必须同时使用isClose和isConnected方法, 即只有当isClosed返回false,isConnected返回true的时候Socket对象才处于连接状态。
java.net.ServerSocket
- ServerSocket(int port) throws IOException
- ServerSocket(int port, int backlog) throws IOException
- ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
参数port指定服务器要绑定的端口(服务器要监听的端口),参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。
Socket accept() throws IOException 等待客户端的连接请求,返回与该客户端进行通信用的Socket对象
服务器获得一个
Socket
对象则通过accept()
方法的返回值ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
1.服务器中有一个accept()方法创建与客户连接的 Socket 对象[socket],而在没有客户机连接的时候,此方法accept会一直阻塞,所以将其放在一个线程类[ServerListener ]中运行较为合适
服务器从该[socket] 对象中获得输入流和输出流, 就能与客户交换数据
public class ServerListener extends Thread {
private int port = 8888;
private ServerSocket serverSocket;
public static ArrayList<ServerThread> serverThreads = new ArrayList<>();
static boolean stop = false;
public ServerListener() throws IOException {
serverSocket = new ServerSocket(port);
System.out.println("服务器启动,端口号为:" + port);
}
@Override
public void run() {
//每一个客户机对象都有一个线程来处理
while (true) {
if (stop) {
//关闭资源,跳出循环
break;
} else {
try {
//该accept()方法将一直等待,直到客户端连接到服务器上给定的端口。
Socket socket = serverSocket.accept();
if (!stop) {
//正常处理
System.out.println("有一个客户机上线了");
ServerThread st = new ServerThread(socket);
serverThreads.add(st);
st.start();
} else {
this.serverSocket.close();
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//关闭serverSocket的方法
public static void closeClient() {
stop = true;
try {
//与本serverSocket建立链接,建立之后立即关闭。这样会打破了accept方法阻塞状态
new Socket("127.0.0.1", 8888).close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.服务器每连接上一个客户端的时候我们都去开启一个线程[ServerThread ],在新开的线程去接收输入输出流,处理输入输出。
socket通信俩端都是socket,不同的是
客户端自己new socket(),new的时候指定ip和端口号来确定去找哪一个socket跟它通信。
服务端,是早早开启socketServer使用accept()监听端口,来一个客户端socket再去创建一个服务端socket去通信,此时俩个socket就都建立起来了
可笑我以前还一直以为ServerSocket就是服务端创建的socket呢,,,一直误会
public class ServerThread extends Thread {
private Socket serverScoket;
private OutputStream ops;
private InputStream ips;
boolean isAck = false;
boolean isOver = false;
public ServerThread(Socket serverScoket) {
this.serverScoket = serverScoket;
try {
ops = serverScoket.getOutputStream();
ips = serverScoket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
//读取从客户机发来的消息
public void readMessage() {
try {
byte[] bytes = new byte[1024];
//循环读取客户端数据到bytes数组
while (true) {
int len = 0;
while ((len = ips.read(bytes)) != -1) {
String str = new String(bytes, 0, len);
System.out.println("服务器接收客户机消息成功——>" + str);
//客户机确认无误,开始通信
if (str.equals("client-ack")) {
isAck = true;
}
//客户端发起关闭连接请求
if (str.equals("client-over")) {
isOver = true;
break;//跳出内循环
}
}
if (isOver) {
serverScoket.close();//关闭socket
ServerListener.closeserverScoket();//关闭serverSocket
break;
}
}
} catch (IOException e) {
System.out.println("服务器读取消息失败");
e.printStackTrace();
}
}
//给客户机发送消息
public void sendMessage(String msa) {
if (!isAck) {//未连接客户端,不允许通信
return;
}
try {
ops.write(msa.getBytes("GBK"));
ops.flush();
System.out.println("服务器发送消息成功:" + msa);
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器发送消息失败");
}
}
//消息随时会有,用线程来执行更为合适
public void run() {
System.out.println("线程信息 id=" + Thread.currentThread().getId() + "名字=" + Thread.currentThread().getName());
try {
//给客户端发送确认信息
ops.write("server-ack".getBytes("GBK"));
ops.flush();
} catch (IOException e) {
e.printStackTrace();
}
readMessage();
}
}
单独启动客户机
=========================
启动服务器--用telnet命令当客户机
红色方块一直亮着。等待客户机连接,并且主线程没有被accept阻塞住
ServerSocket属性和方法
SO_TIMEOUT表示ServerSocket的accept()方法等待客户连接的超时时间,以毫秒为单位。 如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认值。
设置该选项:public void setSoTimeout(int timeout) throws SocketException
读取该选项:public int getSoTimeout () throws IOException
写入网络数据时,调用flush()
方法。
如果不调用flush()
,我们很可能会发现,客户端和服务器都收不到数据,这并不是Java标准库的设计问题,而是我们以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()
强制把缓冲区数据发送出去。