概述
IP
-
网络中计算机设备的标识
-
表示方式
ipv4:4组8位的二进制数,转换成4组十进制数显示为XXX.XXX.XXX.XXX
ipv6:8组16位的二进制数,转换成8组十六进制数显示为XXXX:XXXX:XXXX:XXX -
cmd指令
ipconfig
ping 域名/服务器IP地址 -
本机IP地址: localhost 127.0.0.1
端口
- 计算机设备中应用的标识
- 写法:Ip:端口号
- 分类
0~1023 特殊/系统应用
其他:普通应用
端口号冲突会导致应用无法使用
传输层协议
网络传输规则(格式/速度/大小)
HTTP TCP/IP
-
TCP Transmission Control Protocal 传输控制协议
面向字节流的
有缓冲区的
连接的(所以接收时也需要指定Ip地址端口号)可靠的(三次握手,socket与serversocket建立连接时如果serversocket还未开启就绪,会报错) -
Internet Protocal互联网协议
-
UDP User Datagram Protocal 用户数据报协议
面向数据报的
无缓冲区的
非连接的,不管对方状态,直接发送数据(DatagramSocket直接通过send 和 receive 方法发送读写数据。收件地址未就绪就没有效果而已,不会爆异常)
UDP | 不可靠(会丢包),快速 | 即时通信、在线 | |
UDP | 不可靠(会丢包),快速 | 即时通信、在线 | |
TCP | 可靠,速率较慢 | 下载 | |
TCP | 可靠,速率较慢 | 下载 |
java.net中封装的网络通信相关类
InetAddress
构造方法私有化的,需要通过静态方法获取对象getByName(“域名/IP地址”)
获取当前域名的域名网址+IP地址;
getAllByName(“主机名/IP地址”)
一个域名可能有多个服务器,可获取当前域名下的所有主机名+IP地址的数组(InetAddress[])
getByAddress(byte[] addr)
通过"IP地址".geyBytes 获取InetAddress对象
getHostName()
获取域名网址
getLocalHost()
返回本地主机的InetAddress对象
getHostAddress()
获取服务器地址
DatagramSocket
发送接收者对象,用于进行数据发送和接收(send 和 receive 方法)
- 创建发送者对象,通过参数指定自身端口号
- ①调用receive方法,传入DatagramPacket对象(含字节数组缓冲区及其大小即可),实现接收
②调用send方法,传入DatagramPacket对象(包含字节数组本身,字节数组的长度,InetAddress对象,端口号),实现发送 - 通过datagram对象的getData方法获取数据内容,getLength获得长度
DatagramPacket
封装要传输的数据和发送的地址
字节数组本身 | 字节数组的长度 | InetAddress对象 | 端口号 | 备注 | |
---|---|---|---|---|---|
发送者需要的DatagramPacket 对象 | √ | √ | √ | √ | |
接受者需要的DatagramPacket 对象 | √ | √ |
//案例:多线程模拟双人会话(每个线程是一个客户端,各自都可读可写且互不同步,即无需互斥),以下为其中一个线程,另一个线程只需发送接收的IP和端口号与其对调即可
public static void main(String[] args) throws IOException {
System.out.println("客户端1");
Scanner sc1 = new Scanner(System.in);
//指定发送者对象和自己的端口号
DatagramSocket ds = new DatagramSocket(7788);
new Thread(){
byte[] buf = new byte[1024];
@Override
public void run() {
while(true){
try {
//准备要发送的数据(转换成字节数组)
System.out.println("zoya请输入");
String string ="\t"+ sc1.next();
buf = string.getBytes();
//创建要发送的数据包对象(含数据、长度、目标地址IP、目标端口号)
DatagramPacket dp = new DatagramPacket(buf, buf.length,InetAddress.getLocalHost(), 8899);
//创建发送数据包对象
//执行发送
ds.send(dp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread(){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
@Override
public void run() {
while(true){
try {
//走到recevie方法,程序阻塞,等待发送者发送,等待接收
ds.receive(dp);
//显示接收到的数据(收到消息)
System.out.println(new String(dp.getData()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
ServerSocket
Socket
socket创建时绑定的IP地址可以用以下任意形式
多人聊天室
- 服务端
package com.hw.study;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("=====聊天室开启=====");
//存储所有连接过来的客户端socket
LinkedList<Socket> sockets = new LinkedList<>();
//指定服务器段server socket
ServerSocket serverSocket = new ServerSocket(61000);
//死循环不断接收客户端连入
new Thread(){
@Override
public void run(){
//可以不停接收新的客户端加入
while(true){
try {
//不停等待接收新的客户端socket
Socket recievedFromSocket = serverSocket.accept();
//加入socket集合
sockets.add(recievedFromSocket);
//接收客户端的消息,接收到时群发给所有客户端
//为每一个客户端都开一个线程,与其进行网络通信
new Thread(){
@Override
public void run(){
//初次进入的状态设置,后面不会再走到这一句
int status=0;
//死循环向客户端发消息(没有消息可发时阻塞在read方法)
while(true){
String msg=null;
String clientIP = recievedFromSocket.getInetAddress().getHostAddress();
try {
//初次连接(客户端进入)
if(status == 0){
msg = "\t"+getTime()+"\t"+clientIP+"加入";
sendToAll(msg,sockets);
status=1;
//再次连接(客户端发消息了)
}else{
InputStream ips = recievedFromSocket.getInputStream();
int len = 0;
byte[] b = new byte[1024];
while((len=ips.read(b))!=-1){
msg = clientIP+":"+new String(b,0,len);
//剩下的一切必须在read所在的while循环内执行
//因为在socket连接时网络字节流不会关闭,read也一直处于阻塞状态,不会出这个循环。
//循环后面的语句除非异常则执行不到。
sendToAll(msg,sockets);
}
}
} catch (IOException e) {
//当有一个客户端退出时,由于服务器还在等待其输入,其流关闭,会报异常。
//如果只是打印异常信息,因为在死循环里,会不停打印。所以要关闭线程
msg=recievedFromSocket.getInetAddress().getHostAddress()+"退出";
//移除出集合,避免后续再向其发数据发不过去报异常。
sockets.remove(recievedFromSocket);
//向其他所有客户端通知某个已经退出
sendToAll(msg, sockets);
//关闭当前线程。
return;
}
}
}
}.start();
} catch (Exception e) {
}
}
}
}.start();
}
//封装发送方法
public static void sendToAll(String msg,LinkedList<Socket> sockets) {
for(int j = 0; j< sockets.size();j++){
try {
OutputStream ops = sockets.get(j).getOutputStream();
ops.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
//封装获取时间的方法
public static String getTime() throws IOException{
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
return sdf.format(now);
}
}
- 客户端
package com.hw.study;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientA {
public static void main(String[] args) throws Exception, IOException {
System.out.println("客户端A开启");
Socket client = new Socket("服务的IP",服务的端口号);
//收取线程
new Thread(){
@Override
public void run(){
while(true){
try {
InputStream ips = client.getInputStream();
int i=0;
byte[] bs = new byte[1024];
while((i=ips.read(bs))!=-1){
System.out.println(new String(bs,0,i,"utf-8"));
}
} catch ( Exception e) {
System.out.println("服务器关闭");
return;
}
}
}
}.start();
//发送线程
new Thread(){
//可以一直发
public void run() {
Scanner scanner = null;
while(true){
scanner = new Scanner(System.in);
try {
OutputStream ops = client.getOutputStream();
int len = 0;
byte[] bs = new byte[1024];
ops.write(scanner.next().getBytes());
} catch (Exception e) {
System.out.println("服务器关闭");
return;
}
}
}
}.start();
}
}