网络通讯的原理I
网络通讯就是数据传输,网络间的数据传输,网络通讯的模型和要素是关键。
要素1 IP 地址,IP地址就跟打电话一样IP地址就是你的电话号码,我想和你通讯首先要有你的IP,
要素2 有接受我的数据的软件你要是没软件的话就没办法接受。为了标示这些应用程序所以给这些程序都用数字进行了表示,QQ分配的数字比如是12345。
发信息的话就发给IP地址为1 的12345 的软件上,12345 叫做逻辑端口。首先端口就是不是物理接口,逻辑接口是纯软件的接口,端口是识别软件的关键。
IP地址就是大楼,逻辑端口就是屋子。
要素 3 定义通讯规则,这个规则成为协议,协议啥呢,就是咱们都按照同一种规则进行通讯,互联网上的机器在不停的通讯哥们玩的是韩服我玩的是日服为啥能通讯呢,因为我们都
遵守同样的规则,我们所有的通讯都被主服务区传递。国际上的通用通讯协议是TCP/IP 广域网和局域网都在使用这个协议。如果我现在只在局域网通讯的话能安装自己的协议
有IPX等其他的协议为局域网准备的。同协议的人才能互相联系,你要是不想让别人找到你可以用不一样的协议。
回来继续说IP
IP地址有4段最后一段绝对是255 以下的127.0.0.1 是你的机器的默认地址。有的局域网的地址是保留地址段,局域网的地址可以随便该,只要地址段前三段一样局域网内就能联系。
IP 地址后来不够用了咋办,电信商们把一个地区的弄上一个共网然后都是子网,然后再弄一个子网是IPV6的,V6的最后一位是字母的罗马字母的。逻辑端口是可以自动改变的,如果已经被占了就一次顺延反正都能有号码也就是逻辑端口。,
网络模型
OSI 参考模型,国际定义的一个网络分层(7曾)模型用于网络通讯。每一层都有自己的特征然后这些数据传递到下一层然后继续增加表示。这些加表示的动作叫做数据的封装,通过封装能给数据添加了通讯协议,IP地址等。数据到了最后一层就是物理层,最后数据包通过物理层发给目标。
物理层有光纤,红外,无线电波等。通过这些物理层发送到目标之后目标机器会依层拆包,同样的封装逆向的顺序。
网络模型参考图
IP地址
本地回环地址,本地主机的IPlocalhost address 127.0.0.1 www是万维网的主机,baidu 是百度的主机名称点com是商业组织。
端口号用于标示进程的逻辑地址,不同进程的表示有效端口0 到 65535 其中0 到1024 系统使用的保留端口
实例:
package IPDemo;
import java.net.*;
import java.util.Iterator;
public class IPDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void sopadn(InetAddress ina)
{
sop("address:"+ina.getHostAddress());
sop("name:"+ina.getHostName());
}
public static void main(String[] args) throws UnknownHostException {
InetAddress i = InetAddress.getLocalHost();
//sop(i.toString());//得到PC-20130907ZRPT/192.168.18.2
//sop("address:"+i.getHostAddress());//给address
//sop("name:"+i.getHostName());//给name
//拿到任意主机的IP地址对象返回值是地址
/*InetAddress ia = InetAddress.getByName("www.baidu.com");
sopadn(ia);
输出结果:
address:61.135.169.125
name:www.baidu.com
*/
/*看看百度的地址值使用getAllByName(String
* InetAddress[] ia = InetAddress.getAllByName("www.baidu.com");//百度不是一个地址吗检测
for(InetAddress ina: ia)
{
sopadn(ina);
}
输出结果:
* address:61.135.169.121
name:www.baidu.com
address:61.135.169.125
name:www.baidu.com
还真不是一个地址看来也不是一台主机而是两台主机。
*/
}
}
输出结果:见注释
通讯的规则,常见的协议有TCP UDP
UDP的特点是面向无连接,网络通讯有两段,有UDP 的话你在不在我一样发数据,把数据打成一个包你在我给你,你不在我丢掉包。就是把数据邮寄出去,发哪去我要有一个地址和端口要明确出来,发出来的包括的地址未必在或者说在但是不见得接受。每个数据包是64K以内,无连接不可靠协议刚发也许行半路如果对方不在了数据就部分丢失了这个叫丢包,不需要建立链接速度快。生活中这种传送方式常见,QQ聊天就是UDP ,你在不在不重要速度快就好了,聊天中的数据允许丢失,如果时时刷新就可以更弥补数据丢失造成的损失。UDP 非常有用。
下载的话是TCP,TCP的数据不能丢失,双方建立联系之后才建立数据通道,确认对方在不在使用的是三次握手的方式进行的,发一次问在吗,如果在的话回复 完成两次次握手,之后再次发送信息告知对方我知道你在了。这就是第三次握手。这 个比较可靠如果单方面断开数据会停止传送。优点是数据传送量大可以大数量传送,TCP就像打电话,如果那边不通就不能传送信息。但是计算机会记住断点 下次通讯的时候在断点处继续通讯。
网络应用程序是接受和发送信息的一种机制通讯两段都必须有socket。socket 是通讯的港口进出的环节点。
UDP 发送数据的包就是DatagramPacket
DatagramPacket
java.net
类 DatagramSocket
java.lang.Object java.net.DatagramSocket
-
直接已知子类:
- MulticastSocket
public class DatagramSocket extends Object
此类表示用来发送和接收数据报包的套接字。
数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
在 DatagramSocket 上总是启用 UDP 广播发送。为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。
示例:DatagramSocket s = new DatagramSocket(null); s.bind(new InetSocketAddress(8888));
这等价于:DatagramSocket s = new DatagramSocket(8888);
两个例子都能创建能够在 UDP 8888 端口上接收广播的 DatagramSocket。
-
从以下版本开始:
- JDK1.0 另请参见:
-
DatagramPacket
,DatagramChannel
构造方法摘要 | |
---|---|
| DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。 |
protected | DatagramSocket(DatagramSocketImpl impl) 创建带有指定 DatagramSocketImpl 的未绑定数据报套接字。 |
| DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。 |
| DatagramSocket(int port,InetAddress laddr) 创建数据报套接字,将其绑定到指定的本地地址。 |
| DatagramSocket(SocketAddress bindaddr) 创建数据报套接字,将其绑定到指定的本地套接字地址。 |
凡是带着InetAddress 的都是发送数据包
实例:在自己的Eclips 上面模拟接收端和发送端互相发送信息
接受端口
package Socket;
import java.io.IOException;
import java.net.*;
import java.io.*;
public class UdpRece2Self {
//创建一个逻辑端口
//总是true
//。。创建一个字节流缓冲区
//。。创建一个数据包,包内有缓冲区和大小
//。。逻辑端口接受数据包
//。。获取数据包中的IP地址
//..提取包中的接受到的信息并且放入字符串中
//。。输出来着的IP和其信息
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(324);
while(true)
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
dp.getAddress();
String str = new String(dp.getData());
System.out.println("IP:"+dp.getAddress()+"str:"+str);
}
}
}
输出
IP:/192.168.18.2str:ds
输出端口
package Socket;
import java.io.IOException;
import java.net.*;
import java.io.*;
public class UdpSend2Self {
/*
*
*/
//首先建立一个逻辑端口
//建立一个阅读器
//创建字符串
//阅读器读取的内容放到字符串去,读取指导886停止
//。。把每一次读取的内容转化为字节流数组
//。。吧字节流数组放到数据包中,数据包中填写本机的IP地址和逻辑端口
//。。逻辑端口发送数据包
//逻辑端口关闭
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
while((str = br.readLine())!=null)
{
if("886".equals(str))
break;
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName(InetAddress.getLocalHost().getHostAddress()),324);
ds.send(dp);
}
ds.close();
}
}
输入:
ds
接受端口的数据逻辑端口号码,324
DatagramSocket ds = new DatagramSocket(324)
和输出端口的数据包中的端口号码应该是一致的也应该是324
new DatagramPacket(buf,buf.length,InetAddress.getByName(InetAddress.getLocalHost().getHostAddress()),324);
聊天软件
还可以使用多线程技术,把首发信息的界面结合,发信息的时候也能接信息。因为收和发的动作比一致所以要定义两个run方法,这两个方法要封装到两个不同的类中。
package 聊天程序;
import java.io.IOException;
import java.net.*;
import java.io.*;
class Send implements Runnable
{
private DatagramSocket ds;
//哪一个socket 传不确定,发送端在构造的时候穿进去一个指定的Socket
public 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,0,buf.length,InetAddress.getByName("192.168.18.2"),325);
ds.send(dp);
}
}
catch(Exception e)
{
throw new RuntimeException("send fail");
}
}
}
class Rece implements Runnable
{
private DatagramSocket ds;
//哪一个socket 传不确定,发送端在构造的时候穿进去一个指定的Socket
public 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("receive fail");
}
}
}
public class chatSoftware {
public static void main(String[] args) throws SocketException {
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(325);
new Thread(new Send(sendSocket)).start();
new Thread(new Rece(receSocket)).start();
}
}
运行效果:
TCP数据传输
TCP 分客户端和服务端,客户端对应的是Socket 服务端对应的对象是ServerSocket
客户端:
通过查阅socket 对象,发现在该对象建立的时候就可以去链接指定的主机,因为TCP 是面向连接的,所以在建立socket服务室,就要有服务端存在并且链接成功,形成通路后在该通道进行数据的传输,
实例:通过TCP 传送信息
服务器端口
package TCP数据传输;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
import java.net.*;
import java.io.*;
public class TcpServer2 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(431);
Socket s = ss.accept();
String ip = s.getLocalAddress().getHostAddress();
System.out.println(ip+"connected");
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("server receive hello".getBytes());
ss.close();
}
}
客户端口
package TCP数据传输;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
import java.net.*;
import java.io.*;
public class TcpClient2 {
/*
* 演示tcp的传输端和客户端的互访
* 需求:客户端给服务端发送数据,服务端收到后给客户端反馈信息
*/
/*
* 客户端1 剪子socket服务指定要链接主机和端口
* 2 获取socket六中的输出流,讲数据写到该流中,通过网络发送给服务端。
* 3获取socket六中的输入流将服务端反馈的数据获取并打印
* 4关闭客户端
*/
public static void main(String[] args) throws UnknownHostException, IOException {
Socket s = new Socket(InetAddress.getByName(InetAddress.getLocalHost().getHostAddress()),431);
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();
}
}
输出结果:
TcpServer
192.168.18.2connected
服务端你好
TcpClient
server receive hello
客户端和服务端等在换行的问题
需求:建立一个文本转换服务器,客户端给服务端发送文版,服务端会将文本返回给客户端,而且客户端可以不断的进行文本转化,当客户端输入over 时候,转换结束。
有时候客户端和服务端都在莫名等待原因是客户端和服务端都有阻塞式方法这些方法没有读到结束标记,那就一直等待而两段都在等待,如果想解决就检查两边的read 和 write 方法。8 成都在这两个方法内出了问题。
实例:服务器端口
package TCP数据传输;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
import java.net.*;
import java.io.*;
public class 文本转换服务端 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(432);
Socket s = ss.accept();//s 是客户端对象
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip);
BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//目的socket 输出流将大写的数据写入socket的输出流中并且发送给客户端。
//BufferedWriter bufrOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//简便方法可以接受字节和字符流还能自动刷新。简便无敌
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line = bufrIn.readLine())!= null)
{
System.out.println(line);//readLine()必须读到回车符才能输出回车标记是返回数据的信号
out.println(line.toUpperCase());
// bufrOut.write(line.toUpperCase());//换行才是写入的标志
// bufrOut.newLine();
// bufrOut.flush();
}
s.close();
ss.close();
}
}
客户端
package TCP数据传输;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.*;
import java.io.*;
public class 文本转换客户端 {
public static void main(String[] args) throws IOException {
Socket s = new Socket(InetAddress.getLocalHost().getHostAddress(),432);
//第一读取键盘数据的流对象
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//定义目的,将数据写入到socket 的输出流,发给服务端。
//BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//定义一个socket读取流,读取服务端返回的大写信息。
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str;
while((str = bufr.readLine())!=null)
{
if(str.equals("over"))
break;
out.println(str);
// bufOut.write(str);
// bufOut.newLine();
// bufOut.flush();
String strIn = bufIn.readLine();
System.out.println("server:"+strIn);
}
bufr.close();
s.close();
}
}
输出结果
服务器端:
192.168.18.2
fd
ds
sds
ds
ds
客户端:
fd
server:FD
ds
server:DS
sds
server:SDS
ds
server:DS
ds
server:DS
多线程服务器设计
如果用户增多我们就要使用多线程在服务器内为多个用户提供服务,要明确每一个客户端要在服务端执行的代码即可,将该代码存入run 方法中。
实例:使用多线程技术让多个用户发送图片到服务器中,然后服务器储存这些图片到硬盘中
服务器端
package TCP数据传输;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.*;
import java.io.*;
class PicThread implements Runnable
{
private Socket s;
PicThread(Socket s)
{
this.s = s;
}
public void run()
{
String ip = s.getInetAddress().getHostAddress();
try
{
int count = 1;
System.out.println(ip+"...connected");
InputStream in = s.getInputStream();//InputStream 自己也可以实例化
File file = new File(ip+"("+count+")"+".jpg");
while(file.exists())
{file = new File(ip+"("+count+++")"+".jpg");}
FileOutputStream fos = new FileOutputStream("acopy.jpg");
byte [] buf = new byte[1024];
int len = 0;
while((len = in.read(buf))!= -1)
{
fos.write(buf,0,len);
fos.flush();
}
fos.close();
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
pw.println("上传成功");
s.close();
}
catch(Exception e)
{
throw new RuntimeException(ip+"上传失败");
}
}
}
public class 图形传送保存服务器端 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10001);
while(true)
{
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
//ss.close();
}
}
客户端
package TCP数据传输;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.io.*;
public class 图形传送保存客户端 {
//主机IP地址:"192.168.18.2"
//InetAddress.getLocalHost().getHostAddress()
//Socket s = new Socket(InetAddress.getLocalHost().getHostAddress(),432);
public static void main(String[] args) throws UnknownHostException, IOException {
if(args.length!= 1)
{
System.out.println("请选择一个jpg格式的图片");
return;
}
File file = new File(args[0]);
if(!(file.exists()&&file.isFile()))
{
System.out.println("不是文件或者没有文件");
return;
}
if(!file.getName().endsWith(".jpg"))
{
System.out.println("jpg end require");
return;
}
if(file.length()>1024*1204*5)
{
System.out.println("5 M max");
return;
}
Socket s = new Socket(InetAddress.getLocalHost().getHostAddress(),10001);
FileInputStream fis = new FileInputStream("a.jpg");
PrintStream out = new PrintStream(s.getOutputStream());//获取输出流方法1PrintStream后边必须跟另一个OutputStream
//OutputStream out = s.getOutputStream(); 获取输出流方法2 两种方式都行
byte[] buf = new byte[1024];
int line;
while((line = fis.read(buf))!= -1)
{
out.write(buf,0,line);
}
fis.close();
s.shutdownOutput();
//接收服务器传来的信息。
byte[] bufIn = new byte[1204];
InputStream in = s.getInputStream();
int num = in.read(bufIn);
System.out.println(new String(bufIn,0,num));
s.close();
}
}