——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
一、TCP/IP协议和UDP协议
网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。
目前网络模型主要为:OSI模型和TCP/IP模型,具体如下图:
一般来说开发处于传输层和网际层,应用层为:FTP和HTTP协议等,传输层为:UDP和TCP等,网际层为:IP。
TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
总之,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。
二、网络通信
网络通信三要素:IP地址,端口号,传输协议。
IP地址是网络中的设备标识;在java.net包中有个专门对应ip地址的类InetAddress。
主要方法如下:
InetAddress getLocalHost();//方法获取InetAddress对象,此方法是静态的,返回本类对象。
static InetAddress getByName(String host):获取指定主机的IP和主机名。(最好用ip地址去获取,主机名需要解析)
static InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务返回IP地址所组成的数组。返回对象不唯一时,用此方法。
String getHostAddress():返回IP地址字符串文本形式,以IP地址为主。
String getHostName():返回IP地址主机名。
下面是一个使用的方法:
import java.net.*;
class IPDemo{
public static void main(String[] args) throws UnknownHostException{
//通过名称(ip字符串or主机名)来获取一个ip对象。
InetAddress ip = InetAddress.getByName("www.baidu.com");
System.out.println("addr:"+ip.getHostAddress());
System.out.println("name:"+ip.getHostName());
}
}
端口号:用于标识进程的逻辑地址,不用进程的标识。
在电脑中逻辑端口为:0 ~65535,系统使用端口是:0~ 1024。
传输协议就是上面网络编程的俩个主要协议:TCP/IP和UDP
三、UDP传输
UDP传输是通过类DatagramSocket,此类表示用发送和接收数据包的套接字,即Socket。
Socket就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。
注意:DUP传输数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。
UDP的发送端时的步骤及实例代码:
1,建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。
2,明确要发送的具体数据。
3,将数据封装成了数据包。
4,用socket服务的send方法将数据包发送出去。
5,关闭资源。
import java.net.*;
class UdpSend{
public static void main(String[] args)throws Exception {
// 1,建立udp的socket服务。
DatagramSocket ds = new DatagramSocket(8888);//指定发送端口,不指定系统会随机分配。
// 2,明确要发送的具体数据。
String text = "123";
byte[] buf = text.getBytes();
// 3,将数据封装成了数据包。
DatagramPacket dp = new DatagramPacket(buf,
buf.length,InetAddress.getByName("10.1.31.127"),10000);
// 4,用socket服务的send方法将数据包发送出去。
ds.send(dp);
// 5,关闭资源。
ds.close();
}
}
UDP的接受端时的步骤及实例代码:
1,创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个接收端可以处理的数据。
2,定义数据包,用于存储接收到数据。
3,通过socket服务的接收方法将收到的数据存储到数据包中。
4,通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。
5,关闭资源。
class UdpRec {
public static void main(String[] args) throws Exception{
// 1,创建udp的socket服务。
DatagramSocket ds = new DatagramSocket(10000);
// 2,定义数据包,用于存储接收到数据。先定义字节数组,数据包会把数据存储到字节数组中。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
// 3,通过socket服务的接收方法将收到的数据存储到数据包中。
ds.receive(dp);//该方法是阻塞式方法。
// 4,通过数据包的方法获取数据包中的具体数据内容,比如ip,端口,数据等等。
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());//将字节数组中的有效部分转成字符串。
System.out.println(ip+":"+port+" "+text);
// 5,关闭资源。
ds.close();
}
}
四、TCP传输
两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。
TCP发送和接受方不一样,是C/S架构,一个是客户端,一个是服务端。
客户端:对应的对象,Socket。服务端:对应的对象,ServerSocket。
TCP客户端建立及代码:
1,建立tcp的socket服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。
2,如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到socket流中的读取流和写入流即可,只要通过getInputStream和getOutputStream就可以获取两个流对象。
3,关闭资源。
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("发送1".getBytes());
s.close();
}
}
TCP服务端建立及代码:
1,创建服务端socket服务,并监听一个端口。
2,服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象。
3,可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。
4,如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。
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();
}
}
五、综合应用
- 建立一个文本转换服务器
需求:客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。
步骤:
1、建立服务
2、获取键盘录入
3、将数据发给服务端
4、获取服务端返回的大写数据
5、结束,管资源。
import java.io.*;
import java.net.*;
class TcpClient
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket("127.0.0.1",10000);
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
BufferedReader brin=new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while ((line=br.readLine())!=null)
{
if("over".equals(line))
break;
pw.println(line);//将数据写入流中
String data=brin.readLine();//读取返回的信息
System.out.println(data);
}
br.close();
s.close();
}
}
class TcpServer
{
public static void main(String[] args)throws Exception
{
ServerSocket ss =new ServerSocket(10000);
Socket s=ss.accept();
System.out.println(s.getInetAddress().getHostName()+" connected");
BufferedReader brin=new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
String line=null;
while ((line=brin.readLine())!=null)
{
System.out.println(line);
pw.println(line.toUpperCase());//将读到数据转换为大写后返回
}
s.close();
ss.close();
}
}
注意:上面代码会出现等待的未响应。因为客户端和服务端都有阻塞式方法。这些方法没有读到结束标记。那么就一直等。而导致两端都在等待。
此时可以有下面2个方法解决:
方式一:可用高效缓冲区类的newLine()换行作为结束标记,并用flush()进行刷新。
方式二:可用PrintWriter(s.getOutputStrean(),true)创建输出流对象,true作用是刷新,通过打印方法println()换行,“ln”表示换行。
- 利用多线程上传图片
客户端发送和之前没有变化;
服务端 使用一个线程并发接受上传。
import java.io.*;
import java.net.*;
//客户端
class PicClient
{
public static void main(String[] args) throws Exception
{
File file=new File(args[0]);
Socket s=new Socket("localhost",10000);
//读取图片数据
FileInputStream fis=new FileInputStream(file);
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()
{
//获取客户端ip
String 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");
//将数据写入到指定文件中
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+"图片上传失败");
}
}
}
这样的方法可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。