黑马程序员——30,TCP传输

时间:2023-02-21 21:15:48

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

黑马程序员——30,TCP传输

        Socket就是为网络服务提供的一种机制,网络通信就是两个Socket之间的通信(可以理解为端点之间的通信)。UDP传输如此,TCP传输也是如此。

对于TCP传输,则是对应的客户端Socket与服务端ServerSocket。两者是通过流来传输数据的。

 

一:ServerSocket与Socket---->

建立客户端的步骤:

1,建立Socket服务,指定需要连接的主机以及需要连接的端口号。

2,获取相关的流对象。

3,调用流的方法,读取或者写入数据。

4,关闭资源。

(记得要抛出异常)

建立服务端的步骤:

1,  建立ServerSocket服务,指定需要监听的端口号。

2,  通过accept方法获取连接到的客户端的对象。

3,  利用客户端对象,获取相关流对象,这一步和客户端差不多。

4,  调用流对象的方法,读取或写入数据。

5,  关闭资源。

(特别注意的是read与readLine方法都是阻塞式方法,数据来了才会读取,而且readLine方法是需要读到回车符才会停止的。),对于Socket来说,getInetAddress方法可以用来获取IP地址,该方法比较常用,返回的是一个InetAddress对象,以下会有例题讲解。     

客户端与服务端的数据传输关系如图1所示。

黑马程序员——30,TCP传输

                      图1

 

import  java.net.*;
import java.io.*;
class Wuo
{
//客户端
public static void main(String[]args) throws Exception
{
//建立Socket服务,指定需要连接的主机以及需要连接的端口号。
Socket so=new Socket("192.168.1.2",10023);
//获取相关的流对象。
InputStream in= so.getInputStream();
OutputStream out=so.getOutputStream();
/*
如果是需要写入字符串的话,使用PrintWriter更加方便
PrintWriter pw=new PrintWriter(so.getOutputStream(),true);
pw.println("大家好开心!");
*/
//调用流对象的方法读取或者写入数据
out.write("大家好开心!".getBytes());
//关闭资源
so.close();

}

}
class Wuo_1
{
//服务端
public static void main(String[] args)throws Exception
{
//建立ServerSocket服务端,指定需要监听的端口号
ServerSocket serso=new ServerSocket(10023);
//通过accept方法获得连接过来的客户端的对象
Socket so=serso.accept();
//获得客户端对象后再获取相关的流对象,这一步和客户端差不多
InputStream in= so.getInputStream();
OutputStream out=so.getOutputStream();
//调用流对象的相关方法读取或者写入数据
byte[] by=new byte[1024];
int i= in.read(by);
System.out.println(new String(by,0,i));//按照字节长度从第0位开始打印

}

}

        然后也可以利用IO流在客户端和服务端之间进行数据传输,需要注意的就是IO流对象的应用而已,这里就不必多说了。

        不过,客户端与服务端之间的互访中,关于readLine方法的阻塞式,需要知道的小细节:

        目的:客户端向服务端发送一个放在f盘的文件“8月26号.txt”,该txt文件里面写着两行字符串:

        第一行是:一二vjebveqvqev表情包

        第二行是:就了的的就

       写完第二行后我没有按回车符就保存了文件。

 

import  java.net.*;
import java.io.*;
class Wuo
{
//客户端
public static voidmain(String[] args) throws Exception
{
//建立Socket服务,指定需要连接的主机以及需要连接的端口号。
Socket so=new Socket("192.168.1.2",10023);
//获取相关的流对象。
BufferedReader bur=new BufferedReader(new InputStreamReader(so.getInputStream()));
PrintWriter pw=new PrintWriter(so.getOutputStream(),true);
BufferedReader wjin=new BufferedReader(new InputStreamReader(new FileInputStream("f:\\8月26号.txt")));
//调用流对象的方法读取或者写入数据
String s=null;
while((s=wjin.readLine())!=null)//这里的readLine读取的是文件数据,就算是读取数据时最后一行没有回车符,也是可以读取完的
{
pw.println(s);
//这里之所以用PrintWriter是因为其建立带有自动刷新功能流对象
/*
而这里pw.println(s);表示打印后自动换行了,
服务端那边的readLine方法也不会卡住,所以一般输出的最好用PrintWriter类
*/
}
so.shutdownOutput();//关闭客户端的输出流
/*
如果这里不关闭客户端的输出流,那么输出流一直在连接着服务端,
那么服务端那边的readLine方法是阻塞式的,所以会一直等着,
服务端没能读取完,就不会给客户端返回信息。
所以,两边都是一直卡在那里。
*/
String fankuei= bur.readLine();
System.out.println(fankuei);

//关闭资源
so.close();
wjin.close();

}

}
class Wuo_1
{
//服务端
public static void main(String[] args)throws Exception
{
//建立ServerSocket服务端,指定需要监听的端口号
ServerSocket serso=new ServerSocket(10023);
//通过accept方法获得连接过来的客户端的对象
Socket so=serso.accept();
//获得客户端对象后再获取相关的流对象,这一步和客户端差不多
BufferedReader bur=new BufferedReader(new InputStreamReader(so.getInputStream()));
PrintWriter pw=new PrintWriter(so.getOutputStream(),true);
//调用流对象的相关方法读取或者写入数据
String s=null;
while((s=bur.readLine())!=null)
{
System.out.println(s);
}
pw.println("已经收到!");
so.close();
serso.close();

}

}


        如果是上传图片或者其他非字符文件的话,相应的流对象最好还是使用与字节相关的流对象:像是FileInputStream,FileOutputStream之类的,特别需要注意的是,对于字节流,read方法返回的是int型的基本数据类型,表示读取到的是多少个有效位,没有数据读取返回-1。

        而且,读取时可以按照byte数组的长度来一段一段读取源文件数据:

ServerSocket   serso=new  ServerSocket(10023); 
//通过accept方法获得连接过来的客户端的对象
Socket so=serso.accept();
InputStream in=so.getInputStream();
OutputStream out=so.getOutputStream();
byte[] by=new byte[1024];//一般设为1024位长度较为合适
int i=0;
while((i=in.read(by))!=-1)
{
out.write(by,0,i); //刚好写入所需字节长度的字节
}

TCP客户端并发上传图片,重点在于服务端的改写

import  java.util.*;
import java.net.*;
import java.io.*;
class Wuo_1
{
//服务端
public static void main(String[] args) throws Exception
{
ServerSocket serso=new ServerSocket(10013);
while(true)//使得无限循环
{
Socket so=serso.accept();
//accept也是阻塞型的方法,只有获取到客户端对象后才会往下执行,没有的话就会挂在这里不动,所以即便是while无限循环也不会大量消耗资源
new Thread(new Fwd(so)).start();
//有一个客户端进来就分配一个线程执行。
}
}
}
class Fwd implements Runnable
{
//服务端线程
private Socket so;

Fwd( Socket so)
{
this.so=so;
}
public void run()
{
FileOutputStream fos=null;
try
{
//设定计数器
int shuzi=1;
InputStream in=so.getInputStream();
//获取IP地址
String sAddress= so.getInetAddress().getHostAddress();
File f=new File("f:\\8.26_"+sAddress+"_"+shuzi+".png");
//用一个while循环来防止写入数据时可能发生的覆盖情况
while(f.exists())
{
//不同的客户端对应不同的写入文件,防止同名
shuzi++;
f=new File("f:\\8.26_"+sAddress+"_"+shuzi+".png");
}

fos=new FileOutputStream(f);
byte[] by=new byte[1024];
int i=0;
//一边读取一边写入
while((i=in.read(by))!=-1)
{
fos.write(by,0,i);
}

}
catch(Exception e)
{
throw new RuntimeException("服务端出问题");//为了简化代码,这里就简单处理
}
//这种情况下服务器一般不会关闭资源

}

}

}

        另外一道例题:客户端输入名字,服务端拿着客户端给的名字到数据库(这里本人写的时候就简单的用了一个txt记事作为数据库,txt记事本上写有一些人名)那边,把储存的名字一个一个做对比,客户端传来的名字在数据库中有记录,那么服务端返回“欢迎光临”,否则返回“名字不存在”。并且限制了客户端只有三次输入名字的机会。

        从这道题中,应该学会的是一种思维方式。

        因为有限制三次输入名字的机会,对于客户端,有一个for语句,循环次数规定为3次,写法思路大致如下:

for(int  x=0;x<3;x++)

{

第一步:用之前定义的相关流对象读取键盘输入;

第二步:把键盘输入的字符串传送给服务器;

第三步:获取服务器传送过来的反馈

第四步:判断服务器反馈,如果是“欢迎光临”就用break跳出for循环

     

}

        对于服务端,也有一个for语句,写法思路大致如下:

for( int  x=0;x<3;x++   )

{

第一步:用之前定义的相关流对象读取客户端发送过来的字符串

第二步:用相关流对象读取数据库里面的信息

第三步:设定一个标记,通常是boolean型的变量初始化为false

第四步:再用一个for循环遍历数据库的元素,在遍历过程中判断客户端输入名字与其是否相等,如果相等就令第三步设置的boolean型变量更改为true

第五步:第四步的for循环结束后,判断boolean型的变量是否为true,如果是就反馈给客户端“欢迎光临”,并且用break跳出外层for循环,否则反馈客户端“名字不存在”                 

}

        通过大体的思路,我们可以发现如果我们想要得到的是在一个循环比较之后才可以得到的结果(特别是遍历比较数据库或者集合中是否存在某个元素),那么我们事先设定一个标记,一般就是boolean型的变量,并且设定为false,如果在遍历比较过程中某个元素的确存在于数据库中,就更改变量设为true,遍历过后就来一个判断该变量的值,根据这个值来确定数据库中是否存在某个元素。

 

