网络编程
- 网络模型
- OSI参考模型
- TCP/IP参考模型
- 网络通讯要素
- IP地址
- 端口号
- 传输协议
在一个依赖网络运行的程序,比如说QQ聊天软件,
1,我要给张三发一条消息,我先要在网络上找到张三所用的这台主机,也就是找到他所在的IP地址。
2,数据要发送到对方的应用程序上,为了标识这些应用程序,
所以给这些网络应用程序的用数字进行了标识
为了方便称呼这个数字,叫做端口,这样的端口是逻辑端口
3,为了让我发过去的数据张三所在的机器能能够处理,所以我俩必须先定义一个通信规则,这个通讯规则称为协议
国际组织定义了通用协议TCP/IP
本地回环地址127.0.0.1
是任何一台计算机都有的自身的IP地址。
配置端口的数字范围是在0~65515
系统程序的端口一般为0~1024
记住几个端口:
web服务:80
Tomcat服务器:8080
MySQL:3306
网络通讯要素
- IP地址:InetAddress
- 网络中设备的标识
- 不易记忆,可用主机名
- 本地回环地址:127.0.0.1 主机名:localhost
- 端口号
- 用于标识进程的逻辑地址,不同进程的标识
- 有效端口:065535,其中01024系统使用或保留端口
- 传输协议
- 通讯的规则
- 常见协议:TCP,UDP
示例获取电脑主机的名称和地址
在java的net包中封装了IP地址的类的对象,InetAddress
//获取电脑的主机IP地址和名称
import java.net.*;
public class IPDemo {
public static void main(String[] args) throws Exception
{
//获取本地主机的IP对象
InetAddress i= InetAddress.getLocalHost();
System.out.println(i.toString());
//单独的获取本主机的IP地址
System.out.println("Address = "+i.getHostAddress());
//单独的获取本地主机的主机名称
System.out.println("name = "+i.getHostName());
}
}
运行结果:
以上是获取本地主机的主机名和IP地址,也可以获取指定主机的主机名和IP地址的
//获取指定主机IP地址和名称
import java.net.*;
public class IPDemo {
public static void main(String[] args) throws Exception
{
//获取指定主机的IP对象
InetAddress i= InetAddress.getByName("www.baidu.com");//这里获取的是百度的主机IP
//单独的获取指定主机的IP地址
System.out.println("Address = "+i.getHostAddress());
//单独的获取指定主机的主机名称
System.out.println("name = "+i.getHostName());
}
}
运行结果为:
TCP/UDP
- UDP
- 将数据及源和目的封装成数据包中,不需要建立连接
- 每个数据包的大小限制在64k内
- 因无连接,是不可靠协议
- 不需要建立连接,速度快
- TCP
- 建立连接,形成传输数据的通道
- 在连接中进行大数据量传输
- 通过三次握手完成连接,是可靠协议
- 必须建立连接,效率会稍低。
Socket
- Socket
- Socket就是为网络服务提供的一种机制
- 通信的两端都有Socket
- 网路通信其实就是Socket间的通信
- 数据在两个Socket间通过IO传输
那么现在创建一个UDP传输的演示效果:
传输需要先建立服务,也就是Socket服务
//UDP发送端
import java.net.*;
/*
思路:
1,建立udpsocket服务。
2,提供数据,并将数据封装到数据包中
3,通过socket服务的发送功能,将数据包发出去
4,关闭资源
*/
public class UDPSend {
public static void main(String[] args) throws Exception
{
//先建立一个数据传输服务,并且指定发送端的端口号
DatagramSocket ds = new DatagramSocket(12340);
//建立一个数据包,要传数据先将数据打包,,,将数据指定到一个数组,数组长度,要发送的地址,端口号
byte [] buf ="接收端你好".getBytes();
DatagramPacket dp =
new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);
//使用传输服务将数据发送出去
ds.send(dp);
//关闭资源
ds.close();
}
}
//UDP传输接收端
import java.net.*;
/*
思路:
1,定义udpsocket服务,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识
方便于明确哪些数据过来该应用程序可以处理
2,定义一个数据包,因为要存储接收到的字节数据
因为数据包对象中有更多功能可以提取字节数据中的不同数据信息
3,通过socket服务的receive方法将受到的数据存入已经定义好的数据包中
4,通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
5,关闭资源
*/
public class UDPRec {
public static void main(String[] args) throws Exception
{
//同样先创建一个数据传输服务
DatagramSocket ds = new DatagramSocket(10000);
//再定义一个数据包,用来接收数据时存放发过来的数据包,里面有操作数据包中数据的方法
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//接收数据
ds.receive(dp);
//获取数据中的信息
String Address = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
//将数据打印出来
System.out.println(Address+".."+text+".."+port);
//关闭资源
ds.close();
}
}
运行结果:
因为UDP传输是面向无连接的,所以如果先运行发送端的话,数据就会丢失,
应该先打开接收端,没有数据收到的话,接收端会处于阻塞状态,一直到接收到数据。
练习二:使用键盘获取数据作为发送的数据
//UDP发送端2,使用键盘录入的方式发送数据
import java.net.*;
import java.io.*;
public class UDPSend2 {
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket(12341);
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = bufr.readLine())!=null)
{
//如果读到886就跳出循环,关闭资源
if("886".equals(line))
break;
//读到的数据装进一个数组中
byte[] buf = line.getBytes();
//然后将数组中的数据装进包里
DatagramPacket dp =
new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10001);
ds.send(dp);
}
ds.close();
bufr.close();
}
}
//想要实现像对讲机一样,接收端常开的,发送端一发送数据,这里就能接收到
import java.net.*;
public class UDPRec2 {
public static void main(String[] args) throws Exception
{
//创建好一个端口,常开着,只要过来数据,直接读就可以了,
//不用将服务器再重新开启一编,所以这里讲服务器定义在外边
DatagramSocket ds = new DatagramSocket(10001);
while (true)
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String Address = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
System.out.println(Address+".."+text+".."+port);
}
}
}
运行结果:
练习:将发送端和接收端用两个线程来操作,也就是说在同一个窗口中实现发送和接收消息。
//想写一个聊天软件,在同一个窗口中同时实现发送数据和接收数据
//这就用到了多线程,一条线程负责发送,一条线程负责接收消息
//因为两个线程执行的指令不一样,所以要封装两个run方法
//因为read方法和键盘录入都是阻塞式方法,只要我不操作,两个线程都会在等待,
//当收到数据的时候,接收端就先输出数据
//当遇到键盘录入的时候,就输出我自己键盘录入的消息
import java.io.*;
import java.net.*;
class Send implements Runnable
{
//因为发送数据需要用到服务端,所以这里直接给一个服务端
private DatagramSocket ds;
Send(DatagramSocket ds)
{
this.ds = ds;
}
public void run()
{
try
{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = bufr.readLine())!=null)
{
if("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10002);
ds.send(dp);
}
bufr.close();
ds.close();
}
catch (Exception e)
{
throw new RuntimeException("发送端异常");
}
}
}
class Rece implements Runnable
{
private DatagramSocket ds;
Rece(DatagramSocket ds)
{
this.ds = ds;
}
public void run()
{
try
{
while (true)
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"..."+data);
}
}
catch (Exception e)
{
throw new RuntimeException("接收端异常");
}
}
}
public class ChatDemo {
public static void main(String[] args) throws Exception
{
DatagramSocket sendDs = new DatagramSocket();
DatagramSocket receDs = new DatagramSocket(10002);
new Thread(new Send(sendDs)).start();
new Thread(new Rece(receDs)).start();
}
}
运行结果:
TCP传输
- Socket和ServerSocket
- 建立客户端和服务器端
- 建立连接后,通过Socket中的IO流进行数据的传输
- 关闭Socket
- 同样客户端与服务器端是两个独立的程序
TCP分客户端和服务端
客户端对应的对象是Socket
服务器端对应的对象是ServerSocket
使用TCP传输,从客户端写一段数据,然后由服务端读取到数据,然后打印在控制台上:
//TCP客户端
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws Exception
{
//先创建客户端对象,指定服务端地址和端口号
Socket s = new Socket("127.0.0.1",10003);
//从客户端对象中可以获取到对应的输出流
OutputStream out = s.getOutputStream();
//使用输出流往出写数据
byte[] buf = "tcp wo lai le".getBytes();
out.write(buf);
s.close();
}
}
//TCP 服务端
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) throws Exception
{
//创建服务端对象
ServerSocket ss = new ServerSocket(10003);
//在服务端中获取到客户端对象
Socket s = ss.accept();
//再从客户端对象中获取到读取流,这样可以保证服务端发给本客户端的消息不会发给其他的客户端
InputStream in = s.getInputStream();
//开始读取数据
byte [] buf = new byte[1024];
int len = in.read(buf);
String data = new String(buf,0,len);
System.out.println(data);
//然后关闭资源
ss.close();
s.close();
}
}
运行结果:
想要实现TCP传输之间的客户端和服务器端的互访。
客户端给服务器端发送消息之后,服务器端给一个回应,说我收到了:
//实现TCP传输的互访,也就是说客户端给服务端发送一个数据,服务端爷要给客户端发送一条数据
import java.io.*;
import java.net.*;
public class TCPClient2 {
public static void main(String[] args) throws Exception
{
//服务端先写出去一段数据
Socket s = new Socket("192.168.0.104",10004);
System.out.println(s.getInetAddress().getHostAddress()+",,,,,conect");
OutputStream out = s.getOutputStream();
out.write("服务端,你好".getBytes());
//写完之后再操作读取数据
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
//关闭资源
s.close();
}
}
//接收到信息之后,给客户端写出去一条消息
import java.io.*;
import java.net.*;
public class TCPServer2 {
public static void main(String[] args) throws Exception
{
//先读取客户端发来的信息
ServerSocket ss = new ServerSocket(10004);
Socket s = ss.accept();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
//读取完了之后开始写给客户端消息
OutputStream out = s.getOutputStream();
out.write("收到,你也好".getBytes());
//可选操作,关闭资源
ss.close();
s.close();
}
}
运行结果为:
那么接下来要设计一个客户端给服务端发送字符数据,然后由服务端换成该数据的大写,再发送给客户端,由客户端读取出来,然后打印在控制台上:
//设计一个客户端给服务端发送字符数据,然后由服务端换成该数据的大写,
//再发送给客户端,由客户端读取出来,然后打印在控制台上:
import java.io.*;
import java.net.*;
class TransClient {
public static void main(String[] args) throws Exception
{
Socket s = new Socket("192.168.0.104",10005);
//获取键盘录入数据的流
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//获取Socket输出流,用来给服务端写数据的
BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//获取到Socket输入流,用来接收服务端的数据的
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//开始读取数据
String line = null;
while((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
//将键盘读取到的数据,给服务器端发过去
bufOut.write(line);//因为write方法是不带结束符号写入的,而没有结束符的话,
//服务器端的readLine方法认为该行数据没有结束,所以一直在等,而不会读取出来数据
bufOut.newLine(); //所以这里自己加一个回车符
bufOut.flush(); //调用Buffered缓冲区的方法写数据是写在了缓冲区中,而没有写进流里边去
//所以这里要记得刷新一下
//每发送一次数据就接收回来一个数据,所以这里先来读取一下
String str = bufIn.readLine();
System.out.println("Server:...."+str);
}
s.close();
bufr.close();
}
}
class TransServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10005);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......conect");
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line = null;
while((line = bufIn.readLine())!=null)
{
//这里写数据和在客户端写数据的原理是一致的
bufOut.write(line.toUpperCase());
bufOut.newLine();
bufOut.flush();
}
s.close();
ss.close();
}
}
运行结果:
运行时发现,当输入over让客户端退出的时候,服务器端也自动退出了:
这是因为,Socket中有一个数字,当关闭Socket的时候,会自动返回一个-1,而当服务器端收到这个-1 的时候,就会自动关闭了。
在写代码中还发现,每次使用Socket的方法写入数据的时候都会写入数据,然后加一个换行,然后刷新一次,由此想到了,PrintWriter,参数传true的话,使用println方法可以自动进行换行和刷新。
所以可以修改代码如下:
//设计一个客户端给服务端发送字符数据,然后由服务端换成该数据的大写,
//再发送给客户端,由客户端读取出来,然后打印在控制台上:
import java.io.*;
import java.net.*;
class TransClient {
public static void main(String[] args) throws Exception
{
Socket s = new Socket("192.168.0.104",10005);
//获取键盘录入数据的流
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//获取Socket输出流,用来给服务端写数据的
PrintWriter bufOut = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
//获取到Socket输入流,用来接收服务端的数据的
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//开始读取数据
String line = null;
while((line = bufr.readLine())!=null)
{
if("over".equals(line))
break;
//将键盘读取到的数据,给服务器端发过去
/*
bufOut.write(line);//因为write方法是不带结束符号写入的,而没有结束符的话,
//服务器端的readLine方法认为该行数据没有结束,所以一直在等,而不会读取出来数据
bufOut.newLine(); //所以这里自己加一个回车符
bufOut.flush(); //调用Buffered缓冲区的方法写数据是写在了缓冲区中,而没有写进流里边去
//所以这里要记得刷新一下
*/
//换为下面这一句
bufOut.println(line);
//每发送一次数据就接收回来一个数据,所以这里先来读取一下
String str = bufIn.readLine();
System.out.println("Server:...."+str);
}
s.close();
bufr.close();
}
}
class TransServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10005);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......conect");
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter bufOut = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
String line = null;
while((line = bufIn.readLine())!=null)
{
//这里写数据和在客户端写数据的原理是一致的
/*
bufOut.write(line.toUpperCase());
bufOut.newLine();
bufOut.flush();
*/
bufOut.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
功能一样可以实现的。
练习:用TCP传输上传一个文件到客户端:
//想要从客户端上传一个文件给服务器端,
//上传成功后服务端给客户端发送一个数据,表示数据接收成功
import java.io.*;
import java.net.*;
class TextClient {
public static void main(String[] args) throws Exception
{
Socket s = new Socket("192.168.0.104",10006);
//源选中一个目标文件
BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));
//从Socket流中获取到输入输出流
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter bufOut = new PrintWriter(s.getOutputStream(),true);
//开始读取文件中的数据,将数据写入到输出流中,发送给服务器端
String line = null;
while((line = bufr.readLine())!=null)
{
bufOut.println(line);
}
//发送完之后 ,客户端等着接收服务器端发回来的消息并打印出来
String str = bufIn.readLine();
System.out.println(str);
s.close();
bufr.close();
}
}
class TextServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10006);
//只要一连接上就打印一次IP
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......conect");
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter bufOut = new PrintWriter(s.getOutputStream(),true);
PrintWriter bufw = new PrintWriter(new FileWriter("server.txt"),true);
//开始读取流中发送回来的数据
String line = null;
while((line = bufIn.readLine())!=null)
{
bufw.println(line);
}
//读完之后服务器端给客户端发送一个消息,上传成功
bufOut.println("上传成功");
ss.close();
s.close();
bufw.close();
}
}
运行结果发现,文件可以顺利上传,但是上传成功之后,服务端写的数据没有发送回来,而且两边都一直处在等待状态:
这是为什么呢?
因为客户端发送完数据之后就在等着服务器端发送数据,等着读取
而服务器端并不知道客户端发送完数据了,还在那里一直等着读取呢,
两边都在等,所以服务器端的数据就没有发回来,客户端也读取不到了。
解决方法是:在客户端写完数据之后,加上一个标记,告诉服务器端,我的数据写完了,
这个标记就是:shutdownOutput
然后只要在代码中加一句,s.shutdownOutput:意思是我写完了。
程序就可以正常结束了:
//想要从客户端上传一个文件给服务器端,
//上传成功后服务端给客户端发送一个数据,表示数据接收成功
import java.io.*;
import java.net.*;
class TextClient {
public static void main(String[] args) throws Exception
{
Socket s = new Socket("192.168.0.104",10006);
//源选中一个目标文件
BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));
//从Socket流中获取到输入输出流
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter bufOut = new PrintWriter(s.getOutputStream(),true);
//开始读取文件中的数据,将数据写入到输出流中,发送给服务器端
String line = null;
while((line = bufr.readLine())!=null)
{
bufOut.println(line);
}
s.shutdownOutput();
//发送完之后 ,客户端等着接收服务器端发回来的消息并打印出来
String str = bufIn.readLine();
System.out.println(str);
s.close();
bufr.close();
}
}
class TextServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10006);
//只要一连接上就打印一次IP
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......conect");
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter bufOut = new PrintWriter(s.getOutputStream(),true);
PrintWriter bufw = new PrintWriter(new FileWriter("server.txt"),true);
//开始读取流中发送回来的数据
String line = null;
while((line = bufIn.readLine())!=null)
{
bufw.println(line);
}
//读完之后服务器端给客户端发送一个消息,上传成功
bufOut.println("上传成功");
ss.close();
s.close();
bufw.close();
}
}
运行结果: