黑马程序员java学习笔记——网络编程

时间:2023-02-21 16:12:39
------- android培训java培训、期待与您交流! ----------

网络编程

    概述     java语言涵盖的领域非常广,之前我们所学习的都是在本机上对数据进行处理,现在需要在本机和其他机器上利用java语言进行数据的通讯,这就涉及到了java中非常重要的一部分内容,网络编程。     要学习网络编程首先需要知道网络通讯的模型和网络通讯的要素。     网络模型:网络传输时,为了更细致的划分每个传输层次对应的功能的不同,就有了对这些层次更细致的划分,以便与让每个层次的功能更加明确,所以就引入了网络模型,网络模型有OSI参考模型和TCP/IP参考模型。     OSI参考模型:一共有七层,分别是:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。     TCP/IP参考模型:一共五层,分别是:应用层、传输层、网际层和主机至网络层。     数据是通过几次封包拆包和传输动作在不同的主机之间进行通讯的,我们写网络程序都在传输层和网际层,每个层都有自己的协议。     传输层:TCP,UDP;     网际层:IP;     层应用:http应用层协议,它基于传输层。     网络通讯三要素     在两台机器之间进行数据的传输要满足以下条件:     1,找到对方的IP;     2,数据要发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络引用程序用数字进行标识,为了方便称呼这些数字,叫做端口,它是一个逻辑端口;     3,定义一个通讯规则,这个规则称为协议(数据规则),国际组织定义了一个通用的协议Tcp/IP。     这就是网络通讯的三要素。     IP地址     网络中设备标识,不易记忆,可以用主机名,IP地址分为四段组成,每一段就是一个字节,最大就是255;     本地回环地址127.0.0.1,如果电脑上没配置地址,默认的就是这个。     www:万维网组织,有这标志,说明在这个组织注册过,如:www.baidu.com;     在java中有一个专门用于操作IP的类:InetAddress,这个类是没有构造函数的,但是里边还有非静态的方法,所以这个类提供了静态方法来获取该类对象,它有Inet4Address和Inet6Address两个子类,其中常用的方法有:     getLocalHost:返回本机主机,返回的是InetAddress对象;     getHostAddress:获取主机地址;     getHostName:获取主机名;     getByName(String host):获取给定主机的IP地址,返回的是InetAddress对象;     如果IP地址和对应的主机名没有映射关系在网络上,我这个主机去找这个地址找到了,但没解析成功,那么你的名字还是IP地址,其实以地址为主最方便,以名称为主需要解析过程。     下面对这些方法进行演示:
import java.net.*;
class IPDemo
{
public static void main(String[] args)throws Exception
{
InetAddress i = InetAddress.getLocalHost();//获取本地主机对象;
System.out.println("adress:"+i.getHostAddress());//获取主机地址;
System.out.println("name:"+i.getHostName());//获取主机名称;
InetAddress ia = InetAddress.getByName("www.baidu.com");//获取给定主机的IP;
System.out.println("address:"+ia.getHostAddress());
System.out.println("name:"+ia.getHostName());
}
}

    端口:就是一个数字标识,不需要封装对象,作用就是对网络程序进行标识。     传输协议     UDP传输:相当于对讲机     特点:1,发数据前,两端不需要建立连接,面向无连接;           2,数据需要被封装在包中,而且包的大小限制在64K内,超过分成多包发;           3,由于是无连接的,所以会有可能发生丢包的情况,是不可靠协议;           4,因为不求保证数据不丢失,传输速度快;     TCP传输:下载就是TCP传输     特点:1,数据发送前,需要建立连接,形成传输数据的通道,是面向连接的;           2,在连接中进行大量数据的传输;
          3,通过三次握手完成连接,是可靠的协议;
          4,由于必须建立连接,效率稍低
   
         Socket     1,Socket就是为网络服务提供的一种机制;
    2,通信的两端都有Socket;
    3,网络通信其实就是Socket之间的通信;
    4,数据在两个Socket之间通过IO传输。
         UDP传输     UDP发送端     在UDP传输中,建立Socket服务用的是java中的DatagramSocket对象,用这个对象来建立端点,发送和接受数据的方式都定义在这个对象中。     下面我们通过UDP传送方式将一段文字数据发送出去。     步骤:1,建立UDP的Socket服务,可以不指定端口,由系统自行分配;           2,提供数据,并将数据封装到数据包中;
          3,通过Socket的发送功能讲数据发送出去;
          4,关闭资源。
    演示:
import java.net.*;
class UdpSend
{
public static void main(String[] args)throws Exception
{
//建立Udpsocket服务
DatagramSocket ds = new DatagramSocket(8888);//这里不设置的话,系统会随机分配一个端口,设置后,接收端接收到的就是该端口
//将数据封装成数据包
byte[] buf = "就要开始第二遍的学习了".getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.102"),12400);
//发送数据
ds.send(dp);
//关闭资源
ds.close();
}
}

    数据就这么发送出去,就丢失了,因为UDP发送数据是不保证数据不丢失的,只管发出去,有没有人接收,发送端并不在意。     UDP接收端     发送端和接收端是两个独立运行的程序,接收端就是用于接收发送端发过来的数据。     步骤:1,建立socket服务 DataGramSocket;
          2,建立数据包,这个数据包里没有数据,是用于存储接收到的数据,
          DataGramPacket;
          3,通过socket服务的receive方法,将接收到的数据存储到数据包中;
          receive(数据包,数据包长度);
          4,通过数据包的方法,获取包中的数据;
          InetAddress getAddress,获取主机;
          byte[] getData,获取数据;
          int getPort 获取端口号;
          int getLength();获取要接受的数据的长度;
          5,关闭资源。
    DatagramPacket(byte[],length):接收长度为length的数据包;     定义UDP接收端通常会监听一个端口,其实就是个这个网路应用程序定义一个数字标识,方便与明确哪些数据过来,该应用程序可以处理。     演示:
import java.net.*;
class UdpReceive
{
public static void main(String[] args)throws Exception
{
//建立socket服务,并监听一个端口,建立端点
DatagramSocket ds = new DatagramSocket(12400);
//定义数据包,用于存储数据
while(true)
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//通过socket服务的receive方法将接收到的数据存到数据包中
ds.receive(dp);
//通过数据包的方法获取其中的数据
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
int port = dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
}
//关闭资源
//ds.close();
}
}

    上边我们将接收数据的功能定义在了循环中,因为receive是阻塞式方法, while(true)不是死循环,没数据就等,有数据就接受,但是不能把Socket服务放在while循环中, 循环一次后,会再建立一个Socket服务,但是还是使用的之前的端口,这样会发生绑定异常,就算把某程序关了,Socket使用该程序端口也可能会发生异常,因为有可能该端口还没有被释放。  
    键盘录入方式发送数据     规律:只要用到net包,通常就要用到io包,接收端通常都是开着的。     演示:
import java.io.*;
import java.net.*;
class UdpSend
{
public static void main(String[] args)throws Exception
{
DatagramSocket ds = new DatagramSocket();
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null)
{
if("886".equals(line))//如果输入886,就结束读取录入;
break;
byte[] buf = line.getBytes();//将键盘录入转换成字节数组;
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),11000);
ds.send(dp);//将键盘录入的数据写出去;
}
ds.close();
}
}
class UdpReceive
{
public static void main(String[] args)throws Exception
{
//建立接收端Socket服务,并监听一个端口;
DatagramSocket ds = new DatagramSocket(11000);
while(true)
{
byte[] buf = new byte[1024];//建立一个数组,用于接受接收到的数据,一次最大也就接收64k;
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());//通过getData方法获取包中的数据;
System.out.println(ip+"::"+data);
}
}
}

    广播地址:192.168.1.255,这个地址发送出去的数据,局域网中192.168.1.1~254网段里每台主机都能收的到,只要接收端开着,而且监听端口存在,就能收到。
    UDP-聊天     编写一个聊天程序,有数据接收的的部分,也有数据发送的部分,一个线程控制收,一个线程控制发,因为收发动作不一致,所以定义两个run方法,而且这两个run方法定义在不同的类中。     演示:
