网络编程
一、网络编程
步骤:
1.找到对方ip。
2.数据要发到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这个数字,就叫作端口或者逻辑端口。
3.定义通讯规则。这个通讯规则称为协议。国际组织定义了通用协议TCP/IP。
TCP/IP参考模型
IP地址:网络中设备的标识,不易记忆,可用主机名记忆。本地回环地址:127.0.0.1,主机名:localhost。
端口号:用于标识进程的逻辑地址,不同进程间的标识。有效端口:0~65535,其中0~1024为系统使用或保留端口。
传输协议:常见协议TCP,UDP。
java.net:网络包
InetAddress:专门用于描述网络地址的类。
static InetAddress getLocalHost()
返回本地主机。
例1:
import java.net.*;
class Demo
{
public static void main(String []args) throws Exception{
//获取本机IP地址。
InetAddress add = InetAddress.getLocalHost();
System.out.println(add);
}
}
例2:
import java.net.*;
class Demo
{
public static void main(String []args) throws Exception{
//获取百度IP地址。
InetAddress add = InetAddress.getByName("www.baidu.com");
System.out.println("address:"+add.getHostAddress());
System.out.println("name:"+add.getHostName());
}
}
当IP地址不唯一时,既多台服务器,如百度。可以使用getAllByName方法。
static InetAddress[] getAllByName(String host)
在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。
例:
import java.net.*;
class Demo
{
public static void main(String []args) throws Exception{
//获取百度IP地址。
InetAddress[] add = InetAddress.getAllByName("www.baidu.com");
for(InetAddress ia : add){
System.out.println("address:"+ia.getHostAddress());
System.out.println("name:"+ia.getHostName());
}
}
}
UDP传输:将数据源和目的封装到数据包中。不需要建立连接。每个数据包的大小限制在64k内,因为面向无连接,速度很快,是不可靠协议。
特点:
1.面向无连接。
2.数据会被封包,限制64k。
3.不可靠,因为面向无连接。
4.速度快。
应用:QQ,视频通话。
TCP传输:建立连接,形成传输数据的通道。在连接中,进行大数据量传输。通过三次握手完成连接,是可靠协议。
特点:
1.必须建立连接
2.可靠。
3.效率低。
应用:下载。
Socket:为网络服务提供的一种机制。通信的两端都有Socket。网络通信其实就是Socket间的通信。数据在两个Socket间通过IO传输。
DatagramSocket与DatagramPacket:UDP传输。
1.建立发送端。
2.建立数据包。
3.调用Socket的发送接收方法。
4.关闭Socket。
发送端与接收端是两个独立的运行程序。
DatagramSocket:此类表示用来发送和接收数据报包的套接字。
void receive(DatagramPacket p)
从此套接字接收数据报包。
void send(DatagramPacket p)
从此套接字发送数据报包。
DatagramPacket:此类表示数据报包。
需求:通过UDP传输方式,将一段文字数据发送出去。
思路:
1.建立UDPSocket服务。
2.提供数据,将数据封装到数据包中。
3.通过Socket服务的发送功能,将数据发出去。
4.关闭资源。
发送端:
import java.net.*;
class Demo1
{
public static void main(String []args) throws Exception{
//1.创建UDP服务,通过DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2.确定数据,并封装成数据包。
byte[] buf = "UDP,我来了".getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName
("192.168.0.102"),10001);
//3.通过Socket服务,将已有的数据包发送出去,通过send方法。
ds.send(dp);
//4.关闭资源。
ds.close();
}
}
需求:定义一个应用程序,用于接收UDP协议传输的数据,并处理。
思路:
1.定义UDPSocket服务,监听端口,其实就是给这个接收端口定义数组标识,方便于明确哪些数据过来,该应用程序可以处理。
2.定义一个数据包,存储接收到的字节数据。因为数据包对象中有更多功能可以提前字节数据中的不同数据信息。
3.通过Socket服务的receive方法,将收到的数据存入定义好的数据包中。
4.通过数据包对象的特有功能,将这些不同的数据取出,打印到控制台上。
5.关闭资源。
接收端:
import java.net.*;
class Demo2
{
public static void main(String []args) throws Exception{
//1.创建UDPSocket服务,建立端点。
DatagramSocket ds = new DatagramSocket(10001);
//2.定义数据包,用于存储数据。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3.通过服务的receive方法,将接收到的数据存入数据包中。
ds.receive(dp);
//4.通过数据包的方法获取其中的数据。
String ip = dp.getAddress().getHostAddress();
String data = new String(buf,0,dp.getLength());
int port = dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
//5.关闭资源
ds.close();
}
}
例:
import java.net.*;
class Demo2
{
public static void main(String []args) throws Exception{
DatagramSocket ds = new DatagramSocket(10001);
DatagramSocket ds1 = new DatagramSocket(10001);
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(buf,0,dp.getLength());
int port = dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
ds.close();
}
}
抛出BindException:绑定异常。因为多个服务用一个端口,不能循环建立。
练习:发送端键盘输入,然后接收端打印到控制台。
发送端:
import java.net.*;
import java.io.*;
class Demo1
{
public static void main(String []args) throws Exception{
//创建服务
DatagramSocket ds = new DatagramSocket();
//读取键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = br.readLine())!=null){
//把消息封装成字节数组
byte[] buf = line.getBytes();
//把消息封装成数据包
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.102"),10001);
//将数据包发送出去
ds.send(dp);
//当输入"886"表示结束
if("886".equals(line))
break;
}
//关闭资源
br.close();
br.close();
}
}
接收端:
import java.net.*;
class Demo2
{
public static void main(String []args) throws Exception{
//创建服务监听10001端口
DatagramSocket ds = new DatagramSocket(10001);
while(true){
//定义字节缓冲区,给数据包用于接收数据
byte[] buf = new byte[1024*64];
//定义数据包接收数据
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//将接收到的数据放入数据包中
ds.receive(dp);
//获取ip
String ip = dp.getAddress().getHostAddress();
//获取数据
String data = new String(buf,0,dp.getLength());
//打印
System.out.println(ip+":"+data);
}
}
}
注:xxx.xxx.xxx.255 是广播地址。xxx.xxx.xxx.0 代表网络段。xxx.xxx.xxx.1 默认为网关。
练习:编写聊天程序
import java.net.*;
import java.io.*;
class Demo
{
public static void main(String []args) throws Exception{
DatagramSocket sendsocket = new DatagramSocket();
DatagramSocket receivesocket = new DatagramSocket(10001);
new Thread(new SendThread(sendsocket)).start();
new Thread(new ReceiveThread(receivesocket)).start();
}
}
//发送线程
class SendThread implements Runnable
{
private DatagramSocket ds;
SendThread(DatagramSocket ds){
this.ds = ds;
}
public void run(){
try{
//创建服务
DatagramSocket ds = new DatagramSocket();
//读取键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = br.readLine())!=null){
//把消息封装成字节数组
byte[] buf = line.getBytes();
//把消息封装成数据包
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.255"),10001);
//将数据包发送出去
ds.send(dp);
//当输入"886"表示结束
if("886".equals(line))
break;
}
//关闭资源
br.close();
br.close();
}
catch(Exception e){
System.out.println("发送端失败");
}
}
}
//接收线程
class ReceiveThread implements Runnable
{
private DatagramSocket ds;
ReceiveThread(DatagramSocket ds){
this.ds = ds;
}
public void run(){
try{
//创建服务监听10001端口
while(true){
//定义字节缓冲区,给数据包用于接收数据
byte[] buf = new byte[1024*64];
//定义数据包接收数据
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//将接收到的数据放入数据包中
ds.receive(dp);
//获取ip
String ip = dp.getAddress().getHostAddress();
//获取数据
String data = new String(buf,0,dp.getLength());
//打印
System.out.println(ip+":"+data);
}
}
catch(Exception e){
System.out.println("接收端失败");
}
}
}
Socket和ServerSocket:TCP传输。
1.建立客户端和服务端。
2.建立连接后,通过Socket的IO流进行数据的传输。
3.关闭Socket。
同样,客户端与服务器端是两个独立的应用程序。
TCP分为客户端和服务端:
Socket:对应的是客户端。通过查阅Socket对象,发现该对象建立时,就可以去连接指定主机,因为TCP是面向连接的,所以建立Socket服务时,就要先有服务端存在,并连接成功。形成通路后,在该通道进行数据的传输。
ServerSocket:对应的是服务端。
练习:给服务端发送一个文本数据。
客户端:
import java.net.*;
import java.io.*;
class Demo1
{
public static void main(String []args) throws Exception{
//创建客户端的Socket服务,指定目的主机和端口。
Socket s = new Socket(InetAddress.getByName("192.168.0.102"), 10001);
//为了发送数据,应该获取Socket对象中的输出流
OutputStream out = s.getOutputStream();
//写入数据到服务端
out.write("TCP,我来了".getBytes());
//关闭资源
s.close();
}
}
练习:定义端点接收数据,并打印在控制台上。
服务端:
1.建立服务端的Socket服务ServerSocket,并监听一个端口。
2.获取连接过来的客户端对象,通过ServerSocket的accept方法。accept是一个阻塞式方法,没有连接就会等待。
3.客户端如果发来数据,那么服务端服务端要使用对应的客户端对象才能获取到该客户端对象的读取流来读取发过来的数据,并打印到控制台。
4.关闭服务端(可选)。
import java.net.*;
import java.io.*;
class Demo2
{
public static void main(String []args) throws Exception{
//建立服务端Socket服务,并监听10001端口。
ServerSocket ss = new ServerSocket(10001);
//通过accept方法获取连接过来的客户端对象。
Socket s = ss.accept();
//连接上客户端后,打印其IP地址。
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"连接成功");
//获取客户端发送过来的数据,那么服务端要使用客户端对象的读取流来读取数据。
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
//关闭客户端
s.close();
ss.close();
}
}
练习:TCP传输的客户端和服务端的互访。
客户端:
import java.net.*;
import java.io.*;
class Demo1
{
public static void main(String []args) throws Exception{
Socket s = new Socket(InetAddress.getByName("192.168.0.102"),10001);
OutputStream out = s.getOutputStream();
out.write("你好服务端".getBytes());
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
s.close();
}
}
服务端:
import java.net.*;
import java.io.*;
class Demo2
{
public static void main(String []args) throws Exception{
ServerSocket ss = new ServerSocket(10001);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....连接成功");
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = len = in.read(buf);
System.out.println(new String(buf,0,len));
OutputStream out = s.getOutputStream();
out.write("你也好客户端".getBytes());
s.close();
ss.close();
}
}
客户端结果:
服务端结果:
练习:TCP复制文件
客户端:
import java.net.*;
import java.io.*;
class Demo1
{
public static void main(String []args) throws Exception{
Socket s = new Socket(InetAddress.getByName("192.168.0.102"),10001);
BufferedReader br = new BufferedReader(new FileReader("Demo1.java"));
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line = br.readLine())!=null){
pw.println(line);
}
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = null;
while((str = bufr.readLine())!=null){
System.out.println(str);
}
br.close();
s.close();
}
}
服务端:import java.net.*;
import java.io.*;
class Demo2
{
public static void main(String []args) throws Exception{
ServerSocket ss = new ServerSocket(10001);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....连接成功");
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(new FileWriter("copy.txt"),true);
String line = null;
while((line = br.readLine())!=null){
pw.println(line);
}
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("上传成功");
pw.close();
s.close();
ss.close();
}
}
客户端结果:
处于等待状态。
服务端结果:
也处于等待状态。

答:因为没有结束标记。在客户端将文档写完出去以后,没有结束标记告知服务端停止readLine,因为readLine是阻塞式的。所以服务端等待。而客户端也想获得Socket的读取流读取返回的数据,服务端有没有释放,所以客户端也在等待。
解决办法:当客户端写完数据以后,Socket对象调用shutdownOutput方法,告知服务端,文件已经写完,然后readLine释放,由客户端获得读取权。
改进:
import java.net.*;
import java.io.*;
class Demo1
{
public static void main(String []args) throws Exception{
Socket s = new Socket(InetAddress.getByName("192.168.0.102"),10001);
BufferedReader br = new BufferedReader(new FileReader("Demo1.java"));
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line = br.readLine())!=null){
pw.println(line);
}
s.shutdownOutput();
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = null;
while((str = bufr.readLine())!=null){
System.out.println(str);
}
br.close();
s.close();
}
}
客户端结果:
服务端结果: