一、网络通讯概述
网络通讯三要素:
1、IP地址:每台主机都有一个唯一的标识,即IP地址;每台计算机都有一个本地回环地址:127.0.0.1,主机名是localhost;
2、端口:数据要发送到对方指定的应用程序上,为了标识这些应用程序,给这些网络应用程序都用数字进行标识,以便区分不同的进程,这个数字标识就叫做端口(有效端口是0---65535,其中0---1024是系统使用或保留的端口);
3、定义网络中数据传输的通讯规则(传输协议):TCP/IP。
Socket:为网络服务提供的一种机制
1、Socket就是两台机器间通信的端点,通信的两端都要有Socket;
2、网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输;
3、传输方式有两种:UDP传输和TCP传输。
二、UDP和TCP各自的特点
1、UDP:用户数据包协议
特点:
提供的是面向无连接的、不可靠的数据流传输;
数据会被封包,包体积限制在64K以内;
UDP在传输数据包之前不用建立连接,所以传输速度很快;
因为无连接,所以并不知道数据包是否到达了目的地。
适用情况:
当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择;
在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择,如:DNS交换。
应用举例:
即时通讯、网络视频会议、桌面共享等。
2、TCP:传输控制协议
特点:
提供的是面向连接、可靠的数据流传输;
要通过三次握手完成连接,建立TCP连接通道之后才能传输数据;
TCP能够进行大数据的传输,有流量控制和差错控制,正因为有可靠性的保证和控制手段,所以传输效率比UDP低;
适用情况:
主要用于一次传输大量数据的情形,如:文件传输,远程登录等;
若要保证数据传输的完整性和可靠性时,TCP协议是最好的选择。
应用举例:
软件下载、收发邮件、远程登陆等。
三、UDP和TCP传输示例
UDP传输:编写一个多线程聊天系统,一个当作发送端,一个当作接收端
package blog.itheima;
//编写一个多线程聊天系统,一个当作发送端,一个当作接收端
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
class MultiThreadUDPChat {
public static void main(String[] args) throws Exception{
//通过DatagramSocket对象创建udp服务;可不定义发送端口,发送时再分配
DatagramSocket sendSocker = new DatagramSocket();
//创建接收器的udp socket,建立端口
DatagramSocket recSocker = new DatagramSocket(10000);
//开启发送和接收二个线程
new Thread(new Send(sendSocker)).start();
new Thread(new Rec(recSocker)).start();
}
}
class Send implements Runnable{
private DatagramSocket ds;
//构造函数,接收一个DatagramSocket对象,即sendSocker
Send(DatagramSocket ds){
this.ds = ds;
}
public void run() {
try{
BufferedReader bf =
new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line=bf.readLine())!=null){
//把从键盘录入的一行数据存储进数组中
byte[] buf = line.getBytes();
/*确定数据,并封装成数据名,发送构造方法:DatagramPacket(
byte[] buf, int length, InetAddress address, int port)*/
DatagramPacket dp = new DatagramPacket(buf,buf.length,
InetAddress.getByName("192.168.2.101"),10000);
//数据封装好之后,发送出去
ds.send(dp);
//定义结束标记
if ("886".equals(line))
break;
}
//ds.close();若循环发送,可不用关闭
}catch (Exception e){
throw new RuntimeException("发送信息失败");
}
}
}
class Rec implements Runnable{
private DatagramSocket ds;
Rec(DatagramSocket ds){
this.ds = ds;
}
public void run(){
try
{//接收线程循环接收数据
while(true){
byte[] buf = new byte[1024];
/*DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。*/
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//阻塞式方法,没有数据就会等待
ds.receive(dp);
//dp.getAddress() 获取IP地址封装成的对象
String ip = dp.getAddress().getHostAddress();
//getLength() 返回将要发送或接收到的数据的长度。
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"---"+data);
if ("886".equals(data)){
System.out.println(ip+"....离开聊天室");
break;
}
}
}catch (Exception e){
throw new RuntimeException("接收信息失败");
}
}
}
TCP传输:
1、客户端从键盘输入读取一个字符串,发送到服务器;
2、服务器接收客户端发送的字符串,反转之后发回客户端,客户端接收并打印。
package com.itheima;---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
/**
* 使用TCP协议,客户端从键盘输入读取一个字符串,发送到服务器。
* 服务器接收客户端发送的字符串,反转之后发回客户端。客户端接收并打印。
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) {
//建立客户端Socket通讯服务,初始值为null
Socket socket = null;
try {
//指定Socket的IP地址和端口,以下IP是本机地址
socket = new Socket("192.168.2.100",10000);
} catch (Exception e) {
System.out.println("服务器连接失败");
}
//键盘录入字符
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = null;//定义初始化的字符输出流
BufferedReader bufRead = null;//定义初始化的缓冲读取流
String line = null;//定义初始化的键盘录入数据,每次读一行
try {
/*获取socket流中的输出流,用字符输出流写出,
加上true以后,对println、printf 或 format 可以自动刷新*/
out = new PrintWriter(socket.getOutputStream(),true);
//获取socket流中的输入流,用缓冲流读取
bufRead =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
while((line=bufIn.readLine())!=null){//如果键盘录入的数据不为空,遍历循环
out.println(line);//将键盘录入的数据发送到服务器
//定义结束标记为over字符,放在这里可以将标记传到服务器,同时能够防止返回数据为null的情况
if("over".equals(line))
break;
System.out.println(bufRead.readLine());//打印服务器传输回来的数据
}
} catch (IOException e) {
System.out.println("客户端数据流传输失败");
} finally {
//键盘录入和流服务最后需要关闭,不能一直占用系统资源,所以放在finally中执行
try {
if(bufIn!=null)
bufIn.close();
} catch (IOException e) {
System.out.println("键盘录入关闭失败");
}
try {
if(socket!=null)
socket.close();
} catch (IOException e) {
System.out.println("关闭服务器连接失败");
}
}
}
}
class TcpServer{
public static void main(String[] args) {
//初始化服务器
ServerSocket serverSocket = null;
try {
//创建服务器socket服务,并监听一个端口
serverSocket = new ServerSocket(10000);
} catch (IOException e) {
System.out.println("服务器建立失败");
}
Socket socket = null;
try {
//通过accept方法获取连接过来的客户端对象,返回的是socket对象
socket = serverSocket.accept();
} catch (IOException e) {
System.out.println("服务端连接客户端失败");
}
BufferedReader bufr = null;//初始化缓冲读取流
PrintWriter out = null;//初始化字符输出流
StringBuilder sb = null;//初始化StringBuilder对象
String line = null;
//通过socket流,得到主机IP地址
String ip = socket.getInetAddress().getHostAddress();
System.out.println("IP:"+ ip +" 用户已连接");
try {
//获取socket流中的输入流,用缓冲流读取
bufr =
new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取socket流中的输出流,用字符输出流写出,自动刷新
out = new PrintWriter(socket.getOutputStream(),true);
while((line=bufr.readLine())!=null){
//如果客户端一直有数据传输过来,那么一直循环读取
if("over".equals(line)){
//当收到客户端的over结束标记时,停止循环,不必再传输数据到客户端
System.out.println("客户端退出连接,服务器关闭");
break;
}
sb = new StringBuilder(line);
//把客户端传输来的数据存入StringBuilder
StringBuilder reverse = sb.reverse();
//利用StringBuilder的功能把字符串进行返转
out.println(reverse);
//把反转后的数据传到客户端
//在服务端打印出客户端输入的数据和反转后的数据
System.out.println("客户端输入的数据为:"+line+";反转后的数据为:"+reverse);
}
} catch (IOException e) {
System.out.println("服务器数据流传输失败");
} finally {
//数据流传输和服务器最后需要关闭,不能一直占用系统资源,所以放在finally中执行
try {
if(socket!=null)
socket.close();
} catch (IOException e) {
System.out.println("关闭客户端连接失败");
}
try {
if(serverSocket!=null)
serverSocket.close();
} catch (IOException e) {
System.out.println("服务器关闭失败");
}
}
}
}