/*
Udp-聊天
*/
import java.io.*;
import java.net.*;
class Send implements Runnable
{
private DatagramSocket ds;//定义一个Socket服务的引用;
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.getByName("192.168.1.255"),10010);
ds.send(dp);//将数据发送出去;
}
ds.close();
}
catch (Exception e)
{
throw new RuntimeException("发送端失败");
}
}
}
class Rece implements Runnable
{
private DatagramSocket ds;//定义接收端的Socket的引用;
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+"\r\n"+data);
}
}
catch (Exception e)
{
throw new RuntimeException("接收端失败");
}
}
}
class ChatDemo
{
public static void main(String[] args)throws Exception
{
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(10010);
new Thread(new Send(sendSocket)).start();//发送数据的线程;
new Thread(new Rece(receSocket)).start();//接收数据的线程;
}
}

    TCP传输
  TCP传输是一种面向连接的进行大量数据传输的可靠协议,数据的传输依赖于传输通道,在没有建立连接的情况下是无法完成数据传输的,UDP分为发送端和接收端,而TCP分为客户端和服务端,客户端和服务端作为两个端点,建立完之后就可以进行数据的传输,客户端建立的目的就是为了连接服务端,如果服务端都不存在,那么,客户端的建立是没有意义的,客户端对应的对象是Socket,服务端对应的对象是ServerSocket     客户端     通过查阅socket对象,发现在该对象建立时,就可以去连接指定主机,因为tcp是面向连接的,所以在建立socket服务时,就要有服务端存在,并连接成功,形成通路后,在该通道进行数据的传输。     Socket一旦建立成功,就有了一个Socket流,Socket既有输入流,也有输出流。     创建客户端步骤:1,建立客户端socket服务,并指定目的端主机和端口;
                    2,获取socket对象的输出流,将数据进行发送
                    3,关闭资源
    下面对客户端的创建进行演示:
import java.io.*;
import java.net.*;
class TcpClient
{
public static void main(String[] args) throws Exception
{
//创建客户端的socket服务。指定目的主机和端口
Socket s = new Socket("127.0.0.1",10003);
//为了发送数据,应该获取socket流中的输出流。
OutputStream out = s.getOutputStream();
//往输出流中写入数据,进行发送;
out.write("tcp ge men lai le ".getBytes());
s.close();
}
}

    服务端
    一个服务端的存在,会连接很多客户端进来,连接这么多客户端会不会冲突呢?其实服务端这边没有流对象,它是通过获取客户端对象Socket来判断是哪一个客户端连了进来。     建立服务端的步骤:1,建立服务端的serversocket服务,并监听一个端口;
                      2,获取客户端socket对象,通过服务端的ServerSocket的
                      accept方法,这个方法是阻塞式的,客户端没法送数据过来                       ,就会等;
                      3,如果客户端发送数据过来,服务端要使用对应的客户端
                      socket对象,获取客户端的socket读取流;
                      4,关闭资源(可选操作),服务器一般不会关。
    对服务端的创建进行演示:
import java.io.*;
import java.net.*;
class TcpServer
{
public static void main(String[] args) throws Exception
{
//建立服务端socket服务。并监听一个端口。
ServerSocket ss = new ServerSocket(10003);
//通过accept方法获取连接过来的客户端对象。
Socket s = ss.accept();
String ip = s.getInetAddress().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));
s.close();//关闭客户端;
}
}

    服务端和客户端的互访
    客户端:
    1,建立Socket服务,并指定目的端的主机和端口;
    2,通过socket对象获取socket服务的输出流,将数据发送给服务端;
    3,通过socket对象获取socket服务的读取流,对服务端返回来的信息进行读取;
    4,关闭资源。
    服务端:
    1,建立服务端的Serversocket服务,并监听一个端口;
    2,通过ServerSocket对象的accept方法,获取客户端的socket对象;
    3,如果客户端发过来数据,服务端获取对应的客户端的读取流,对客户端发来的数
    据进行读取并打印;
    4,服务端获取对应的客户端输出流,给客户端返回信息;
    5,关闭资源。
