网络基础知识
IP地址:每个网卡/机器都有一个或多个IP地址
····IPV4:192.168.0.100 每段从0到255
····IPV6:分成8段,每段4个16进制数
eg:FE80:0000:0000:0000:AAAA:0000:00C2:0002
port:端口,0-65535
·····0-1023 OS已经占用,80是web,23是telnet
·····1024-65535 一般程序可用(谨防冲突)
两台机器通讯就是在IP+Port上进行的
TCP通讯协议
·····传输控制协议,面向连接的协议
·····两台机器的可靠无差错的数据传输
·····双向字节流传递
UPD通讯协议
·····用户数据报协议,面向无连接协议
·····不保证可靠的数据传输
·····速度快,也可以在较差网络下使用
Java UDP编程
计算机通讯:数据从一个IP的port出发(发送方),运输到另一个IP的port(接收方
UDP:无连接无状态的通讯协议
······发送方发送消息,如果接收方刚好在目的地,则可以接受。
如果不在,信息就丢失了
······发送方也无法得知是否发送成功
·····UDP的好处:简单 节省 经济
注意接收方要先于发送方执行,否则信息会丢失
public class UpdRecv {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DatagramSocket ds = new DatagramSocket(3000); // 数据管道 3000端口
byte [] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, 1024); //定义集装箱
System.out.println("UdpRecv:我在等待消息");
ds.receive(dp); // 如果有数据封装入dp中,没有改行一直阻塞
System.out.println("UdpRecv:我接收到消息");
String strRecv = new String(dp.getData(),0,dp.getLength()) + "from" +
dp.getAddress().getHostAddress()+ ":" + dp.getPort();
System.out.println(strRecv);
// 再定义一个集装箱
DatagramPacket dp2 = new DatagramPacket(strRecv.getBytes(), strRecv.length(),
InetAddress.getByName("127.0.0.1"), dp.getPort());
// dp.getPort()获得之前发送方的端口
}
public class UpdSend {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket(); // 数据管道 没写则随机选择端口发送
String str = "hello world";
DatagramPacket dp = new DatagramPacket(str.getBytes(), str.length(),
InetAddress.getByName("127.0.0.1"), 3000); //定义集装箱 内容和长度 127.0.0.1默认为本机
System.out.println("UdpSend:我在发消息");
ds.send(dp); // 如果有数据封装入dp中,没有改行一直阻塞
System.out.println("UdpSend:我发送消息结束");
}
}
Java TCP编程
TCP协议:有链接,保证可靠的无误差通讯
(1)服务器:创建一个ServerSocket,等待连接
(2)客户机:创建一个Socket,连接到服务器
(3)服务器:ServerSocket接收到连接,创建一个Socket和客户的Socket建立专线连接,后续服务器和客户机的对话(这一对Socket)会在一个单独的线程(服务器端)上进行
(4)服务器的ServerSocket继续等待连接,返回(1)
软件服务器:
(1)必须实现一定的功能
(2)必须在一个公开的地址(IP+Port)上对外提供服务
ServerSocket : 服务器码头
·····需要绑定port(以确定公开IP,不指定IP则默认为本机)
·····如果机器有多个网卡(多个IP),需要绑定一个IP地址
Socket : 运输通道
·····客户端需要绑定服务器的地址和Port
·····客户端往Socket输入流写入数据,送到服务端
·····客户端从Socket输出流取服务器端过来的数据
·····服务器反之亦然
·····服务端等待响应时,处于阻塞状态
·····服务端可以同时响应多个客户端(通过多线程实现)
·····服务端每接受一个客户端,就启动一个独立的线程与之对应
·····客户端和服务端都可以选择关闭这对Socket的通道
实例:
·····服务端先启动,且一直保留
·····客户端后启动,可以先退出
服务端
public class TcpServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8001); // 驻守在8001端口
Socket s = ss.accept(); // 阻塞,等待有客户端连接上来
System.out.println("Welcome to Java world");
InputStream ips = s.getInputStream(); // 有人连接上来,打开输入流
OutputStream ops = s.getOutputStream(); // 打开输出流
// 同一个通道,服务器端的输入流就是客户端的输出流
// getBytes使用指定的字符集将字符串编码为 byte序列,并将结果存储到一个新的 byte数组
ops.write("Hello, Client!".getBytes()); // 输出一句话给客户端
// 把输入流东西进行一次包装,提高读取效率
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
// 从客户端读一句话
System.out.println("Client said: " + br.readLine());
ips.close();
ops.close();
s.close();
ss.close();
}
}
客户端
public class TcpClient {
public static void main(String[] args) throws IOException{
// 需要服务端先开启 目标服务器的地址和端口
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8001);
InputStream ips = s.getInputStream(); // 开启通道的输入流
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
// BufferedReader包装字符流 加快读写速度
OutputStream ops = s.getOutputStream();// 开启通道的输出流
DataOutputStream dos = new DataOutputStream(ops);
BufferedReader brkey = new BufferedReader(new InputStreamReader(System.in));
// 客户端输入是键盘输入,故用System.in
while(true) {
String strWord = brkey.readLine();
if(strWord.equalsIgnoreCase("quit")) {
break;
} else {
System.out.println("I want to send: " + strWord);
// 发送给服务端
dos.writeBytes(strWord + System.getProperty("line.separator"));
// 接受打印服务端回话
System.out.println("Server said: " + brNet.readLine());
}
}
dos.close();
brNet.close();
}
}
每来一个客户端,新开一个线程处理(客户端代码和上面的例子一样)
public class TcpServer2 {
public static void main(String[] args) throws IOException{
// TODO Auto-generated method stub
ServerSocket ss = new ServerSocket(8001);
while(true) {
Socket s = ss.accept(); // 每次收到一个请求,就新建一个Socket
System.out.println("来了一个client");
//注意这样写线程多了(1000左右)开销会很大,考虑用并发线程池
new Thread(new Worker(s)).start(); // Socket作为构造参数提交给新的线程对象
}
}
}
public class Worker implements Runnable{
Socket s;
public Worker(Socket s) {
this.s = s;
}
// throws子句的Exception和接口中声明的不一致 故run() throws IOException改为try catch
public void run(){
System.out.println("服务人员已经启动");
// 定义通道的(字节)输入流和输出流
/*
* BufferedReader从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
* 为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader(字节流转化为字符流)。例如:
* BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
*/
try {
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
DataOutputStream dos = new DataOutputStream(ops); // 创建一个新的数据输出流,将数据写入指定基础输出流
while(true) {
String strWord = br.readLine();
System.out.println("client said: " + strWord + ":" + strWord.length());
if(strWord.equalsIgnoreCase("quit")) {
break;
}
String strEcho = strWord + " 666";
System.out.println("server said:" + strWord + "------->" + strEcho);
dos.writeBytes(strWord + "------->" + strEcho + System.getProperty("line.separtor"));
}
br.close();
// 关闭包装类,会自动关闭包装类中所包装的底层类,所以不调用ips.close()
dos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}