网络模型:
OSL参考模型和TCP/IP参考模型
网络通讯的三要素:
IP地址:网络中设备的标识,不易记忆可以用主机名,本机回环地址:127.0.0.1 主机名:localhost
端口号:用于标识进程的逻辑地址,不同进程的标识,有效端口号范围0-65535,0-1024的端口一般被系统程序占用
传输协议:通信的规则,常用协议TCP、UDP
UDP:可用于聊天工具
1、面向无连接
2、发的包是有限制的,不超过64k
详述:将数据及源和目的封装成数据包,不需要建立连接,每个数据包的大小限制在64k内,因为无连接,是不可靠的协议,不需要建立连接速度快
TCP:可用于下载
建立连接,形成传输数据的通道,在建立连接中进行大数据量传输,通过三次握手完成连接,是可靠协议,必须建立连接,效率会稍低
国际组织定义了通用协议:TCP/IP
OSI参考模型由上到下依次为:
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
TCP/IP参考模型由上到下依次为:
应用层、传输层、网际层、网络层
Scoket(网络编程)
Scoket就是为网络服务提供的一种机制,通信的两端都有Scoket,网络通信其实就是Scoket之间的通信,数据在两个Scoket间通过IO传输
UDP传输:
DatagramSocket与DatagramPacket
流程为:
建立发送端
建立接收端
建立数据包
调用Scoket的发送接收方法
关闭Socket
发送端与接收端是两个独立的运行程序
练习:
通过UDP传输方式将一段文字数据发送出去
思路:
1、建立UDPSocket服务
2、提供数据,并将数据封装到数据包中
3、通过Socket服务的发送功能,将数据包发送出去
4、关闭资源
代码如下:
class UdpSend
{
public static void main(String[] args)throws UnknownHostException,SocketException,IOException
{
DatagramSocket ds=new DatagramSocket(8888);//通过DatagramSocket对象穿件UDP服务
byte[] buf="第一条传输信息".getBytes();
DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("zjd-PC"),10000);//确定数据并封装成包
ds.send(dp);//通过Socket服务将已有的数据包发送出去
ds.close();
}
}
其中DatagramSocket中的端口号如果不设定的话系统将自动给他分配,当设定的话,一定不能和DatagramPacket中的端口号相同
通过UDP接收数据并处理:
思路:
1、创建Socket服务
2、创建接收数据的数据包
3、通过receive方法将接收的数据方法数据包中
4、通过数据包的特有功能将其数据取出并打印在控制台上
5、关闭资源
代码如下:
class UdpRece
{
public static void main(String[] args)throws UnknownHostException,SocketException,IOException
{
DatagramSocket ds=new DatagramSocket(10000);//创建Socket服务
while(true)
{
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,buf.length);//定义数据包用于存储数据
ds.receive(dp);//将接收的数据存到数据包中
String ip=dp.getAddress().getHostAddress();//读取主机Ip地址
String data=new String(dp.getData(),0,dp.getLength());//通过数据包的特有功能提取出数据
int port=dp.getPort();//获得端口号
System.out.println(ip+":"+data+":"+port);//打印数据
}
ds.close();
}
}
练习,制作简单QQ
import java.net.*;
import java.io.*;
class Send implements Runnable
{
private DatagramSocket ds;
Send(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
while(true)
{
try{
BufferedReader bis=new BufferedReader(new InputStreamReader(System.in));//创建字符输入流
String line=null;
while((line=bis.readLine())!=null)//读取数据
{
byte[] buf=line.getBytes();//将字符串转化为字节数组
DatagramPacket dp=new DatagramPacket(buf,0,buf.length,InetAddress.getByName("zjd-PC"),10000);//将字节数据打包
ds.send(dp);//发送
}
ds.close();//关闭流
}
catch(Exception e){}
}
}
}
class Receive implements Runnable
{
private DatagramSocket ds;
Receive(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
while(true)
{
try{
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());//这个地方一定不能写成dp.getData().lenght
System.out.println(IP+":"+data);
}
catch(Exception e){}
}
}
}
class MyQQ
{
public static void main(String[] args) throws Exception
{
DatagramSocket sendDs=new DatagramSocket();//创建发送Socket服务
DatagramSocket receDs=new DatagramSocket(10000);//创建接收Socket服务
new Thread(new Send(sendDs)).start();//启动发送线程
new Thread(new Receive(receDs)).start();//启动接收线程
}
}
上面的程序你没有对异常正确处理,应该catch三个异常分别为UnknownException、IOException、SocketException
TCP传输
Socket和ServerSocket建立客户端和服务器端,建立连接后,通过Socket中的IO流进行数据的传输,然后关闭Socket
TCP分客户端和服务端
客户端对应的是Socket
服务器端对应的是ServerSocket
客户端:
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机,因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功,形成通路后,在该通路上进行数据传输
则客户端创建步骤为:
1、创建Socket服务,并指定要连接的主机名和端口
2、为了发送数据应该获取Socket流中的输出流
3、关闭资源
class TCPClient
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket("zjd-Pc",10000);//创建客户端的Socket服务
OutputStream out=s.getOutputStream();//为了发送数据应该获得socket的输出流
out.write("wo lai le".getBytes());
s.close();
}
}
服务端:定义端口接收数据,并打印在控制台上
1、建立服务端的Socket服务,ServerSocket();并监听一个端口
2、获取链接过来的客户端对象
通过ServerSocket的accept方法获得,如果没有连接程序就会等待,这个方法是堵塞式方法
3、客户端发过来数据,那么服务端要使用对应的客户端对象,并获取的该客户端对象的读取流来读取发过来的数据
4、关闭服务端(可选)
class TCPServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(10000);//创建ServerSocket服务
while(true)
{
Socket s=ss.accept();//获取链接过来的客户端对象
String IP=s.getInetAddress().getHostAddress();//获取客户端IP
System.out.println(IP+"......connect");
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();//关闭客户端
}
}
}
实现TCP相互之间通信,将客户端传入的字母,在服务端将其变为大写返回给客户端
import java.io.*;
import java.net.*;
class FinalTcpClient
{
public static void main(String[] args) throws Exception
{
BufferedReader bufIn=new BufferedReader(new InputStreamReader(System.in));//从键盘读取数据
Socket s=new Socket("zjd-PC",10000);
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//获取Socket的输出流
BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));//获取Socket的输入流
String line=null;
while((line=bufIn.readLine())!=null)
{
if("over".equals(line))
break;
out.write(line);
out.newLine();//这个地方必须加换行和刷新否则服务端接收不到,因为不刷新数据在缓冲区中,服务端无法读取,
out.flush();//用因为readLine是通过换行符返回值的,所以一定要加newLine否则无法返回值
line=in.readLine();//读取服务端发过来的数据
System.out.println(line);
}
bufIn.close();
s.close();
}
}
class FinalTcpServer
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(10000);
Socket s=ss.accept();//通过ServerSocket的accept方法获得客户端的Socket对象
BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));//接收客户端传过来的流
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//返回给客户端的流
String line=null;
while((line=in.readLine())!=null)
{
System.out.println(line);
out.write(line.toUpperCase());//将客户端的信息变为大写并传给客户端
out.newLine();
out.flush();
}
s.close();
ss.close();
}
}
上传视频代码,运用了多线程
import java.io.*;
import java.net.*;
class TcpPic
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(10000);//服务端一定要先执行
Thread.sleep(2000);//是为了让服务端连接上
Socket s=new Socket("zjd-PC",10000);
new Thread(new Server(ss)).start();
new Thread(new Client(s)).start();
}
}
class Client implements Runnable
{
private Socket s;
Client(Socket s)
{
this.s=s;
}
public void run()
{
try
{
BufferedInputStream bufr=new BufferedInputStream(new FileInputStream("1.wmv"));//创建一个文件读取缓冲区
BufferedOutputStream out=new BufferedOutputStream(s.getOutputStream());//定义一个获取socket输出流的缓冲区
BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));//定义一个获取Socket输入流的缓冲区
byte[] buf=new byte[1024*1024];
int len=0;
while((len=bufr.read(buf))!=-1)
{
out.write(buf,0,len);
out.flush();//防止断电等特殊情况是缓冲区的数据丢失
}
s.shutdownOutput();//定义结束标志,如果无此标记本程序将无法停止
String str=in.readLine();//读取服务端发送回来的数据
System.out.println(str);
bufr.close();
s.close();
}
catch(Exception e){}
}
}
class Server implements Runnable
{
private ServerSocket ss;
Server(ServerSocket ss)
{
this.ss=ss;
}
public void run()
{
try
{
System.out.println("服务端已开启");
Socket s=ss.accept();//获取socket对象
BufferedInputStream in=new BufferedInputStream(s.getInputStream());//创建获取Socket输入流的缓冲区
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//创建获取Socket输出流动缓冲区
BufferedOutputStream bufOut=new BufferedOutputStream(new FileOutputStream("TCP.wmv"));//创建输出文件的缓冲区
byte[] buf=new byte[1024*1024];
int len=0;
while((len=in.read(buf))!=-1)
{
bufOut.write(buf,0,len);
bufOut.flush();
}
out.write("已上传成功");//给服务端发送数据
out.newLine();
out.flush();
bufOut.close();
s.close();
ss.close();
}
catch(Exception e){}
}
}
对于上面程序最容易出现错误的地方就是服务端在接受完文件后不能结束,原因是没有结束read读入,为了结束在客户端写入socket的shutdownOutputStream()方法,就可以结束,还有一种方法就是定义时间戳,例如在服务端开始传入数据之间定义一个System.currentTimeMillis()并将这个数据传给服务端,等客户端传完文件之后,再讲这个时间戳再传一次,然后在服务端进行判断来实现程序结束
练习:TCP多线程并发执行文件传输程序
import java.io.*;
import java.net.*;
class PicChuanshu
{
public static void main(String[] args)
{
new Thread(new Client()).start();//开客户端线程模拟多人同时上传文件
new Thread(new Client()).start();
new Thread(new Client()).start();
new Thread(new Client()).start();
new Thread(new Client()).start();
new Thread(new Client()).start();
new Thread(new Client()).start();
}
}
class Client implements Runnable
{
public void run()
{
try
{
Socket s=new Socket("zjd-PC",10000);//建立客户端Socket服务
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("1.wmv"));//创建读取文件缓冲区
BufferedOutputStream out=new BufferedOutputStream(s.getOutputStream());//创建接收Socket对象输出流的缓冲区
BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));//穿件Socket对象输入流的的缓冲区
byte[] buf=new byte[1024*1024];//创建存储字节的字节数组
int len=0;
while((len=bis.read(buf))!=-1)
{
out.write(buf,0,len);
out.flush();
}
s.shutdownOutput();//标记结束,一定要加这句,否则无法结束程序
String str=in.readLine();//读取服务端发过来的信息
System.out.println(str);
bis.close();
s.close();
}
catch(Exception e)
{
}
}
}
class ServerThread implements Runnable
{
private Socket s;
ServerThread(Socket s)
{
this.s=s;
}
public void run()
{
String Ip=s.getInetAddress().getHostAddress();
try
{
System.out.println(Ip+".......connected");
int count=0;
BufferedInputStream in=new BufferedInputStream(s.getInputStream());//创建接收Socket的输入流缓冲区
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//创建Socket输出流的缓冲区
File file=null;
file=new File("chuan\\aaa("+(count)+").wmv");
synchronized(ServerThread.class)//加锁防止同一时间生成同一文件而发生覆盖,导致信息丢失
{
while(file.exists())
{
file=new File("chuan\\aaa("+(count++)+").wmv");
}
}
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(file));//将信息写到生成的文件上
byte[] buf=new byte[1024*1024];
int len=0;
while((len=in.read(buf))!=-1)
{
bos.write(buf,0,len);
}
out.write("上传成功");
out.newLine();
out.flush();
s.close();
bos.close();
}
catch(Exception e)
{
System.out.println(Ip+"上传失败");
}
}
}
class Server
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(10000);
while(true)
{
Socket s=ss.accept();
new Thread(new ServerThread(s)).start();//启动服务端线程
}
}
}
对于本题服务端:
这个服务端有个局限性,当A客户端连接以后,被服务端获取到,服务端执行具体的流程。这是B客户端连接,只有等待,因为服务端还没有处理完A客户端的请求,还有循环回来执行下一次accept方法,所以暂时获取不到B客户端对象,那么可以同时并发访问服务端,那么服务端最好就是将每个客户端封装到一个单独的线程中,这样就可以同时处理多个客户端的请求
如何定义线程呢?
只要明确了每个客户端要在服务端执行的代码即可,将该代码存入run方法中
客户端和服务端都在莫名的等待为什么呢?
因为客户端和服务端都与堵塞方法,这些方法在没有读到结束标记时就会一直等而导致两端都在等待
为什么客户端结束了服务端也关闭了?
因为客户端关闭的代码s.close()的返回值是-1所以服务端也关了