import java.io.*;
import java.net.*;
class Client
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("127.0.0.1",10003);
OutputStream os = s.getOutputStream();
os.write("服务端你好".getBytes());
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);//这个read方法是阻塞式的,服务端不发数据过来,这边就一直等
String data = new String(buf,0,len);
System.out.println(data);
}
}
class Server//记住,要先启动服务端
{
public static void main(String[] args)throws Exception
{
ServerSocket ss = new ServerSocket(10003);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"...........is connected");
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String data = new String(buf,0,len);
System.out.println(data);
Thread.sleep(3000);//在服务端这头读到数据后停上3秒钟,在给客户端反馈信息,这就证明了客户端的read方法是阻塞式的
OutputStream os = s.getOutputStream();
os.write("客户端你也好".getBytes());
s.close();
ss.close();
}
}

    练习:建立一个文本转换服务器,客户端给服务端发送文本,服务端将文本转换成大写,在返回给客户端,而且客户端可以不断进行文本转换,直到输入over后结束。
    客户端:
    源:键盘录入,   目的:网络输出流,   是纯文本操作,需要提高效率
    步骤:1,建立socket服务,并指定目的端的主机和端口
          2,获取键盘录入
          3,通过socket对象获取socket服务的输出流,将从键盘录入读取到的数据发
          送给服务端;
          4,通过socket对象获取socket服务的读取流,读取服务端返回的数据;
          5,关闭资源;
    服务端:
    源:网络读取流   目的:网络输出流   是纯文本操作,需要提高效率;
    步骤:1,建立socket服务,并监听一个端口
          2,通过serversocket服务的accept方法获取客户端的socket对象
          3,通过对应的客户端socket对象获取对应的读取流;对客户端发送过来的数
          据进行读取
          4,通过对应的客户端socket对象获取对应的输出流,将数据转换成大写后返
          回给客户端;
          5,关闭资源;
    现象:发现客户端和服务端两端都在等待,因为客户端和服务端都是阻塞式方法,客户端在等着读键盘录入,而服务端在等着网络读取流读取到数据,这时只要开始键盘录入,就可以激活服务端的readLine方法。

import java.io.*;
import java.net.*;
class TransClient
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("127.0.0.1",10003);
//键盘录入
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//将键盘录入数据写入到socket流的输出流中
//方法一:BufferedWriter bufOut = new BufferedWriter(new OutputStream(s.getOutputStream()));
//方法二:通过打印流将数据发送出去;
PrintWriter out = new PrintWriter(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);
//bufOut.newLine();
//bufOut.flush();
out.println(line);
String str = bufIn.readLine();
System.out.println(str);
}
bufr.close();
s.close();
}
}
class TransServer
{
public static void main(String[] args)throws Exception
{
//建立服务端socket服务,并监听一个端口
ServerSocket ss = new ServerSocket(10003);
//获取客户端传入的socket对象,通过其socket流中的输入流获取数据
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+".......connected");
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
//方法一:通过缓冲区发送数据;
//BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//方法二:通过打印流发送数据;
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line=bufr.readLine())!=null)
{
//bufOut.write(line.toUpperCase());
//bufOut.newLine();
//bufOut.flush();
System.out.println(line);
out.println(line.toUpperCase());
}
s.close();
ss.close();
}
}

    练习:TCP上传文件

import java.io.*;
import java.net.*;
class Client
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("127.0.0.1",10005);
BufferedReader bufr = new BufferedReader(new FileReader("我要上传的文件.java"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line=bufr.readLine())!=null)
{
out.println(line);
}
//s.shutdownOutput();//关闭客户端输出流,相当于在流中加入一个结束标记-1;
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String info = bufIn.readLine();//这里的readLine方法也在等着读服务端返回的信息;
System.out.println(info);
bufr.close();
s.close();
}
}
class Server
{
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+".......is connected");
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(new FileWriter("我上传的文件.java"),true);
String line = null;
while((line=bufr.readLine())!=null)//循环停不下来的原因就是这里还在等着读数据;
{
pw.println(line);//如果客户端输出流没结束标记,这个循环是停不下来的;
}
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("文件上传成功");//循环不能结束,所以程序也就走不到这里来,就不会给客户端反馈信息,造成客户端的readLine方法也在等待;
pw.close();
s.close();
ss.close();
}
}

    在我们上边的代码中有多个阻塞式方法:客户端中的两个readLine方法,服务端中的readLine方法,这些方法在没读到数据时就会等待。
    如果我们没有给客户端的输出流定义一个结束标记,那么在服务端的while循环读取客户端发送过来的数据时没有接收到结束标记,循环就停不下来,程序也就无法向下继续只执行,那么给客户端发送反馈信息的语句就执行不到,客户端等待读取反馈信息的readLine方法也会陷入等待中,这样一来,服务端和客户端都陷入了等待状态:     黑马程序员java学习笔记——网络编程
    怎么解决这个问题呢?我们可以想办法在客户端的输出流定义一个结束标记,让服务端读取到,但是,不能直接给服务端发送一个"over"这样的结束标记,因为可能在我们的文件中会出现这样的单词,就会造成服务端读取数据的提前结束,我们可以用shutdownOutput这个方法,相当于告诉服务端数据发送完了,你不要再读了,这样服务端就不会在对对数据进行读取了,程序也能继续向下执行:     黑马程序员java学习笔记——网络编程     Tcp上传图片
import java.io.*;
import java.net.*;
class PicClient
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("127.0.0.1",10008);
FileInputStream fis = new FileInputStream("Demo1.jpg");
OutputStream out = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1)
{
out.write(buf,0,len);
}
s.shutdownOutput();//告诉服务端数据已写完
InputStream in = s.getInputStream();
byte[] bufIn = new byte[1024];
int num = in.read(bufIn);
System.out.println(new String(bufIn,0,num));
fis.close();
s.close();
}
}
class PicServer
{
public static void main(String[] args)throws Exception
{
ServerSocket ss = new ServerSocket(10008);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......connected");
InputStream in = s.getInputStream();
FileOutputStream fos = new FileOutputStream("我上传的图片.jpg");
byte[] buf = new byte[1024];
int len = 0;
while((len=in.read(buf))!=-1)
{
fos.write(buf,0,len);
}
OutputStream out = s.getOutputStream();
out.write("文件上传成功".getBytes());
fos.close();
s.close();
ss.close();
}
}

    其实上传图片和上边的上传文件原理是一样的,都需要在客户端定义一个结束标记,只不过上传图片需要使用字节流,而文本文件可以使用字符流。
    TCP并发上传图片     为了让其他人也能上传图片到服务端,我们可以用一个while循环,重复的获取客户端,但是这个服务端有局限性,当A客户端连接上以后,被服务端获取到,服务端执行具体流程,这是B客户端连接,只能等待A客户端执行完,以为服务端还没处理完A客户端的请求,还要循环回来执行下次accept方法,所以暂时获取不到B客户端的对象,那么为了让每个客户端同时并发访问服务端,那么服务端最好就是将每一个客户端定义到单独的线程中,这样就可以同时处理多个客户端的请求,所有服务器的原理都是这个。     那么如何定义线程呢?     只要明确了每一个客户端要在服务端执行的代码即可,将该代码存入run方法中。
import java.io.*;
import java.net.*;
class PicThread implements Runnable
{
private Socket s;
PicThread(Socket s)
{
this.s = s;
}
public void run()
{
String ip = s.getInetAddress().getHostAddress();
int count = 1;
try
{
System.out.println(ip+"......connected");
File file = new File(ip+".jpg");//将客户端的ip作为上传图片的名称;
while(file.exists())
file = new File(ip+"("+(count++)+").jpg");//避免客户端上传文件发生覆盖;
InputStream in = s.getInputStream();
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=in.read(buf))!=-1)
{
fos.write(buf,0,len);
}
OutputStream out = s.getOutputStream();
out.write("上传图片成功".getBytes());
fos.close();
s.close();
}
catch (Exception e)
{
throw new RuntimeException("上传图片失败");
}
}
}
class PicServer
{
public static void main(String[] args)throws Exception
{
ServerSocket ss = new ServerSocket(10009);
while(true)
{
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
class PieClient
{
public static void main(String[] args)throws Exception
{
if(args.length!=1)
{
System.out.println("请上传一个图片文件");
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格式的图片");
return;
}
if(file.length()>1024*1024*3)
{
System.out.println("图片太大,没安好心");
return;
}
Socket s = new Socket("192.168.1.102",10009);
FileInputStream fis = new FileInputStream(file);
OutputStream out = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1)
{
out.write(buf,0,len);
}
s.shutdownOutput();
InputStream in = s.getInputStream();
byte[] bufIn = new byte[1024];
int num = in.read(bufIn);
System.out.println(new String(bufIn,0,num));
fis.close();
s.close();
}
}

    TCP客户端并发登陆
    客户端通过键盘录入用户名,服务端对这个用户名进行校验,如果该用户存在,在服务端显示xxx,已登陆,并在客户端显示 xxx,欢迎光临,如果该用户存在,在服务端显示xxx,尝试登陆,并在客户端显示 xxx,该用户不存在,最多就登录三次。
import java.io.*;
import java.net.*;
class LoginClient
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("127.0.0.1",11010);
BufferedReader bufIn = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int x=0;x<3;x++)
{
String line = bufIn.readLine();//如果不判断,ctrl+c,返回的是-1,而readLine方法返回的是null,这时客户端就把null发到服务端去了
if(line==null)
break;
out.println(line);
String info = bufr.readLine();
System.out.println("info:"+info);//如果返回了欢迎您登录,那么客户端就不需要再录入用户名了。所以也要判断
if(info.contains("欢迎"))
break;
}
bufIn.close();
s.close();
}
}
class LoginServer
{
public static void main(String[] args)throws Exception
{
ServerSocket ss = new ServerSocket(11010);
while(true)
{
Socket s = ss.accept();
new Thread(new LoginThread(s)).start();
}
}
}
class LoginThread implements Runnable
{
private Socket s;
LoginThread(Socket s)
{
this.s = s;
}
public void run()
{
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......connected");
try
{
for(int x=0;x<3;x++)
{
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String name = bufr.readLine();
if(name==null)//这里也要进行判断,如果客户端按的ctrl+c,这里会读到null....尝试登录,虽然客户端已结束,但服务端不会停止,会再度一次,结果还是null,else中又会打印;
break;
BufferedReader bufIn = new BufferedReader(new FileReader("user.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
boolean flag = false;//定义一个标记,根据循环后的结果来判断;
String line = null;
while((line=bufIn.readLine())!=null)
{
if(line.equals(name))
{
flag = true;
break;
}
}
if(flag)
{
System.out.println(name+",已登录");
out.println("欢迎光临");
break;
}
else
{
System.out.println(name+"尝试登录");
out.println("该用户名不存在");
}
}
s.close();
}
catch (Exception e)
{
throw new RuntimeException(ip+"校验失败");
}
}
}

    浏览器客户端----自定义服务端
    我们上网使用的浏览器就是一个客户端,现在我们可以自己建立服务端,然后在浏览器中输入服务端的ip地址和对应的端口号,就能访问我们自定义的客户端。     演示客户端和服务端。     客户端:浏览器;     服务端:自定义。    
import java.net.*;
import java.io.*;
class ServerDemo
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(11000);
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress());
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("<font color='red' size='7'>老板你好</font>");
s.close();
ss.close();
}
}

    Tomcat服务端
    Tomcat服务器是纯java编写的服务器。     http请求消息头:浏览器和服务器虽然都属于不同厂商,但是它们都遵从一些协议规则,底层传输规则是Tcp,在应用层遵从的是http协议,它是一个公共的传输规则,浏览器要向和不同厂商的服务器进行数据交互,就必须遵从一定的传输规则。     自定义浏览器
