黑马程序员——Java之网络编程
来源:互联网 发布:黑色沙漠男忍捏脸数据 编辑:程序博客网 时间:2024/05/19 05:01
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
内容提要:
网络编程概述
网络模型
Socket
UDP-发送端及接受端
TCP-发送端及接受端
域名解析
网络模型
Socket
UDP-发送端及接受端
TCP-发送端及接受端
域名解析
网络编程概述
网络编程,顾名思义就是面向网络的应用。OSI(Open System Interconnect),是ISO(国际标准化组织)组织在1985年研究的网络互联模型。该体系结构标准定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层和应用层),即ISO开放系统互连参考模型。如下图所示:
OSI模型(开放系统互联)和TCP/IP模型。数据每经过一层都要经过该层功能模型的封装,加上了每一层的特有信息,最后得到了数据封包。数据封包抵达目的机器后,经过每层的“数据拆包”动作并向上传递过程,最后得到了源机器想要发送的原始信息。以上模型理解比较繁琐, 之后产生了TCP/IP模型:主机至网络层、网际层、传输层和应用层。我们所说的网络编程,都在网际层和传输层上进行编程(使用计算机直接地进行数据传输。)。Java web开发是在应用层上进行编程,这种方式是对网络编程的一种封装,应用起来会更加简单。每一层都有自己的通信协议,传输层最常见的协议:TCP/UDP协议;网际层最常见的协议:IP协议;应用层最常见的协议:http、ftp协议。
网络入侵,必须具备和对方机器“沟通”的协议,否则信息无法从本机传输到对方机器。
网络入侵,必须具备和对方机器“沟通”的协议,否则信息无法从本机传输到对方机器。
为了很好理解OSI表述的七层协议内容,做了以下的图解,用公司邮件收发事例模拟该过程,如下图:
通常用户操作的是应用层,而开发人员需要做的工作主要是传输层和网际层,用户在应用层操作的数据经过逐层封包,最后到物理层发送到另一个模型中,再进行逐层解包。
网络编程
网络通信三要素:IP地址、端口号以及传输协议。
其一、IP地址
首先找到对方的IP地址,也就是网络通讯的目的地;在网络通讯的模型,通过IP地址访问其他机器,是网络中设备标识。
网络中设备的标识;不易记忆,可用主机名;本地回环地址:127:0:0:1,主机名:localhost。
在Java中封装成了InetAddress类。
InetAddress类:
其一:无构造函数,可通过getLocalHost()方法获取InetAddress对象,此方法是静态的,返回本类对象。
InetAddress i = InetAddress.getLocalHost();
其二:
1)static InetAddress getByName(String host):获取指定主机的IP和主机名(最好用ip地址去获取,主机名需要解析)。
2)static InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务返回IP地址所组成的数据,返回对象不唯一时,用此方法。
3)String getHostAddress():返回IP地址字符串文本形式,以IP地址为主。
4)String getHostName():返回IP地址主机名。
如何获取任意一台主机的IP地址对象,该功能返回InetAddress对象;对于任意主机,需要制定传入主机名的参数。
需要注意的是:如果IP地址和对应的主机名,这种映射关系没有在网络上,就不会解析成功,返回的还是指定的IP。
import java.net.InetAddress;public class NetDemo {public static void main(String[] args) throws Exception {// TODO Auto-generated method stub//获取本类对象InetAddress ia = InetAddress.getLocalHost();//ipString address = ia.getHostAddress();//主机名String name = ia.getHostName();//IP=192.168.1.101name = yankang-PCSystem.out.println("IP="+address+"\tname = "+name); // 通过名称(ip字符串or主机名)来获取一个ip对象InetAddress[] baidu = InetAddress.getAllByName("www.baidu.com");for(InetAddress b:baidu){//ipString adds = b.getHostAddress();//主机名String names = b.getHostName();System.out.println("IP="+adds+"\tname = "+names); }}/* * 测试结果: IP=14.215.177.38name = www.baidu.com * IP=14.215.177.37name = www.baidu.com */}
代码分析:通过名称(ip字符串or主机名)来获取一个ip对象。
其二、端口
端口号:用于标识进程的逻辑地址,不同进程的标识;有效端口:0~65535,其中0~1024为系统使用或保留端口。数据要发送到对方指定的应用程序上,为了标示这些应用程序,给这些网络应用程序都用数字进行标识。为了方便称呼这个数字,就称之为“逻辑端口”(并不是物理的端口)。Web服务端口:80;Tomcat服务器端口:8080;MySQL数据库端口:3306(端口可以修改)。
其三、通信协议
定义通讯规则,也就是“握手协议”。国际组织定义了通用协议TCP/IP。
传输协议一般采用两种:TCP协议和UDP协议。
UDP协议,将数据及源和目的封装成数据包中,不需要建立连接;每个数据包的大小在限制在64K内;因无连接,是不可靠的协议;不需要建立连接,速度快。
TCP协议(通过三次握手建立连接):必须建立连接,形成数据传输的通道;在连接中进行大数据量传输;通过三次握手完成连接,是可靠协议;必须建立连接,效率会稍低,较消耗资源。三次握手:第一次本方发送请求,第二次对方确认连接;第三次本方在此确认连接成功。
通信的步骤:1.找到IP地址;2.数据要发送到对象指定的应用程序,需要指定端口;3.定义通信规则,即协议。
Socket
Socket编程(套接字),即网络编程:Socket就是为网络服务提供的一种机制;
Socket编程(套接字),即网络编程:Socket就是为网络服务提供的一种机制;
Socket含义:每个应用程序都有一个类似于“插座”的程序,而这个插座就是用于通信的,而物理传输介质则是底层的网卡。
两端都有Socket,才能建立通信服务(先有码头,才会有装货和卸货的动作),网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。
使用多线程实现聊天程序:有数据发送和接受功能,这两部分同时执行,一个线程控制发送数据,另一个线程控制接受数据。
使用多线程实现聊天程序:有数据发送和接受功能,这两部分同时执行,一个线程控制发送数据,另一个线程控制接受数据。
UDP-发送端及接受端
UDP分的是:发送端和接收端。
public class NetDemo1 {/* * 通过类DatagramSocket,此类表示用于发送和接受数据包的套接字,即Socket * 步骤: * 1.发送数据 * 建立连接;提供数据,并将数据封装到数据包中;通过socket服务将数据包发送出去 * 2.接受数据 * 定义服务,并监听一个端口;定义数据包,用于存储数据;通过receive方法获取数据;数据显示 * */public static void main(String[] args) throws Exception{}}class UdpSend{public static void main(String[] args) throws Exception{//1.建立udp的socket服务,DatagramSocket ds = new DatagramSocket();//指定发送端口,不指定系统会随机分配。//2.明确要发送的具体数据String text = "udp传输演示 哥们来了";byte[] buf = text.getBytes();//3.将数据封装成了数据包,DatagramPacket表示数据报包用来实现无连接包投递服务DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);//4.用socket服务的send方法将数据包发送出去ds.send(dp);//5.关闭资源ds.close();}}class UdpReceive{public static void main(String[] args) throws Exception{while(true){//1.建立udp的socket服务,DatagramSocket ds = new DatagramSocket(10000);//监听端口:10000//2.定义数据包,用于存储数据byte[] buf = new byte[1024];DatagramPacket dp = new DatagramPacket(buf,buf.length);//3.用socket服务的receive方法将接受到的数据包存储ds.receive(dp);//4.通过数据包的方法获取其中的数据信息String ip = dp.getAddress().getHostName();String data = new String(dp.getData(),0,dp.getLength());int port = dp.getPort();System.out.println(ip+"::"+data+"::"+port);//5.关闭资源ds.close();}}}
需要注意的是:
发送端和接收端是两个独立的运行程序;
在发送端,要再数据包对象中明确目的地IP和端口;
在接受端,要指定监听的端口。
练习1:
/*用键盘录入的方式,来发送数据*/import java.net.*;import java.io.*;public class NetDemo2 {public static void main(String[] args) {// TODO Auto-generated method stub}}//UDP 发送端class UdpSend2{public static void main(String[] args)throws Exception{//1、创建udp Socket服务DatagramSocket ds=new DatagramSocket(9999);//2、确定数据,从键盘录入,并把键盘录入的数据封装成数据包DatagramPacket dp=null;BufferedReader br=new BufferedReader(new InputStreamReader(System.in));String line=null;while((line=br.readLine())!=null){byte[] buf=line.getBytes();dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10000);//3、通过Socket服务,将已有的数据包发送出去ds.send(dp);if ("886".equals(line)){break;}}//4、关闭资源ds.close();}}//UDP 接收端class UdpReceive2{public static void main(String[] args)throws Exception{//1、创建udp Socket服务DatagramSocket ds=new DatagramSocket(10000);//一直处于接收状态while (true){//2、定义数据包,用于存储数据byte[] buf=new byte[1024];DatagramPacket dp=new DatagramPacket(buf,buf.length);//3、通过Socket服务,将数据接收并存储进数据包中ds.receive(dp);//4、通过数据包的方法获取其中的数据String ip=dp.getAddress().getHostAddress();String data=new String(dp.getData(),0,dp.getLength());int port=dp.getPort();System.out.println("IP:"+ip+"=="+data);}//5、关闭资源//ds.close();}}练习2:
import java.io.BufferedReader;import java.io.InputStreamReader;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;/*编写一个聊天程序。有收数据的部分,和发数据的部分。这两部分需要同时执行。那就需要用到多线程技术。一个线程控制收,一个线程控制发。因为收和发动作是不一致的,所以要定义两个run方法。而且这个两个方法要封装到不同的类中。*/public class NetDemo3 {public static void main(String[] args) {// TODO Auto-generated method stub}}//Udp发送线程class UdpSend3 implements Runnable {// 定义Socket服务引用private DatagramSocket ds;UdpSend3(DatagramSocket ds){this.ds=ds;}// 复写run方法public void run() {try {// 2、确定数据,从键盘录入,并把键盘录入的数据封装成数据包DatagramPacket dp = null;BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String line = null;while ((line = br.readLine()) != null) {byte[] buf = line.getBytes();dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.1.255"), 10000);// 3、通过Socket服务,将已有的数据包发送出去ds.send(dp);if ("886".equals(line)) {break;}}// 4、关闭资源ds.close();} catch (Exception e) {throw new RuntimeException("发送数据失败");}}}// Udp接收线程class UdpReceive3 implements Runnable {// 定义Socket服务引用private DatagramSocket ds;UdpReceive3(DatagramSocket ds){this.ds=ds;}// 复写run方法public void run() {try {// 一直处于接收状态while (true) {// 2、定义数据包,用于存储数据byte[] buf = new byte[1024];DatagramPacket dp = new DatagramPacket(buf, buf.length);// 3、通过Socket服务,将数据接收并存储进数据包中ds.receive(dp);// 4、通过数据包的方法获取其中的数据String ip = dp.getAddress().getHostAddress();String data = new String(dp.getData(), 0, dp.getLength());int port = dp.getPort();System.out.println("IP:" + ip + "==" + data);}// 5、关闭资源// ds.close();} catch (Exception e) {throw new RuntimeException("接收端接收数据失败");}}}class UdpChatDemo {public static void main(String[] args) throws Exception {new Thread(new UdpSend3(new DatagramSocket())).start();new Thread(new UdpReceive3(new DatagramSocket(10000))).start();}}
TCP-发送端及接受端
TCP:客户端对应的是Socket端和服务器端对应的是ServerSocket端;建立连接后,通过Socket中的IO流。
TCP:客户端对应的是Socket端和服务器端对应的是ServerSocket端;建立连接后,通过Socket中的IO流。
相关方法:
1.创建客户端对象:
Socket():创建空参数的客户端对象,一般用于服务端接受数据;
Socket(String host, int port):指定要接收的IP地址和端口号;
2.创建服务端对象:ServerSocket(int port):指定接收的客户端的端口;
3.Sokcet accept():监听并接收到此套接字的连接;
4.void shutdownInput():此套接字的输入流置于流末尾;
5.void shutdownOutput():禁用此套接字的输出流;
6.InputStream getInputStream():返回此套接字的输入流;
7.OutputStream getOutputStream():返回套接字的输出流。
基本思路
客户端:
1)客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。
2)连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
3)与服务端通讯结束后,关闭Socket。
服务端:
1)服务端需要明确它要处理的数据是从哪个端口进入的。
2)当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。
3)当该客户端访问结束,关闭该客户端。
客户端:
1)客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。
2)连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
3)与服务端通讯结束后,关闭Socket。
服务端:
1)服务端需要明确它要处理的数据是从哪个端口进入的。
2)当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。
3)当该客户端访问结束,关闭该客户端。
创建客户端:通过查阅socket对象,发现在该对象建立时,就可以去连接指定主机,因为tcp是面向连接的,在建立socket服务时就要有服务端存在,并连接成功。形成通路后,在该通道进行数据传输。
import java.net.*;import java.io.*;//需求:客户端给服务器端发送一个数据。class TcpClient{public static void main(String[] args) throws Exception{Socket s = new Socket("10.1.31.69",10002);OutputStream out = s.getOutputStream();//获取了socket流中的输出流对象。out.write("tcp演示,哥们又来了!".getBytes());s.close();}}代码分析:TCP传输中的客户端。
class TcpServer{public static void main(String[] args) throws Exception{ServerSocket ss = new ServerSocket(10002);//建立服务端的socket服务Socket s = ss.accept();//获取客户端对象String ip = s.getInetAddress().getHostAddress();System.out.println(ip+".....connected");//可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。InputStream in = s.getInputStream();//读取客户端的数据,使用客户端对象的socket读取流byte[] buf = new byte[1024];int len = in.read(buf);String text = new String(buf,0,len);System.out.println(text);//如果通讯结束,关闭资源。注意:要先关客户端,在关服务端。s.close();ss.close();}}代码分析:TCP传输中的服务端代码。服务器端出的异常现象:客户端和服务端都在莫名的等待。为什么?因为两端都有阻塞式方法,这些方法都没有读到结束标记,导致两端都在等待。
/*需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息。*/import java.net.*;import java.io.*;public class NetDemo4 {public static void main(String[] args) {// TODO Auto-generated method stub}}//客户端class TcpClient{public static void main(String[] args) throws Exception{//1、创建客户端的Socket服务。指定目的主机和端口Socket s=new Socket("127.0.0.1",10000);//2、获取Socket流中输出流,发送数据OutputStream out=s.getOutputStream();out.write("你好!".getBytes());//3、获取Socket流中的输入流,用来接收服务端的反馈信息并打印InputStream in=s.getInputStream();byte[] buf=new byte[1024];int len=in.read(buf);//读取反馈的数据//输出接收的数据System.out.println(new String(buf,0,len));s.close();//关闭资源}}//服务端class TcpServer{public static void main(String[] args)throws Exception{//1、创建服务端的Socket服务,并监听一个端口ServerSocket ss=new ServerSocket(10000);//2、通过accept方法获取连接过来的客户端对象。Socket s=ss.accept();//获取客户端ipString ip=s.getInetAddress().getHostName();System.out.println(ip+"connected....");//3、获取对应客户端对象的读取流读取发过来的数据,并打印InputStream in=s.getInputStream();byte[] buf=new byte[1024];int len=in.read(buf);System.out.println(new String(buf,0,len));//4、调用对应的客户端的输出流写入返回数据OutputStream out=s.getOutputStream();out.write("哥们,收到!".getBytes());//关闭资源s.close();ss.close();//可选操作}}练习一:
/*练习需求:建立一个文本转换服务器客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。此练习出现的问题:现象:客户端和服务端都在莫名的等待。原因:因为客户端和服务端都有阻塞式方法。这些方法没有读到结束标记。那么就一直等。而导致两端都在等待。解决:需要用到刷新和换行的方式将写入和读取的数据从流中刷新到内存中方式一:可用高效缓冲区类的newLine()换行作为结束标记,并用flush()进行刷新。方式二:可用PrintWriter(s.getOutputStrean(),true)创建输出流对象,true作用是刷新,通过打印方法println()换行,“ln”表示换行。*/import java.io.*;import java.net.*;public class NetDemo5 {public static void main(String[] args) {// TODO Auto-generated method stub}}/* * 分析: 客户端: 既然是操作设备上的数据,那么就可以使用io技术,并按照io的操作规律来思考。 源:键盘录入 目的:网络设备,网络输出流。 * 而且操作的是文本数据。可以选择字符流。 * * 步骤: 1、建立服务 2、获取键盘录入 3、将数据发给服务端 4、获取服务端返回的大写数据 5、结束,管资源。 * * 都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。 */class TcpClient2 {public static void main(String[] args) throws Exception {// 创建Socket服务Socket s = new Socket("127.0.0.1", 10000);// 定义读取键盘数据的流对象BufferedReader br = new BufferedReader(new InputStreamReader(System.in));// 定义目的,将数据写入到Socket输出流。发给服务端// BufferedWriter bwout=new BufferedWriter(new// OutputStreamWriter(s.getOutputStream()));PrintWriter pw = new PrintWriter(s.getOutputStream(), true);// 定义一个Socket读取流,读取服务端返回的大写信息。BufferedReader brin = new BufferedReader(new InputStreamReader(s.getInputStream()));String line = null;while ((line = br.readLine()) != null) {if ("over".equals(line))break;// bwout.write(line);//写入输出流// bwout.newLine();//换行// bwout.flush();//刷新pw.println(line);// 将数据写入流中String data = brin.readLine();// 读取返回的信息System.out.println(data);}br.close();// 关流s.close();}}/* * 服务端: 源:Socket读取流 目的:Socket输出流 都是文本,装饰 */class TcpServer2 {public static void main(String[] args) throws Exception {// 创建服务端的ServerSocket服务,并指定监听端口ServerSocket ss = new ServerSocket(10000);// 获取客户端连接Socket s = ss.accept();// 获取客户端ipSystem.out.println(s.getInetAddress().getHostName() + " connected.......");// 读取Socket读取流中的数据BufferedReader brin = new BufferedReader(new InputStreamReader(s.getInputStream()));// 将大写数据写入到Socket输出流,并发送给客户端。// BufferedWriter bwout=new BufferedWriter(new// OutputStreamWriter(s.getOutputStream()));PrintWriter pw = new PrintWriter(s.getOutputStream(), true);String line = null;while ((line = brin.readLine()) != null) {System.out.println(line);// bwout.write(line.toUpperCase());//将读到数据转换为大写后返回// bwout.newLine();//换行// bwout.flush();//刷新pw.println(line.toUpperCase());// 将读到数据转换为大写后返回}s.close();// 关流ss.close(); // 关闭资源(可选)}}练习二:
/*需求:向服务器上传一个文件,服务返回一条信息1、客户端:源:硬盘上的文件;目的:网络设备,即网络输出流。若操作的是文本数据,可选字符流,并加入高效缓冲区。若是媒体文件,用字节流。2、服务端:源:socket读取流;目的:socket输出流。3、出现的问题: 现象:a、文件已经上传成功了,但是没有得到服务端的反馈信息。b、即使得到反馈信息,但得到的是null,而不是“上传成功”的信息原因:a、因为客户端将数据发送完毕后,服务端仍然在等待这读取数据,并没有收到结束标记,就会一直等待读取。b、上个问题解决后,收到的不是指定信息而是null,是因为服务端写入数据后,需要刷新,才能将信息反馈给客服端。解决:方法一:定义结束标记,先将结束标记发送给服务端,让服务端接收到结束标记,然后再发送上传的数据。但是这样定义可能会发生定义的标记和文件中的数据重复,而导致提前结束。 方法二:定义时间戳,由于时间是唯一的,在发送数据前,先获取时间,发送完后在结尾处写上相同的时间戳,在服务端,接收数据前先接收一个时间戳,然后在循环中判断时间戳以结束标记。 方法三:通过socket方法中的shutdownOutput(),关闭输入流资源,从而结束传输流,以给定结束标记。通常用这个方法。*/import java.io.*;import java.net.*;public class NetDemo6 {public static void main(String[] args) {// TODO Auto-generated method stub}}// 客户端class TcpClient3 {public static void main(String[] args) throws Exception {// 创建Socket服务Socket s = new Socket("127.0.0.1", 10000);// 定义读取流读取文件数据BufferedReader br = new BufferedReader(new FileReader("TcpDemo.java"));// 定义目的,将数据写入到Socket输出流。发给服务端// BufferedWriter bwout=new BufferedWriter(new// OutputStreamWriter(s.getOutputStream()));PrintWriter pw = new PrintWriter(s.getOutputStream(), true);// 定义一个Socket读取流,读取服务端返回信息。BufferedReader brin = new BufferedReader(new InputStreamReader(s.getInputStream()));String line = null;while ((line = br.readLine()) != null) {pw.println(line);}s.shutdownOutput();// 关闭客户端的输出流。相当于给流中加入一个结束标记-1.System.out.println(brin.readLine());// 接收返回信息br.close();s.close();}}// 服务端class TcpServer3 {public static void main(String[] args) throws Exception {// 创建服务端的ServerSocket服务,并指定监听端口ServerSocket ss = new ServerSocket(10000);// 获取客户端连接Socket s = ss.accept();// 获取客户端ipSystem.out.println(s.getInetAddress().getHostName() + " connected.......");// 读取Socket读取流中的数据BufferedReader brin = new BufferedReader(new InputStreamReader(s.getInputStream()));// 将接收到的数据写入文件中PrintWriter out = new PrintWriter(new FileWriter("TcpDemo.txt"), true);// 将返回信息写入Socket流的写入流中BufferedWriter bwout = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line = null;while ((line = brin.readLine()) != null) {out.println(line);}// PrintWriter pw = new PrintWriter(s.getOutputStream(),true);// pw.println("上传成功");bwout.write("上传成功!");bwout.newLine();// 换行bwout.flush();// 刷新out.close();// 关流s.close();ss.close();}}网络编程应用
一、用TCP客户端并发上传图片
1、一对一(单线程)上传的思路:
客户端:a、服务端点;b、读取客户端已有的图片数据;c、通过Socket输出流将数据发给服务端;d、读取服务端反馈信息;e、关闭。
服务端:a、服务端服务,并监听窗口;b、获取客户端对象,并获取客户ip;c、读取客户端输入流数据;d、写入文件;e、用客户端输出流反馈信息;f、关流。
2、单线程的服务端有个局限性。当A客户端连接上以后,被服务端获取到。服务端执行具体流程。这时B客户端连接,只能等待。因为服务端还没有处理完A客户端的请求。还没有循环回来执行下一次accept方法。所以,暂时获取不到B客户端对象。
那么为了可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
如何定义线程呢?只要明确了每一个客户端要在服务端执行的代码,将该代码存入run方法即可。
1、一对一(单线程)上传的思路:
客户端:a、服务端点;b、读取客户端已有的图片数据;c、通过Socket输出流将数据发给服务端;d、读取服务端反馈信息;e、关闭。
服务端:a、服务端服务,并监听窗口;b、获取客户端对象,并获取客户ip;c、读取客户端输入流数据;d、写入文件;e、用客户端输出流反馈信息;f、关流。
2、单线程的服务端有个局限性。当A客户端连接上以后,被服务端获取到。服务端执行具体流程。这时B客户端连接,只能等待。因为服务端还没有处理完A客户端的请求。还没有循环回来执行下一次accept方法。所以,暂时获取不到B客户端对象。
那么为了可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
如何定义线程呢?只要明确了每一个客户端要在服务端执行的代码,将该代码存入run方法即可。
/*需求:并发上传图片*/import java.io.*;import java.net.*;public class NetDemo7 {public static void main(String[] args) {// TODO Auto-generated method stub}}// 客户端class PicClient {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("图片格式错误,请重新选择!");return;}// 对文件大小进行判断if (file.length() > 1024 * 1024 * 5) {System.out.println("你上传的文件过大,居心叵测!");return;}// 创建服务Socket s = new Socket("localhost", 10000);// 读取图片数据FileInputStream fis = new FileInputStream(file);// 用Socket服务输出流写入数据OutputStream out = s.getOutputStream();BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));byte[] buf = new byte[1024];int len = 0;while ((len = fis.read(buf)) != -1) {out.write(buf, 0, len);}// 结束标记,表示文件数据已经上传完了s.shutdownOutput();String info = in.readLine();// 读取返回信息System.out.println(info);fis.close();// 关流s.close();}}// 服务端class PicServer {public static void main(String[] args) throws Exception {// 创建服务,监听端口ServerSocket ss = new ServerSocket(10000);while (true) {// 获取客户端对象Socket s = ss.accept();// 客户端执行线程new Thread(new PicThread(s)).start();}// ss.close();}}// 利用多线程实现并发上传class PicThread implements Runnable {private Socket s;PicThread(Socket s) {this.s = s;}public void run() {int count = 1;// 获取客户端ipString ip = s.getInetAddress().getHostAddress();try {System.out.println(ip + " connected.....");// 通过客户端的读取流读取数据InputStream in = s.getInputStream();// 文件保存路径File dir = new File("C:\\Users\\asus\\Desktop");// 文件名File file = new File(dir, ip + ".jpg");// 判断文件是否存在while (file.exists()) {file = new File(dir, ip + "(" + (count++) + ").jpg");}// 将数据写入到指定文件中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(ip + "图片上传失败");}}}二、客户端并发登录
客户端通过键盘录入用户名,服务端对这个用户名进行校验。
如果该用户存在,在服务端显示xxx,已登陆;并在客户端显示xxx,欢迎光临。
如果用户不存在,在服务端显示xxx,尝试登陆;并在客户端显示xxx,该用户不存在。
最多就登录三次。
import java.io.*;import java.net.*;public class NetDemo8 {public static void main(String[] args) {// TODO Auto-generated method stub}}// 客户端class LoginClient {public static void main(String[] args) throws Exception {// 创建服务Socket s = new Socket("localhost", 10000);// 键盘录入BufferedReader br = new BufferedReader(new InputStreamReader(System.in));// 用Socket服务输出流写入数据PrintWriter out = new PrintWriter(s.getOutputStream(), true);// 接收服务器返回的信息BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));String line = null;for (int x = 0; x < 3; x++) {line = br.readLine();// 读取键盘录入if (line == null) {break;// 如果键盘没有输入,则直接结束}out.println(line);// 将数据写入流中String info = in.readLine();// 读取返回信息System.out.println(info);if (info.contains("欢迎"))// ---------------{break;// 如果登录成功,就跳出循环}}br.close();// 关流s.close();}}// 服务端class LoginServer {public static void main(String[] args) throws Exception {// 创建服务,监听端口ServerSocket ss = new ServerSocket(10000);while (true) {// 获取客户端对象Socket s = ss.accept();// 客户端执行线程new Thread(new LoginThread(s)).start();}// ss.close();}}// 利用多线程实现并发登录class LoginThread implements Runnable {private Socket s;LoginThread(Socket s) {this.s = s;}public void run() {// 获取客户端ipString ip = s.getInetAddress().getHostAddress();System.out.println(ip + " connected.....");try {for (int x = 0; x < 3; x++) {// 通过客户端的读取流读取数据BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));// 读取数据库中的数据,这里用文件来表示数据库BufferedReader br = new BufferedReader(new FileReader("users.txt"));String line = in.readLine();// 读取客户端数据if (line == null)// --------------{break;// 如果客户端没有发送数据,则跳出循环}String data = null;boolean flag = false;// 设置标记// 读取数据库中的用户数据while ((data = br.readLine()) != null) {if (line.equals(data)) {flag = true;// 如果用户存在,则将标记设为truebreak;}}// 将数据写入到指定文件中PrintWriter out = new PrintWriter(s.getOutputStream(), true);if (flag) {System.out.println(line + ",已登陆!");out.println(line + ",欢迎光临!");break;// -----------} else {System.out.println(line + ",尝试登陆!");out.println(line + ",用户名不存在!");}}s.close();// 关流} catch (Exception e) {throw new RuntimeException("用户登陆失败");}}}三、客户端和服务的浏览器演示
浏览器是一个标准的客户端,它可以对服务端传送过来的数据消息进行解析,把符合应用层协议的消息部分解析后,将头信息拆包掉,传送到应用层,只保留了正确的正文主题部分显示在主体部分上。
而由于使用java编译是在传输层和网际层处理的,所以,会接受到全部的消息,包含了头消息。而浏览器处于应用层,已将发送来的头消息去除,只留下了主体信息。
import java.io.*;import java.net.*;public class NetDemo9 {public static void main(String[] args) {// TODO Auto-generated method stub}}// 服务器class ServerDemo {public static void main(String[] args) throws Exception {// 创建服务,监听端口ServerSocket ss = new ServerSocket(10000);// 获取客户端Socket s = ss.accept();// 显示ipString ip = s.getInetAddress().getHostAddress();System.out.println(ip);// 读取客户端读取流数据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);/// true一定要记得写out.println("<font color='red' size='7'>客户端你好!</font>");s.close();// 关流ss.close();}}域名解析
想要将主机名翻译成IP地址,需要域名解析——DNS域名解析服务器。使用的是电信的宽带,配置DNS的时候就默认配置为电信的DNS服务器地址,“就近原则”。
0 0
- 黑马程序员————JAVA之网络编程
- 黑马程序员——java之网络编程
- 黑马程序员——JAVA拾遗之网络编程
- 黑马程序员——JAVA基础之网络编程
- 黑马程序员——JAVA笔记之网络编程
- 黑马程序员——Java之网络编程
- 黑马程序员——java学习之网络编程
- 黑马程序员——Java基础之网络编程
- 黑马程序员——Java基础之网络编程
- 黑马程序员——Java之网络编程
- 黑马程序员—java基础之网络编程
- 黑马程序员—JAVA基础之网络编程
- 黑马程序员—【Java基础篇】之网络编程
- 黑马程序员—网络编程之UDP
- 黑马程序员—网络编程之TCP
- 黑马程序员—网络编程之UDP
- 黑马程序员—网络编程之TCP
- 黑马程序员—网络编程之UDP
- iOS应用的crash日志的分析基础
- 关于Autodesk View and Data API的一些问题
- php解析数据格式转化为js数组
- mybatis报错
- iOS中nil/Nil/NULL/NSNull的区别
- 黑马程序员——Java之网络编程
- Neural Networks: Representation
- 电源的输出纹波噪声究竟该取多少才合适?
- Largest prime factor
- winedt 经验
- Hadoop中常用的InputFormat、OutputFormat(转)
- 【iOS】数据库FMDB的使用(一)
- android自定义属性attrs
- openStreetMap下载指定城市