二:浏览器(客户端)与tomcat服务器软件---->

        浏览器也是一个客户端软件。

        由java语言编写的简单客户端程序不能够识别HTML语句,而浏览器则是可以识别HTML语句,例如java服务端需要给客户端传送的是”<font  color=’red’  size=’7’>你好</font>”,如果传送给java客户端就会完整显示”<font  color=’red’  size=’7’>你好</font>”,如果传送给浏览器,那么浏览器显示的是红色字体的”你好”。

        小知识点:DOS命令行telnet,是Windows提供的可以远程登录的工具,可以在DOS命令行下连接网络中的任意一台主机,例如要连接192.168.1.2的10012端口,写法是在DOS窗口中写入telnet  192.168.1.2 10012就可以连接了。并且对这台主机进行命令式的配置。说白了就是类似一个客户端软件。

        打开tomcat服务器后,该软件一般都是使用主机的8080端口号;如果用浏览器访问的话如图2所示。

黑马程序员——30,TCP传输

                               图2

        既然tomcat是一个服务器软件,那么就一定有可以访问的网页资源,没有的话,我们也可以自己在里面创建一个HTML网页,我本人就写了一个简陋的HTML网页并且用浏览器访问tomcat如图3所示。

黑马程序员——30,TCP传输

                         图3

 

 

        如果自定义一个ServerSocket服务端,然后用浏览器来访问,浏览器也是一个客户端,所以会向服务端发送请求消息头,如果有消息主体的话就会发送请求的消息主体。而服务端向浏览器发送应答消息头,并且还发送答应的消息主体。

        TCP数据传输:数据一般从发送端主机的应用层往下走回数据层层封包,通过物理线路来到目的主机底层,再往上走会数据拆包。

        由于浏览器处于最上面的应用层,根据数据往上传输过程中会进行数据拆包,所以消息头就在传输过程中被拆掉了,浏览器显示的也只有消息主体。至于java自定义的服务端是处于传输层和网纪层(这也是前期java编程主要位于的层面),会把所有的数据显示,所以会把消息头也显示出来。特别注意的是,不管是请求还是应答,消息头和消息主体之间需要有一行空行隔开,为了保险起见,编写的时候可以隔开两行。

 

三:URL与DNS---->

        前期java编程如果需要拆掉消息头的话,就不得不用到位于应用层的URL类了。URL是统一资源定位符,是指向了网络资源的指针。另外URI也可以表示资源定位符,范围比URL要大,为了方便理解,举一个例子,如果说URL定位的资源是一本书的字,那么URI定位的资源就是书中的字,甚至还包括了书本封面的代码。

        URL里面封装了Socket,用法非常方便。

import  java.net.*;
class Wuo3
{
public static void main(String[]args) throws MalformedURLException
{
//先用URL指定资源
URL u=new URL("http://192.168.1.2:8080/my_web/lizi.html?name=xiaozhe&age=12");
//其中的问号是间隔资源与相关参数,参数是以键值对形式存在,参数之间以&连接
URLConnection ucon= u.openConnection(); <span style="font-family: Arial, Helvetica, sans-serif;">//表示内部封装的Socket直接连接了指定资源</span>


//获取URL文件名(资源与相关参数全部获取了)
sop(u.getFile());///my_web/lizi.html?name=xiaozhe&age=12

//获取URL主机名
sop(u.getHost());//192.168.1.2

//获取URL路径(不包括参数)
sop(u.getPath());///my_web/lizi.html

//获取URL端口号
sop(u.getPort());//8080

// 获取URL协议名称
sop(u.getProtocol());//http

//获取URL查询部分
sop(u.getQuery());//name=xiaozhe&age=12
}
public static void sop(Object obj)//打印方法
{
System.out.println(obj);
}
}

        因为URL里面封装了Socket,所以用OpenConnection连接之后,也可以直接使用URLConnection类的对象的getInputStream与getOutputStream方法获取相关流对象,非常方便。

Socket的小知识点:

        Socket的建立也可以用不带参数的构造函数建立,不过,需要调用connect方法连接服务器:

        void  connect(SocketAddress  endpoint)

        里面的SocketAddress是一个抽象类,为了方便理解,观察其子类InetSocketAddress,InetSocketAddress把IP地址和端口号封装在一起的,所以,常用的构造函数就是    InetSocketAddress(InetAddress addr,  int  port)

 

ServerSocket的小知识点:

        ServerSocket还有一个常用的构造函数:ServerSocket(int port,int  backlog)

        其中backlog这个数字是限定了可以连接到这个服务端的客户端的个数。

 

DNS小知识点:

        一般人们访问网站的时候都不是输入IP地址号的,例如访问百度输入的是http://www.baidu.com,那么这就需要解析域名,每一个域名都会至少映射一个IP地 址,去那里得到这个IP地址呢?

        这个时候DNS服务器就登场了。

        DNS就是一种专门提供域名解析的服务器,里面存放着很多域名与对应的IP地址,说到这个域名解析,其实就会想到localhost和127.0.0.1,这两者都是指向本机的,并且储存在本机里面。

        这也大致理清了计算机域名解析的过程:先是在本机里面寻找域名对应的IP地址,如果没有才去DNS服务器寻找。

        我们也可以利用这个特点把一些恶意网站的网址存放在本机上,对应的都是127.0.0.1那么到时候浏览器访问这些网址,计算机都会转回本机上。

 

 ------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------