import java.io.*;
import java.net.*;
class MyIE
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("192.168.1.254",8080);
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("GET /myweb/demo.html HTTP/1.1");//这个必须写上;
out.println("Accept: */*");
out.println("Accept-Language: zh-cn");
out.println("Host: 192.168.1.254:11000");
out.println("Connection: closed");
//以上的部分是请求消息头;
out.println();
out.println();//注意这里必须空一行,请求数据头和请求数据体必须隔开;
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line);//读取Tomcat返回的数据;
}
s.close();

}
}

    发送过去之后,Tomcat服务器会返回一段信息,这段信息叫应答消息头。
         URL-URLConnection     URL代表统一资源定位符,URL就是用一种统一的格式来描述各种信息资源,包括文件、服务器的地址、端口号和目录等。URL的格式URL的格式由下列三部分组成:第一部分是协议,第二部分是存有该资源的主机IP地址和端口号),第三部分是主机资源的具体地址,如目录和文件名等。第一部分和第二部分之间用“://”符号隔开,第二部分和第三部分用“/”符号隔开。     URI和URL的区别:URI是通用资源标识符,它的范围币URL的大。     URL中的方法     构造方法     URL(String spec):根据 String 表示形式创建 URL 对象;
    URL(String protocol, String host, int port, String file):
    根据指定 protocol、host、port 号和 file 创建 URL 对象;     String getFile():获取此URL的文件名; 
    String getHost():获取此URL的主机名(如果适用); 
    String getPath():获取此URL的路径部分; 
    int getPort():获取此URL的端口号; 
    String getProtocol():获取此URL的协议名称; 
    String getQuery():获取此URL的查询部。
    演示:
import java.net.*;
class URLDemo
{
public static void main(String[] args) throws MalformedURLException
{
URL url = new URL("http://127.0.0.1/myweb/demo.html?name=haha&age=30");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
}
}

    黑马程序员java学习笔记——网络编程
    String getFile():可以拿到后边的参数name=haha&age=30;     String getPath():拿不到后边的参数;     getQuery():得到的就是后边的参数,有些时候往服务端提交时,需要带着这些特定信息,那么这时就用加参数的形式把信息提交到服务端去。     当没有指定端口时,获取的端口号就是-1,端口为-1时,会定义一个默认端口号80。     URLConnection     URL中有一个openConnection方法,返回的是一个URLConnection对象,只要调用本方法,就回去连接URL构造函数中指定的主机,获取主机连接对象,这个对象在内部完成了连接动作,就不需要在用Socket了,这个对象已经帮我们连出去了,而且它是带着协议封装的,以前用Socket在传输层,而现在它将Socket封装进内部,用的是http封装,现在就到应用层了。     这个对象把Socket封装进内部,然后把Socket的读取流InputStream和输出流OutputStream返回给我们。     URLConnection的子类:HttpURLConnection和JarURLConnection。     演示:
