- 一、InetAddress和InetSocketAddress
- 二、套接字(Socket/DatagramSocket)
- 1、基于TCP的网络编程
- 2、基于UDP的网络编程
InetAddress:Java中封装IP的一个类;
InetSocketAddress:封装IP,端口号
//InetAddress ia = new InetAddress(); //不能直接创建对象,因为InetAddress()被default修饰
InetAddress ia1 = InetAddress.getByName("localhost"); // 本机ip/localhost/127.0.0.1均代指本机ip地址
System.out.println(ia1);
InetAddress ia2 = InetAddress.getByName("www.baidu.com");//封装域名
System.out.println(ia2);
System.out.println(ia2.getHostName()); //获取域名
System.out.println(ia2.getHostAddress()); //获取ip地址
InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 8080);
System.out.println(isa);
System.out.println(isa.getHostName());
System.out.println(isa.getPort());
InetAddress ia = isa.getAddress();
System.out.println(ia.getHostName());
System.out.println(ia.getHostAddress());
二、套接字(Socket/DatagramSocket)
TCP:
客户端:Socket 程序感觉的是输入/出流
服务器端:ServerSocket 程序感觉的是输入/出流
客户端和服务器端地位是不平等的,服务器端需要一直等待接收数据
UDP:
发送方:DatagramSocket 发送:数据包 DatagramPacket
接收方:DatagramSocket 接收:数据包 DatagramPacket
发送方和接收方地位是平等的
1、基于TCP的网络编程
(1)模拟客户端和服务器端双向通信
客户端(步骤):创建套接字,使用输出流向外发数据,使用输入流接收数据,关闭流和网络资源;
服务器(步骤):创建套接字,等待客户端发数据,使用输入流接收数据,使用输出流发送数据,关闭流和网络资源。
代码示例:
public class ClientTest {
public static void main(String[] args) throws IOException {
//1.创建套接字,需指定服务器端ip和端口号
Socket s = new Socket("127.0.0.1", 8888);
//2.向外发送数据-->使用输出流
OutputStream os = s.getOutputStream(); //利用OutputStream就可以向外发送数据了,但没有发送String的方法
DataOutputStream dos = new DataOutputStream(os); //因此,又套了一个处理流:DataOutputStream
dos.writeUTF("你好,我是客户端!");
//接收服务器发回的信息-->输入流
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
System.out.println("服务器发来的信息为:"+dis.readUTF());
//3.关闭流,关闭网络资源
dis.close();
dos.close();
s.close();
}
}
public class ServerTest {
public static void main(String[] args) throws IOException {
//1.创建套接字,指定服务器的端口号
ServerSocket ss = new ServerSocket(8888);
//2.等待客户端发来信息
Socket s = ss.accept(); //是一个阻塞方法:等待接收客户端的数据,接收到了就继续向下执行
//accept()返回是一个Socket,这个Socket就是客户端的Socket
//接收到这个Socket之后,客户端和服务器才真正产生连接,才可以通信
//3.接收信息
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
String str = dis.readUTF();
System.out.println("客户端发来的数据为:"+str);
//向客户端发信息-->输出流
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好,我是服务器,我收到你的请求了!");
//4.关闭流,关闭网络资源
dos.close();
dis.close();
s.close();
ss.close();
}
}
(2)模拟用户登录
改进的要点:
1、将数据接收/发送改为对象流,用于接收/发送用户名和密码封装成的一个对象;
2、加入完整的处理异常方式(try-catch方式);
代码示例:
User:封装一个用户的用户名和密码
public class User implements Serializable {
private String account;
private String pwd;
public User(String account, String pwd) {
this.account = account;
this.pwd = pwd;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
客户端:
public class ClientTest {
public static void main(String[] args){
Socket s = null;
OutputStream os = null;
ObjectOutputStream oos = null;
InputStream is = null;
DataInputStream dis = null;
try {
s = new Socket("127.0.0.1", 8888);
Scanner sc = new Scanner(System.in);
System.out.println("请输入账户:");
String account = sc.next();
System.out.println("请输入密码:");
String pwd = sc.next();
User user = new User(account, pwd);
os = s.getOutputStream();
oos = new ObjectOutputStream(os);
oos.writeObject(user);
is = s.getInputStream();
dis = new DataInputStream(is);
System.out.println("服务器发来的信息为:"+dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(dis != null)
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(oos != null)
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(s != null)
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器:
public class ServerTest {
public static void main(String[] args){
ServerSocket ss = null;
Socket s = null;
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
DataOutputStream dos = null;
try {
ss = new ServerSocket(8888);
s = ss.accept();
is = s.getInputStream();
ois = new ObjectInputStream(is);
User user = (User)ois.readObject();
os = s.getOutputStream();
dos = new DataOutputStream(os);
dos.writeUTF("登陆成功!");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if(dos != null)
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(ois != null)
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(s != null)
s.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(ss != null)
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3)进一步改进
多线程接收用户请求:解决客户端程序结束,服务器端也跟着结束的问题,服务器应一直监听是否有客户端发送数据。
客户端和User类都不需要进行修改。
代码示例:
服务器:
public class ServerTest {
public static void main(String[] args){
ServerSocket ss = null;
Socket s = null;
int count = 0; //记录请求的客户端数量
try {
ss = new ServerSocket(8888);
while (true){ //服务器一直监听客户端是否发送数据
s = ss.accept();
//每次过来的客户端的请求,靠线程处理
new Thread(new ServerThread(s)).start();
System.out.println("当前是第"+(++count)+"个用户访问!对应的用户是:"+s.getInetAddress());
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
线程:
public class ServerThread implements Runnable{ //处理客户端的请求
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
DataOutputStream dos = null;
Socket s = null;
public ServerThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
is = s.getInputStream();
ois = new ObjectInputStream(is);
User user = (User)ois.readObject();
os = s.getOutputStream();
dos = new DataOutputStream(os);
dos.writeUTF("登陆成功!");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
if(dos != null)
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(ois != null)
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、基于UDP的网络编程
(1)模拟双向通信
发送端(步骤):准备套接字(指定发送方的端口号),准备数据包(需要转为byte[]数组),DatagramPacket(包含四个参数),回复,关闭资源;
接收端(步骤):准备套接字(指定接收端的端口号),准备数据包(用来接收对方传过来的数据包),DatagramPacket(接收对方的数据包填充到dp数据包中),取数据,回复,关闭资源。
发送端:
public class SendTest {
public static void main(String[] args){
System.out.println("学生上线了。");
//1.准备套接字:指定发送方的端口号
DatagramSocket ds = null;
try {
ds = new DatagramSocket(8888);
//2.准备数据包,需要转为byte[]数组
Scanner sc = new Scanner(System.in);
System.out.print("学生说:");
String str = sc.next();
byte[] b = str.getBytes();
//3.DatagramPacket需要四个参数:传送的字节数组、字节数组的长度、接收方的ip、接收方的端口号
DatagramPacket dp = new DatagramPacket(b, b.length, InetAddress.getByName("localhost"), 9999);
ds.send(dp);
//回复
byte[] b1= new byte[1024];
DatagramPacket dp2 = new DatagramPacket(b1, b1.length);
ds.receive(dp2);
byte[] data = dp2.getData();
String s = new String(data, 0, dp2.getLength());
System.out.println("老师对我说:"+s);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源
if(ds != null)
ds.close();
}
}
}
接收端:
public class ReceiveTest {
public static void main(String[] args){
System.out.println("老师上线了。");
//1.创建套接字:指定接收方的端口
DatagramSocket ds = null;
try {
ds = new DatagramSocket(9999);
//2.有一个空的数据包,打算用来接收对方传过来的数据包
byte[] b= new byte[1024];
DatagramPacket dp = new DatagramPacket(b, b.length);
//3.接收对方的数据包,然后填充到dp数据包中
ds.receive(dp);
//4.取数据
byte[] data = dp.getData();
String s = new String(data, 0, dp.getLength()); //数据包中的有效长度
System.out.println("学生对我说:"+s);
//回复
Scanner sc = new Scanner(System.in);
System.out.print("老师说:");
String str = sc.next();
byte[] b1 = str.getBytes();
DatagramPacket dp2 = new DatagramPacket(b1, b1.length, InetAddress.getByName("localhost"), 8888);
ds.send(dp2);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.关闭资源
if(ds != null)
ds.close();
}
}
}
(2)进一步改进
上述代码中,发送方和接收方各发送一条数据即结束,为了实现完整的正常的通信(可以一直对话),做出如下改进。
1、对双方的发送和接收行为进行循环,使用while(true){…};
2、当发送端发送"byebye"为终止交互信号。
发送端:
public class SendTest {
public static void main(String[] args){
System.out.println("学生上线了。");
//1.准备套接字:指定发送方的端口号
DatagramSocket ds = null;
try {
ds = new DatagramSocket(8888);
while(true){
//2.准备数据包,需要转为byte[]数组
Scanner sc = new Scanner(System.in);
System.out.print("学生说:");
String str = sc.next();
byte[] b = str.getBytes();
//3.DatagramPacket需要四个参数:传送的字节数组、字节数组的长度、接收方的ip、接收方的端口号
DatagramPacket dp = new DatagramPacket(b, b.length, InetAddress.getByName("localhost"), 9999);
ds.send(dp);
if(str.equals("byebye")) {
System.out.println("学生下线。");
break;
}
//回复
byte[] b1= new byte[1024];
DatagramPacket dp2 = new DatagramPacket(b1, b1.length);
ds.receive(dp2);
byte[] data = dp2.getData();
String s = new String(data, 0, dp2.getLength());
System.out.println("老师对我说:"+s);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源
if(ds != null)
ds.close();
}
}
}
接收端:
public class ReceiveTest {
public static void main(String[] args){
System.out.println("老师上线了。");
//1.创建套接字:指定接收方的端口
DatagramSocket ds = null;
try {
ds = new DatagramSocket(9999);
while(true){
//2.有一个空的数据包,打算用来接收对方传过来的数据包
byte[] b= new byte[1024];
DatagramPacket dp = new DatagramPacket(b, b.length);
//3.接收对方的数据包,然后填充到dp数据包中
ds.receive(dp);
//4.取数据
byte[] data = dp.getData();
String s = new String(data, 0, dp.getLength()); //数据包中的有效长度
System.out.println("学生对我说:"+s);
if(s.equals("byebye")){
System.out.println("学生下线,老师也下线了。");
break;
}
//回复
Scanner sc = new Scanner(System.in);
System.out.print("老师说:");
String str = sc.next();
byte[] b1 = str.getBytes();
DatagramPacket dp2 = new DatagramPacket(b1, b1.length, InetAddress.getByName("localhost"), 8888);
ds.send(dp2);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.关闭资源
if(ds != null)
ds.close();
}
}
}



