线程与线程可以通过通信来实现内存共享,而所谓的网络编程就是实现应用程序间的相互通信来实现信息共享。
网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据交换的复杂。虽然通过IP地址和端口可以找到网络上运行的一个程序,但是如果需要进行网络编程,则还需要了解网络通讯的过程
网络通讯模型:
网络通讯基于“请求-响应”模型。为了理解这个模型,先来看一个例子,经常看电视的人肯定见过审讯的场面吧,一般是这样的:
警察:姓名
嫌疑犯:XXX
警察:性别
嫌疑犯:男
警察:年龄
嫌疑犯:29
……
在这个例子中,警察问一句,嫌疑犯回答一句,如果警察不问,则嫌疑犯保持沉默。这种一问一答的形式就是网络中的“请求-响应”模型。也就是通讯的一端发送数据,另外一端反馈数据,网络通讯都基于该模型。
在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。
由此,网络编程中的两种程序就分别是客户端和服务器端,例如QQ程序,每个QQ用户安装的都是QQ客户端程序,而QQ服务器端程序则运行在腾讯公司的机房中,为大量的QQ用户提供服务。这种网络编程的结构被称作客户端/服务器结构,也叫做Client/Server结构,简称C/S结构。
使用C/S结构的程序,在开发时需要分别开发客户端和服务器端,这种结构的优势在于由于客户端是专门开发的,所以根据需要实现各种效果,专业点说就是表现力丰富,而服务器端也需要专门进行开发。但是这种结构也存在着很多不足,例如通用性差,几乎不能通用等,也就是说一种程序的客户端只能和对应的服务器端通讯,而不能和其它服务器端通讯,在实际维护时,也需要维护专门的客户端和服务器端,维护的压力比较大。
其实在运行很多程序时,没有必要使用专用的客户端,而需要使用通用的客户端,例如浏览器,使用浏览器作为客户端的结构被称作浏览器/服务器结构,也叫做Browser/Server结构,简称为B/S结构。
使用B/S结构的程序,在开发时只需要开发服务器端即可,这种结构的优势在于开发的压力比较小,不需要维护客户端。但是这种结构也存在着很多不足,例如浏览器的限制比较大,表现力不强,无法进行系统级操作等。
总之C/S结构和B/S结构是现在网络编程中常见的两种结构,B/S结构其实也就是一种特殊的C/S结构。
P2P(Pointto Point)程序,常见的如BT、电驴等。P2P程序是一种特殊的程序,应该一个P2P程序中既包含客户端程序,也包含服务器端程序,例如BT,使用客户端程序部分连接其它的种子(服务器端),而使用服务器端向其它的BT客户端传输数据。如果这个还不是很清楚,其实P2P程序和手机是一样的,当手机拨打电话时就是使用客户端的作用,而手机处于待机状态时,可以接收到其它用户拨打的电话则起的就是服务器端的功能,只是一般的手机不能同时使用拨打电话和接听电话的功能,而P2P程序实现了该功能。
网络编程中最重要,也是最复杂的概念——协议。网络编程就是运行在不同计算机中两个程序之间的数据交换。在实际进行数据交换时,为了让接收端理解该数据,计算机比较笨,什么都不懂的,那么就需要规定该数据的格式,这个数据的格式就是协议。比如,以前在没有电话的时候常用电台发送。在实际发报时,需要首先将需要发送的内容转换为电报编码,然后将电报编码发送出去,而接收端接收的是电报编码,如果需要理解电报的内容则需要根据密码本翻译出该电报的内容。这里的密码本就规定了一种数据格式,这种对于网络中传输的数据格式在网络编程中就被称作协议。
因为网络上的格式不统一,为了方便不同程序之间的相互通信,IOS组织定义了一个标准的参考模型。
一般来说开发处于传输层和网际层,应用层为:FTP和HTTP协议等,传输层为:UDP和TCP等,网际层为:IP。
通常用户操作的是应用层,而编程人员需要做的是传输层和网际层,用户在应用层操作的数据,经过逐层封包,最后到物理层发送到另一个模型中,再进行逐层解包。
网络通信三要素:IP地址,端口号,传输协议
1.IP地址
A:方便的识别网络上的每个设备.每个设备唯一性,分为IPv4和IPv6。
B: IP地址由4个0-255之间的数字组成,例如10.0.120.34。
c: 本机回环地址:127.0.0.1,主机名为:localhost。
D:IP地址在Java中也是对象存在的,是InetAddress类,存在于java.net包中。
知识点:什么是域名解析?
由于IP地址不容易记忆,所以为了方便记忆,有了另外一个概念——域名,例如sohu.com等。一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。域名的概念可以类比手机中的通讯簿,由于手机号码不方便记忆,所以添加一个姓名标识号码,在实际拨打电话时可以选择该姓名,然后拨打即可。
在网络中传输的数据,全部是以IP地址作为地址标识,所以在实际传输数据以前需要将域名转换为IP地址,实现这种功能的服务器称之为DNS服务器,也就是通俗的说法叫做域名解析。例如当用户在浏览器输入域名时,浏览器首先请求DNS服务器,将域名转换为IP地址,然后将转换后的IP地址反馈给浏览器,然后再进行实际的数据传输。
当DNS服务器正常工作时,使用IP地址或域名都可以很方便的找到计算机网络中的某个设备,例如服务器计算机。当DNS不正常工作时,只能通过IP地址访问该设备。所以IP地址的使用要比域名通用一些。
2端口号
A: 一个计算机上可以并发运行多个网络程序,而不会在互相之间产生干扰。
B: 在硬件上规定,端口的号码必须位于0-65535之间,每个端口唯一的对应一个网络程序,一个网络程序可以使用多个端口。
3、传输协议
在现有的网络中,网络通讯的方式主要有两种:
1、 TCP(传输控制协议)方式
是面向无连接,明确了对方的端口,无论在不在网上,只管传输,不在就会丢失数据。只求速度,应用于网络视频会议和聊天等应用程序中。
2、 UDP(用户数据报协议)方式
是面向连接的,必须连接成功才能传输数据,应用于下载或机密文件等程序上
那么这俩种方式有什么区别呢?
UDP协议
1.将数据源和目的封装在数据包中,不需要建立连接。
2.每个数据包的大小限制在64K内。
3.无连接,不可靠。
4.不需要建立连接,速度快。
TCP协议
1建立连接,形成传输数据的通道。
2.在连接中可以进行大数据量的传输。
3.通过三次握手完成连接,是可靠的协议。
4.必须建立连接,效率会降低。
注意:TCP连接的原理是计算机的三次握手。第一次本方发送请求,第二次对方确认连接,第三次本方再次确认连接成功。
客户端:
a、建立UDPSocket服务。
b、提供数据,并将数据封装到数据包中
c、通过socket服务的发送功能,将数据包发送出去
d、关闭资源
服务器端:
a、定义UDPSocket服务,监听一某个端口。
b、定义一个数据包,用来存储接收到的字节数据,因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。
c、通过socket服务的receive方法接收到的数据存入已定义好的数据包中
d、通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
e、关闭资源
练习一:使用键盘录入方式,一端发送一端接收。
客户端:
import java.net.*;
import java.io.*;
class UdpClientSystem
{
//客户端:从控制台输入数据,发送到服务器端。
publicstatic void send(){
try{
DatagramSocketds=new DatagramSocket();//创建通信服务。
BufferedReaderbr=new BufferedReader(new InputStreamReader(System.in));//从命令台读取数据。
InetAddressia=InetAddress.getByName("127.0.0.1");//给定主机确定IP地址
Stringline=null;
while((line=br.readLine())!=null){
if("886".equals(line))//定义如果输入886则结束。
break;
byte[]buff=line.getBytes();//将数据存放在byte数组。
DatagramPacketdp=new DatagramPacket(buff,buff.length,ia,10000);//将数据放入到数据包中。
ds.send(dp);//发送数据包。
}
ds.close();//关闭资源
}
catch(Exceptione){
thrownew RuntimeException("发送失败");
}
}
publicstatic void main(String[] args)
{
send();
}
}
服务器端:
import java.net.*;
class UdpServerSystem
{
publicstatic void receive(){
try{
DatagramSocketds=new DatagramSocket(10000);//创建通信对象并监听端口。
while(true){
byte[]buff=new byte[1024];
DatagramPacketdp=new DatagramPacket(buff,buff.length);//定义数据包
ds.receive(dp);//用数据包来接收数据。
Stringip=dp.getAddress().getHostAddress();//获取本地IP地址。
intport=dp.getPort();//获取端口号
byte[]data=dp.getData();//获取数据内容
System.out.println("ip地址:"+ip+" 端口号:"+port+"数据:"+new String(data,data.length));
}
ds.close();//关闭资源。
}
catch(Exceptione){
thrownew RuntimeException("接收失败");
}
}
publicstatic void main(String[] args)
{
receive();
}
}
练习二,聊城程序,可以接收,可以发送。
客户端:
import java.net.*;
import java.io.*;
public class UdpClientChat implements Runnable
{
privateDatagramSocket ds;
UdpClientChat(DatagramSocketds){
this.ds=ds;
}
//客户端:从控制台输入数据,发送到服务器端。
public void run(){
try{
BufferedReaderbr=new BufferedReader(new InputStreamReader(System.in));//从命令台读取数据。
InetAddressia=InetAddress.getByName("127.0.0.1");//给定主机确定IP地址
Stringline=null;
while((line=br.readLine())!=null){
if("886".equals(line))//定义如果输入886则结束。
break;
byte[]buff=line.getBytes();//将数据存放在byte数组。
DatagramPacketdp=new DatagramPacket(buff,buff.length,ia,10000);//将数据放入到数据包中。
ds.send(dp);//发送数据包。
}
}
catch(Exceptione){
thrownew RuntimeException("发送失败");
}
finally{
ds.close();//关闭资源
}
}
}
服务器端:
import java.net.*;
import java.io.*;
public class UdpServerChat implementsRunnable
{
privateDatagramSocket ds;
UdpServerChat(DatagramSocketds){
this.ds=ds;
}
public void run(){
try{
while(true){
byte[]buff=new byte[1024];
DatagramPacketdp=new DatagramPacket(buff,buff.length);//定义数据包
ds.receive(dp);//用数据包来接收数据。
Stringip=dp.getAddress().getHostAddress();//获取本地IP地址。
intport=dp.getPort();//获取端口号
byte[]data=dp.getData();//获取数据内容
System.out.println("ip地址:"+ip+" 端口号:"+port+"数据:"+new String(data,data.length));
}
}
catch(Exceptione){
thrownew RuntimeException("接收失败");
}
finally{
ds.close();//关闭资源。
}
}
}
运行类:
import java.net.*;
class Test10
{
publicstatic void main(String[] args)
{
try{
DatagramSocketclient=new DatagramSocket();
DatagramSocketserver=new DatagramSocket(10000);
UdpClientChatucc=new UdpClientChat(client);
UdpServerChatusc=new UdpServerChat(server);
newThread(ucc).start();
newThread(usc).start();
}
catch (SocketException e) {
e.printStackTrace();
}
}
}
a:TCP分客户端和服务端。
b客户端对应的对象是Socket,服务端对应的对象是ServerSocket。
c:socket它被称之为插座,相当于港口一样,是网络服务提供的一种机制。
d:通信两端都要有Socket,才能建立服务。
e:网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。
客户端对应的对象是Socket,
A:创建Socket服务,并指定要连接的主机端口。通路一建立,就会产生Socket流(包括输入流和输出流),通过accept方法获取.
B:为了发送数据,应获取Socket中的输出流,如果要接收服务端的反馈信息,还需要获取Socket的输入流
C:通过输出流的write()方法将要发送的数据写入到流中
D:关闭Socket流资源
服务器端对应的是ServerSocket.
A:建立服务器的Socket服务,并监听一个端口。
B:获取连接过来的客户端对象,通过accept方法,没有连接就等待,是阻塞式方法。
C:如果客户端发送过来数据,那么服务端要使用对应的客户端对象,并获取到该对象的读取流来读取发送过来的数据。
D:关闭服务器(可选)
练习一:客户端向服务器发送文本,服务器会返回大写的给客户端。输入over结束。
客户端
public class TcpClientText {
public static void method_1(){
try {
Socket s= newSocket("127.0.0.1" ,10000);
OutputStreamos=s.getOutputStream();
InputStreamis=s.getInputStream();
//定义读取键盘的输入流对象。
BufferedReader br= newBufferedReader( new InputStreamReader(System.in ));
//定义目的,将数据写入到Socket输入流,发送给服务器。
//BufferedWriter bw=new BufferedWriter(newOutputStreamWriter(os));
PrintWriter pw= newPrintWriter(os, true );
//定义一个读取流,读取服务器返回来的大写信息。
BufferedReader brs= newBufferedReader( new InputStreamReader(is));
String line= null ;
while ((line=br.readLine())!=null){
if ("over".equals(line))
break ;
pw.println(line);
//bw.write(line);
//因为客户端和服务器端都是阻塞式方法,这些方法没有读到结束标记会一直等待。
//bw.newLine();//结束标记。
//使用字符流必须要刷新数据缓冲区。
//bw.flush();
String str=brs.readLine();
System. err.println("server:" +str);
}
br.close();
//关闭Socket后他会返回一个-1,服务器读到-1也会关闭服务。
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args){
method_1();
}
}
public class TcpServerText {
public static void method_1(){
try {
ServerSocket ss= newServerSocket(10000);
Socket s=ss.accept();
Stringstr=s.getInetAddress().getHostAddress();
System. err .println(str);
//获取socket流中的数据,并装饰。
InputStreamis=s.getInputStream();
OutputStreamos=s.getOutputStream();
BufferedReader br= newBufferedReader( new InputStreamReader(is));
//BufferedWriter bw=newBufferedWriter(new OutputStreamWriter(os));
//也可以使用printWiter方法,有自动结束标记
PrintWriter pw= newPrintWriter(os, true);
String line= null ;
while ((line=br.readLine())!=null){
System. err .println(line);
pw.println(line);
// bw.write(line.toUpperCase());
// bw.newLine();
// bw.flush();
}
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
method_1();
}
}
练习二:本地拷贝文件到服务器。
客户端
public class TcpClientFile {
public static void method_1(){
try {
Socket s= newSocket("127.0.0.1" ,10006);
//获取socket流。
InputStreamis=s.getInputStream();
OutputStreamos=s.getOutputStream();
//定义输入流。源为硬盘文件。
BufferedReader br= newBufferedReader( new FileReader("E:\\IO\\1.txt" ));
PrintWriter pw= newPrintWriter(os, true);
String line= null ;
while ((line=br.readLine())!=null){
pw.println(line);
}
pw.println( "over" );
BufferedReader brs= newBufferedReader( new InputStreamReader(is));
String str=brs.readLine();
System. err.println("server:" +str);
br.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
method_1();
}
}
public class TcpServerFile {
public static void method(){
try {
ServerSocket ss= new ServerSocket(10007);
Socket s=ss.accept();
//获取客户端的流对象。
InputStream is=s.getInputStream();
OutputStream os=s.getOutputStream();
Stringstr=s.getInetAddress().getHostAddress();
System. err .println(str);
//装饰对象,并写到文件中。
BufferedReader br= new BufferedReader( new InputStreamReader(is));
//定义输出流,目的是硬盘文件。
PrintWriter pw= new PrintWriter( new FileWriter("E:\\IO\\Server_文章摘抄1.txt" ), true);
String line= null ;
while ((line=br.readLine())!= null){
if ("over".equals(line))
break ;
pw.println(line);
}
//PrintWriter pwc =new PrintWriter(os);
//pwc.println("上传成功");
os.write( "上传成功".getBytes());
//pw.close();
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
method();
}
}
public class TcpClientPic {
public static void method_1(){
try {
Socket s= newSocket("127.0.0.1" ,10000);
InputStreamis=s.getInputStream();
OutputStreamos=s.getOutputStream();
FileInputStream fi= newFileInputStream("E:\\IO\\1.jpg" );
byte [] buff=new byte[1024];
int len=0;
while ((len=fi.read(buff))!=-1){
os.write(buff,0,len);
}
//结束标记。
s.shutdownOutput();
byte [] buffo= new byte [1024];
int num=is.read(buff);
System. err .println(newString(buff,0,num));
fi.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
method_1();
}
}
服务器端
public class TcpServerPic {
public static void method_1(){
try {
ServerSocket ss= newServerSocket(10000);
Socket s=ss.accept();
InputStreamis=s.getInputStream();
FileOutputStream fo= newFileOutputStream("E:\\IO\\11.jpg" );
byte [] buff=new byte[1024];
int len=0;
while ((len=is.read(buff))!=-1){
fo.write(buff,0, len);
}
OutputStreamos=s.getOutputStream();
os.write( "上传成功" .getBytes());
fo.close();
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
method_1();
}
}
单线程的服务端有个局限性。当A客户端连接上以后,被服务端获取到。服务端执行具体流程。这时B客户端连接,只能等待。因为服务端还没有处理完A客户端的请求。还没有循环回来执行下一次accept方法。所以,暂时获取不到B客户端对象。
那么为了可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
客户端:
public class TcpClientPic {
public static void method_1(){
try {
Socket s= newSocket("127.0.0.1" ,10000);
InputStream is=s.getInputStream();
OutputStreamos=s.getOutputStream();
FileInputStream fi= newFileInputStream("E:\\IO\\1.txt" );
byte [] buff=new byte[1024];
int len=0;
while ((len=fi.read(buff))!=-1){
os.write(buff,0,len);
}
//结束标记。
s.shutdownOutput();
byte [] buffo= new byte [1024];
int num=is.read(buff);
System. err .println(newString(buff,0,num));
fi.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if (args.length !=1){
System. err .println("请选择一个jpg格式的文件" );
return ;
}
File file= new File(args[0]);
if (!(file.exists()&&file.isFile())){
System. err .println("该文件有问题,要么不存在,要么不是文件" );
return ;
}
if (!file.getName().endsWith("jpg" )){
System. err .println("文件格式错误" );
return ;
}
if (file.length()>1024*1024*5){
System. err .println("文件过大" );
return ;
}
method_1();
}
}
public class PciThread implements Runnable{
private Socket s;
public PciThread(Socket s){
this .s =s;
}
public void run(){
int count=1;
String ip= s.getInetAddress().getHostAddress();
System. err .println(ip);
try {
InputStream is=s.getInputStream();
File file= newFile("E:\\IO\\" +ip+"(" +(count)+ ")"+"jpg" );
//如果文件存在则后面加数字表示。
while (file.exists())
file= newFile("E:\\IO\\" +ip+"(" +(count++)+ ")"+"jpg" );
FileOutputStream fw= newFileOutputStream(file);
byte [] buff=new byte[1024];
int len=0;
while ((len=is.read(buff))!=-1){
fw.write(buff,0,len);
}
OutputStream os=s.getOutputStream();
os.write( "上传成功" .getBytes());
fw.close();
s.close();
}
catch (Exception e){
throw new RuntimeException(ip+"上传失败");
}
}
}
服务器端:
public class TcpServerPic {
public static void main(String[] args) {
try {
ServerSocket ss= newServerSocket(10000);
while (true ){
Socket s=ss.accept();
new Thread(newPciThread(s)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端通过键盘录入用户名。服务器对这个用户名进行效验。
如果该用户存在,在服务器上显示 xxx,已登陆。并在客户端显示 xxx,欢迎光临。
如果用户不存在,在服务器端显示 xxx,尝试登陆。并在客户端显示 xxx,该用户不存在。
最多登陆三次。
代码实现:
//客户端
public class LoginClient{
public static void method(){
try {
Socket s= newSocket("127.0.0.1" ,10006);
OutputStreamos=s.getOutputStream();
InputStream is=s.getInputStream();
BufferedReader br= newBufferedReader( new InputStreamReader(System.in ));
PrintWriter out= newPrintWriter(os, true);
BufferedReader bufr= newBufferedReader( new InputStreamReader(is));
for (int x=0;x<3;x++){
String str=br.readLine();
if (str==null )
break ;
out.println(str);
String info=bufr.readLine();
System. err.println("server:" +info);
if (info.contains("欢迎" ))
break ;
}
br.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
method();
}
}
//服务器端
public class LoginServer {
public static void main(String[] args ) {
try {
ServerSocket ss= newServerSocket(10006);
while (true ){
Socket s=ss.accept();
new Thread(newUserThread(s)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class UserThread implements Runnable{
private Socket s;
public UserThread(Socket s){
this .s =s;
}
public void run(){
String ip= s.getInetAddress().getHostAddress();
System. err .println(ip+".....连接成功" );
try {
for (int x=0;x<3;x++){
BufferedReader br= newBufferedReader(new InputStreamReader(s .getInputStream()));
String name=br.readLine();
if (name==null )
break ;
BufferedReader bufr= newBufferedReader(new FileReader("E:\\IO\\User.txt" ));
PrintWriter out= newPrintWriter(s .getOutputStream(), true);
String line= null ;
boolean flag=false ;
while((line=bufr.readLine())!= null){
if (line.equals(name)){
flag= true ;
break ;
}
}
if (flag){
System. err.println(name+"已登陆" );
out.println(name+"欢迎光临" );
break ;
}
else {
System. err.println(name+"尝试登陆" );
out.println(name+"用户名不存在" );
}
}
s.close();
}
catch (Exception e){
throw new RuntimeException(ip+"效验失败");
}
}
}
浏览器是一个标准的客户端,它可以对服务端传送过来的数据消息进行解析,把符合应用层协议的消息部分解析后,将头信息拆包掉,传送到应用层,只保留了正确的正文主题部分显示在主体部分上。
而由于使用java编译是在传输层和网际层处理的,所以,会接受到全部的消息,包含了头消息。而浏览器处于应用层,已将发送来的头消息去除,只留下了主体信息。
1.客户端:浏览器;
服务端:自定义服务器;
2.客户端:浏览器;
服务端:tomcat服务器。
URI:范围更大,条形码也包含于此范围
URL:范围较小,即域名
常见方法:
构造函数:URL(String protocol,String host,int port,String file);//根据指定 protocol、host、port号和 file 创建 URL对象。
String getProtocol();//获取协议名称
String getHost();//获取主机名
int getPort();//获取端口号
String getFile();//获取URL文件名
String getPath();//获取此URL的路径部分
String getQuery();//获取此URL的查询部,客户端传输的特定信息
方法:
1)URLConnection openConnection();//用URL调用此方法,返回一个 URLConnection对象,它表示到 URL 所引用的远程对象的连接。
2)InputStream getInputStream();//获取输入流
3)OutputStream getOutputStream();//获取输出流