import java.net.*;
import java.io.*;
class URLConnectionDemo
{
public static void main(String[] args) throws Exception
{
URL url = new URL("http://127.0.0.1:8080/myweb/demo.html");
URLConnection conn = url.openConnection();
System.out.println(conn);
InputStream in = conn.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
}
}

    运行结果:        
    黑马程序员java学习笔记——网络编程
    我们发现通过这种方式,服务端返回的应答消息头消失了,URLConnection内部封装的http协议把这个消息头给去掉了,只返回数据主体部分的内容。     URL中还有一个openConnection方法,该方法返回的是一个Socket的InputStream流对象,其实就相当于openConnection().getInputStream()的简写,但是获取URLConnection中有更多的方法可以操作。     练习: 自定义图形化界面浏览器
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
class MyIE
{
private Frame f;
private TextField tf;
private Button but;
private TextArea ta;
private Dialog d;
private Label lab;
private Button okBut;
MyIE()
{
init();
}
public void init()
{
f = new Frame("my window");
f.setBounds(300,100,600,500);
f.setLayout(new FlowLayout());
tf = new TextField(60);
but = new Button("转到");
ta = new TextArea(25,70);
d = new Dialog(f,"提示信息-self",true);
d.setBounds(400,200,240,150);
d.setLayout(new FlowLayout());
lab = new Label();
okBut = new Button("确定");
d.add(lab);
d.add(okBut);
f.add(tf);
f.add(but);
f.add(ta);
myEvent();
f.setVisible(true);
}
private void myEvent()
{

okBut.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
d.setVisible(false);
}
});
d.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
d.setVisible(false);
}
});
tf.addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent e)
{
try
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
showDir();
}
catch (Exception ex)
{
throw new RuntimeException("连接失败");
}
}
});
but.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
showDir();
}
catch (Exception ex)
{
throw new RuntimeException("连接失败");
}
}
});

f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
private void showDir()throws Exception
{
ta.setText("");
String urlPath = tf.getText();//从文本框中获取地址;
URL url = new URL(urlPath);//将地址封装到URL中;
URLConnection conn = url.openConnection();//建立Connection;
InputStream in = conn.getInputStream();//获取Socket读取流;
byte[] buf = new byte[1024];
int len = in.read(buf);
ta.setText(new String(buf,0,len));//将获取到的数据添加进文本区;
}
public static void main(String[] args)
{
new MyIE();
}
}

    小知识点:我们在进行Socket对象建立时,它有一个不需要主机、端口的一个空参数的构造函数,用这种方式构造的对象要调用一个方法去连接,这个方法是connect(SocketAddress endpoint),这里边的SocketAddress和InetAddress的区别就在于,这个对象的子类InetSocketAddress里边封装的是IP地址和端口,而InetAddress代表的是地址。
    ServerSocket这个类有一个构造函数ServerSocket(int port, int backlog),这个backlog代表的是队列的最大长度,意思就是能同时连接到服务器的客户端最大个数,简单的说也就是同时连接的最多人数。     域名解析     我们在往浏览器中写入一个网址去访问某一台主机时,这句话做了什么事情呢?     浏览器解析完地址后,先看是什么协议,看完是什么协议后浏览器会启动对应的协议,并解析后边的主机和端口,因为地址不方便我们记忆,所以写的时候我们往往写的是主机名,访问这台主机前,需要把主机名翻译成ip地址,这时就需要使用DNS服务器对域名进行解析,当我们输入的是主机名时,浏览器就会先去共网上找一台域名解析服务器,这个服务器中放的是Ip地址和主机名的映射表,浏览器会先去找DNS,DNS把地址返回来,然后浏览器再通过这个地址给对应的主机发请求,在我们的主机上可以自己指定DNS服务器,只要是公网中的都能用,但是如果该服务器没有记录我们输入的主机名和地址,访问就会失败。     127.0.0.1和localhost是对应的,它们之间的映射关系就在本机上的c:\windows\system32\drivers\etc\hosts这个文件中。当我去访问主机时,先访问的是本地这个文件,这个文件中找到了这个主机名,而且还有对应的地址,那么就走了本地了,如果将某主机名和对应的地址放到本地的hosts这个文件中,在访问该主机时,就不会在去DNS服务器进行域名解析了,直接访问本机上对应的Ip地址,这样做提高了速度,可以用于屏蔽网站,将要屏蔽的主机名的ip地址映射成127.0.0.1就可以了