一:必要的基本知识
网络:将不同区域的计算机连接到一起
ip地址: 确定计算机在网络上的一个绝对地址(可以看作是一个房子)
端口号:区分计算机软件(可以看作是房子里的房间)
资源定位符:url(可以定位到计算机中某个文件的位置,可以相成是房间里的东西)
数据的传输:1.tcp 类似于三次握手 面向连接,安全可靠,效率低
2.udp 短信,非面向连接,效率高,安全性低
二:掌握两个类
可以将其理解成io中的File类,找到计算机的地址
类一:
InetAddress(封装计算机的ip地址和dns,没有端口) dns是域名解析
代码如下:
package jibenlei;
import java.net.InetAddress;
import java.net.UnknownHostException;
//InetAddress中的构造方法不支持外部访问,所以我们利用静态方法来获取对象
//这个类可以得到对象的静态方法有三种,基本方法有两个
public class Inetaddress {
public static void main(String[] args) throws UnknownHostException {
//通过静态方法得到该类的对象
// InetAddress a=InetAddress.getLocalHost(); //得到指向本机的对象
// InetAddress a=InetAddress.getByName("www.163.com"); //通过域名得到指向该地址的对象
InetAddress a=InetAddress.getByName("124.132.144.132"); //通过ip地址得到指向该地址的对象
System.out.println("ip地址为"+a.getHostAddress());
System.out.println("域名为"+a.getHostName());
}
}
类二:InetSocketAddress (在上个类的基础上又封装了端口)
代码如下:
package jibenlei;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
//这个类可以调用构造方法
public class InetsocketAddress {
public static void main(String[] args) throws UnknownHostException {
//构造InetSocketAddress对象 直接通过域名加端口
InetSocketAddress a=new InetSocketAddress("www.163.com",8888);
System.out.println(a.getHostName());
System.out.println(a.getPort());
System.out.println(a.getAddress());
//构造对象,通过InetAddress对象加端口
InetAddress b=InetAddress.getByName("www.163.com");
InetSocketAddress c=new InetSocketAddress(b,8888);
System.out.println(c.getHostName());
System.out.println(c.getPort());
System.out.println(c.getAddress());
}
}
三:url详解
url通常用于唯一的标识一个资源。由四部分组成:协议,存放资源的主机域名,资源文件名和端口号。资源可以是简单的文件或目录,亦可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
我们应该学习java类库中的URL,URL还可以选择指定一个端口,它是用来建立到远程主机tcp连接的端口号,如果未指定该端口号,则使用协议默认的端口
http://www.baidu.com:80/MosaicDocs-old/url-primer.html
URL: 1.创建 URL(String str)通过绝对路径创建
URL(URL context,String str) 通过相对路径创建
代码如下:
package jibenlei;
import java.net.MalformedURLException;
import java.net.URL;
public class url {
public static void main(String[] args) throws MalformedURLException {
//绝对路径构造
URL url=new URL("http://www.baidu.com:80/index.html#aa?uname=bjsxt");
//#aa表示锚点 uname=bjsxt 表示参数,和服务器进行交互
System.out.println("协议"+url.getProtocol());
System.out.println("域名"+url.getHost());
System.out.println("端口"+url.getPort());
System.out.println("资源"+url.getFile());
System.out.println("相对路径资源"+url.getPath());
url=new URL(url,"b.txt"); //相对路径构造
System.out.println("协议"+url.getProtocol());
System.out.println("域名"+url.getHost());
System.out.println("端口"+url.getPort());
System.out.println("资源"+url.getFile());
System.out.println("相对路径资源"+url.getPath());
}
}
获取网络资源:代码如下
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
public class url {
//我们来获取资源:源代码
//URL中有一个方法 openStream()打开到此url的连接并返回一个用于从该链接读入的InputStream
public static void main(String[] args) throws IOException {
//绝对路径构造
URL url=new URL("http://www.baidu.com"); //主页 默认资源
/* InputStream is=url.openStream();
byte b[]=new byte[1024];
int len=is.read(b);
while(len!=-1) {
System.out.println(new String(b,0,len));
}
is.close();
*/
//有的时候会出现乱码,出现的原因可能是数组字节数不够,另一个是编码和解码的字符集不统一,我们可以使用转换流来
//转换解码的字符集,所以我们写了以下代码
BufferedReader br=new BufferedReader(new InputStreamReader(url.openStream(),"utf-8"));
String msg=null;
while((msg=br.readLine())!=null) {
System.out.print(msg);
}
br.close();
}
}
四:udp通信
建立一个服务器端:
package jibenlei;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Myserver {
//udp编程 以数据为中心,不安全,非面向链接,效率高 java中的udp类是 DatagramSocket 和DatagramPacket
/*
* 一:客户端 1.创建客户端:DatagramSocket 类
* 2.准备数据 字节数组
* 3.打包 DatagramPacket +服务器地址+端口
* 4.发送 5.释放资源
*
* 二:服务器端: 1.创建服务器 DatagramSocket 类+指定端口
* 2.准备接受容器 字节数组 封装 DatagramPacket
* 3.包 接受数据 4.分析 5.释放数据
*/
public static void main(String[] args) throws IOException {
DatagramSocket server=new DatagramSocket(8888); //创建服务器+端口
byte container[]=new byte[1024]; //准备接受容器
DatagramPacket packet=new DatagramPacket(container,container.length);// 2.准备接受容器 字节数组 封装 DatagramPacket
server.receive(packet); //包 接受数据
byte data[]=packet.getData(); //分析数据
int len=packet.getLength();
System.out.println(new String(data,0,len));
server.close(); //释放资源
}
}
建立一个客户端:
package jibenlei;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class Myclient {
public static void main(String[] args) throws IOException {
DatagramSocket client=new DatagramSocket(6666); //创建客户端+端口
String msg="Hello World"; //准备数据
byte data[]=msg.getBytes();
DatagramPacket packet=new DatagramPacket(data,data.length,new InetSocketAddress("localhost",8888));
//打包数据
client.send(packet); //发送包
client.close(); //释放资源
}
}
以上我们是将字符串进行输出,然后我们将解决输出任意数据+类型
package jibenlei;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Myserver {
//udp编程 以数据为中心,不安全,非面向链接,效率高 java中的udp类是 DatagramSocket 和DatagramPacket
/*
* 一:客户端 1.创建客户端:DatagramSocket 类
* 2.准备数据 字节数组
* 3.打包 DatagramPacket +服务器地址+端口
* 4.发送 5.释放资源
*
* 二:服务器端: 1.创建服务器 DatagramSocket 类+指定端口
* 2.准备接受容器 字节数组 封装 DatagramPacket
* 3.包 接受数据 4.分析 5.释放数据
*/
public static void main(String[] args) throws IOException {
DatagramSocket server=new DatagramSocket(8888); //创建服务器+端口
byte container[]=new byte[1024]; //准备接受容器
DatagramPacket packet=new DatagramPacket(container,container.length);// 2.准备接受容器 字节数组 封装 DatagramPacket
server.receive(packet); //包 接受数据
byte data[]=packet.getData(); //分析数据 将字节数组变成double
double a=convert(packet.getData());
System.out.println(a);
server.close(); //释放资源
}
public static double convert(byte data[]) throws IOException {
DataInputStream dis=new DataInputStream(new ByteArrayInputStream(data));
double num=dis.readDouble();
dis.close();
return num;
}
}
package jibenlei;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class Myclient {
//将double类型变成字符数组,用convert方法
public static void main(String[] args) throws IOException {
DatagramSocket client=new DatagramSocket(6666); //创建客户端+端口
Double num=48.23; //准备数据
byte data[]=convert(89.12);
DatagramPacket packet=new DatagramPacket(data,data.length,new InetSocketAddress("localhost",8888));
//打包数据
client.send(packet); //发送包
client.close(); //释放资源
}
public static byte[] convert(double num) throws IOException {
byte data[]=null;
ByteArrayOutputStream bos=new ByteArrayOutputStream();
DataOutputStream dos=new DataOutputStream(bos);
dos.writeDouble(num);
dos.flush();
data=bos.toByteArray();
dos.close();
return data;
}
}
client server 模型 一般用于局域网
browser(浏览器) server 模型 一般用于互联网,方便,一般电脑上都有安装
Tcp:
tcp中的服务器端:
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class server03 {
//现在服务器是等客户端发一条信息,它读一条。但是正常的聊天室中服务器的读取和客户端发不发信息无关
public static void main(String[] args) throws IOException {
//1.创建服务器 指定端口
ServerSocket server=new ServerSocket(8888);
//2.接收客户端连接 阻塞式
Socket client=server.accept();
//写出数据 先读取后输出
//读取数据
DataInputStream dis=new DataInputStream(client.getInputStream());
String msg=dis.readUTF();
System.out.println(msg);
//输出数据
DataOutputStream dos=new DataOutputStream(client.getOutputStream());
dos.writeUTF("服务器-->"+msg);
dos.flush();
}
}
tcp中的客户端:
import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket;
public class client03 { //这个时候客户端有明确的顺序,即先输出数据后读取数据,这样跟日常的聊天软件不符,因为输入流和输出流在同一个线程内,应该放在不同的线程中 public static void main(String[] args) throws IOException { //1.创建客户端时,必须指定服务器端+端口 此时就在连接 (客户端的端口是系统内部分配) Socket client=new Socket("localhost",8888); //控制台输入流 我们从控制台上获取数据 BufferedReader console=new BufferedReader(new InputStreamReader(System.in)); String info=console.readLine(); //输出数据 DataOutputStream dos=new DataOutputStream(client.getOutputStream()); dos.writeUTF(info); dos.flush(); //读取数据 DataInputStream dis=new DataInputStream(client.getInputStream()); String msg=dis.readUTF(); System.out.println(msg); }
}
这个时候我们的聊天室还有很多问题,比如服务器仅能跟一个客服端连接,客户端仅能发送一次信息
我们在客户端中加入多线程,在服务器端中加入多线程并加入容器管理多线程。
服务器
import java.awt.List;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.AllPermission;
import java.util.ArrayList;
public class server04 {
//为每个客户端建立一个管道,彼此访问服务器时没有先后顺序(服务器中加入线程)
//为了实现群聊,我们在跟客户端建立连接时应该知道还有没有别人,所以增加容器管理
private ArrayList<kehu> all=new ArrayList<kehu>();
public static void main(String[] args) throws IOException {
new server04().start();
}
public void start() throws IOException {
ServerSocket server=new ServerSocket(9999);
while(true) {
Socket client=server.accept();
kehu a=new kehu(client);
all.add(a); //我们使用容器对线程(客户端和服务器的通道)进行管理
new Thread(a).start(); //一条道路
}
}
class kehu implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
public kehu(Socket client) throws IOException {
dis=new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
}
private String receive() throws IOException { //读取数据
String msg=dis.readUTF();
return msg;
}
private void send(String msg) throws IOException {
if(msg==null&&msg.equals("")) {
return;
}
dos.writeUTF(msg);
dos.flush();
}
private void sendothers() throws IOException {
String msg=receive();
//遍历容器
for(kehu other:all) {
if(other==this) { //如果遍历的是本身,跳过
continue;
}
//发送给其他客户端
other.send(msg);
}
}
public void run() {
while(true) {
try {
//send(receive()); //这是服务器接收客户端a的信息又返回客户端a,并没有实现发给其他人
sendothers(); //将客户端a发来的信息发给其他的客户端
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
客户端
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class client04 {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket client=new Socket("localhost",9999);
new Thread(new send(client)).start();
new Thread(new receive(client)).start();
}
}
客户端里的输入输出线程:
输入线程
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
public class receive implements Runnable{
private DataInputStream dis; //输入流
public receive(Socket client) throws IOException {
dis=new DataInputStream(client.getInputStream()); //初始化输入流,这个时候需要客户端跟服务器建立的socket,这样才知道从哪输入的
}
public String receive() throws IOException { //我们读取数据
String msg=dis.readUTF();
return msg;
}
public void run() {
try {
while(true) {
String str=receive();
System.out.println(str);}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出线程:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class send implements Runnable{
private BufferedReader console; //一种输入流是从控制台输入,一种输出流
private DataOutputStream dos;
send(){
console=new BufferedReader(new InputStreamReader(System.in));
}
send(Socket client) throws IOException{
this();
dos=new DataOutputStream(client.getOutputStream());
}
private String get() throws IOException { //从控制台上得到数据
return console.readLine();
}
private void send() throws IOException { //将得到的数据输出
String msg=get();
if(msg!=null) {
dos.writeUTF(msg);
dos.flush();
}
}
public void run() {
try {
while(true) {
send();}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
我们通过多线程完成了群聊的功能,然后解决私聊的问题。
服务器端:
import java.awt.List;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.AllPermission;
import java.util.ArrayList;
public class server04 {
//为每个客户端建立一个管道,彼此访问服务器时没有先后顺序(服务器中加入线程)
//为了实现群聊,我们在跟客户端建立连接时应该知道还有没有别人,所以增加容器管理
private ArrayList<kehu> all=new ArrayList<kehu>();
public static void main(String[] args) throws IOException {
new server04().start();
}
public void start() throws IOException {
ServerSocket server=new ServerSocket(9999);
while(true) {
Socket client=server.accept();
kehu a=new kehu(client);
all.add(a); //我们使用容器对线程(客户端和服务器的通道)进行管理
new Thread(a).start(); //一条道路
}
}
class kehu implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private String name;
public kehu(Socket client) throws IOException {
dis=new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
this.name=dis.readUTF();
this.send("欢迎进入聊天室");
this.sendothers(this.name+"进入了聊天室",true);
}
private String receive() throws IOException { //读取数据
String msg="";
msg=dis.readUTF();
return msg;
}
private void send(String msg) throws IOException {
if(msg==null&&msg.equals("")) {
return;
}
dos.writeUTF(msg);
dos.flush();
}
private void sendothers(String msg,boolean iftrue) throws IOException {
//查看是否为私聊还是群聊
if(msg.startsWith("@")&&msg.indexOf(":")>-1) {//私聊
//获取name
String name=msg.substring(1,msg.indexOf(":"));
String content=msg.substring(msg.indexOf(":")+1); //左闭右开所以我们要+1
for(kehu other:all) {
if(other.name.equals(name)) {
other.send("对您悄悄的说"+content);
}
}
}
else {
//遍历容器
for(kehu other:all) {
if(other==this) { //如果遍历的是本身,跳过
continue;
}
//发送给其他客户端
if(iftrue==true) {
other.send("系统信息"+msg);
}
else {
other.send(this.name+"对所有人说"+msg);}
} }
}
public void run() {
while(true) {
try {
//send(receive()); //这是服务器接收客户端a的信息又返回客户端a,并没有实现发给其他人
sendothers(receive(),false); //将客户端a发来的信息发给其他的客户端
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
客户端:
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class client04 {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("请输入姓名");
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String name=br.readLine();
if(name==null) {
return;
}
Socket client=new Socket("localhost",9999);
new Thread(new send(client,name)).start();
new Thread(new receive(client)).start();
}
}
客户端的输出流:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class send implements Runnable{
private BufferedReader console; //一种输入流是从控制台输入,一种输出流
private DataOutputStream dos;
String name;
send(){
console=new BufferedReader(new InputStreamReader(System.in));
}
send(Socket client,String name) throws IOException{
this();
dos=new DataOutputStream(client.getOutputStream());
this.name=name;
send(this.name);
}
private String get() throws IOException { //从控制台上得到数据
return console.readLine();
}
private void send(String msg) throws IOException { //将得到的数据输出
if(msg!=null) {
dos.writeUTF(msg);
dos.flush();
}
}
public void run() {
try {
while(true) {
send(get());}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端的输入:
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
public class receive implements Runnable{
private DataInputStream dis; //输入流
public receive(Socket client) throws IOException {
dis=new DataInputStream(client.getInputStream()); //初始化输入流,这个时候需要客户端跟服务器建立的socket,这样才知道从哪输入的
}
public String receive() throws IOException { //我们读取数据
String msg=dis.readUTF();
return msg;
}
public void run() {
try {
while(true) {
String str=receive();
System.out.println(str);}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}