一.网络的基本认识
1、网卡
每一个网卡都有一个被称为MAC地址的独一无二的48位串行号,它被写在卡上的一块内存中。在网络上的每一个计算机都必须拥有一个独一无二的MAC地址。
没有任何两块被生产出来的网卡拥有同样的地址。这是因为电气电子工程师协会负责为网络接口控制器(网卡)销售商分配唯一的MAC地址
2、MAC地址、IP地址
其中物理地址指的就是MAC地址、IPv4 地址就是IP。
MAC地址也叫物理地址和局域网地址,主要用于确认网上设备的地址,类似于身份证号,具有唯一标识,每一个网卡制作完成之后就带有一个MAC地址,永远都不会改变。
IP地址,类似于你的现住址,是标记你在网络中的具体位置,一个网卡的IP地址是可以改变的。
IP地址的表示方式:32位
ip地址与子网掩码做与运算得到网络号
3.域名(就是ip的名字)你可以不知道你女朋友的电话号码,但是你一定得知道你女朋友的名字
DNS服务器
如果你的ip可以想象成电话号码的话,这个就是114
二.网络传输
应用层: 提供应用程序之间的通信。 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
表示层 :处理数据格式,数据加密和压缩等。 没有协议
会话层 :建立、管理、终止两主机的会话。 没有协议
传输层 :建立主机的端到端连接。 TCP,UDP
网络层 :路径选择。 ICMP,RIP,OSPF,BGP,IGMP,IP
数据链路层: 负责两个相邻结点之间的数据传输。 SLIP,CSLIP,PPP,ARP,RARP,MTU
物理层 :使原始的数据比特流能在物理媒介上传输。 ISO2110,IEEE802,IEEE802.2
1、数据是怎么传输的
(1)TCP协议
建立连接
断开连接
(2)UDP协议
UDP(User Datagram Protocol,用户数据报协议)是一种传输层的协议,它提供不可靠服务,它是无连接的,所以不存在建立连接需要的时延(说人话就是直接怼,管你受得了受不了)
三.socket编程(tcp的实现,udp的实现)
通过域名,主机名,环回地址拿取ip
@Test
public void InetAddressTest() throws UnknownHostException {
//从域名,回环都只能拿到一个ip地址 但是一个IP地址可以对应多个名字(主机名,域名,回环地址)
System.out.println("InetAddress.getLoopbackAddress() = " + InetAddress.getLoopbackAddress());
System.out.println("InetAddress.getByName(\"www.baidu.com\") = " + InetAddress.getByName("www.baidu.com"));
//拿到的是环回地址
System.out.println("InetAddress.getByName(\"localhost\") = " + InetAddress.getByName("localhost"));
//拿到的是网卡
System.out.println("InetAddress.getByName(\"吕超\") = " + InetAddress.getByName("吕超"));
}
网络连接(可以通过url 打开连接,拿到流来抓取内容)
@Test
public void UrlTest() throws IOException {
URL url = new URL("file:///D:\\IDEA激活\\a\\a.txt");
System.out.println("url.getFile() = " + url.getFile());
System.out.println("url.getPath() = " + url.getPath());
System.out.println("url.getProtocol() = " + url.getProtocol());
URLConnection urlConnection = url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
FileOutputStream outputStream = new FileOutputStream("D:\\IDEA激活\\b\\a.txt");
int len;
byte[] bytes = new byte[1024 * 1024];
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
}
tcp
/**
* socket实现tcp(建立链接额三次握手,四次挥手)
* @throws IOException
*/
//服务端接受客户端发来的东西
@Test
public void tcpServerTest() throws IOException {
//创建ServerSocket
ServerSocket server = new ServerSocket();
//绑定端口
server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 8888));
//监听端口
Socket socket = server.accept();
InputStream inputStream = socket.getInputStream();
int len;
byte[] bytes = new byte[1024 * 1024];
while ((len = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
inputStream.close();
}
@Test
public void tcpClientTest() throws IOException {
//创建Socket
Socket socket = new Socket();
//利用创建好的Socket进行连接
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 8888));
OutputStream outputStream = socket.getOutputStream();
outputStream.write("C哥牛逼666".getBytes(StandardCharsets.UTF_8));
outputStream.close();
socket.close();
}
udp
@Test
public void udpClientTest() throws IOException {
//用户创建udp服务端口,也可以随机分配
DatagramSocket socket = new DatagramSocket();
//数据区
String data="C哥牛逼666";
byte[] bytes = data.getBytes();
//构建数据包
DatagramPacket datagramPacket = new DatagramPacket(
bytes,0,bytes.length,
InetAddress.getByName("localhost"),8080);
socket.send(datagramPacket);
}
@Test
public void udpServerTest() throws IOException {
//服务商创建udp接受端口,也可以随机分配
DatagramSocket socket = new DatagramSocket(8080);
byte[] buffer=new byte[1024];
//构建数据包
DatagramPacket datagramPacket = new DatagramPacket(
buffer ,0,buffer.length
);
socket.receive(datagramPacket);
String s = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println(s);
socket.close();
}
小案例
客户端
import Utils.MsgUtils;
import Utils.ScannerUtil;
import constant.Constant;
import constant.MessageType;
import message.Message;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Optional;
public class Client {
public static void main(String[] args) throws IOException {
//创建客户端接口
Socket socket = new Socket();
//拿到连接
socket.connect(new InetSocketAddress(8080));
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
System.out.println("请登录");
//负责登陆的入口
//表示当前登录的用户
String username = null;
while (true) {
if (username == null) {
username = login(outputStream, inputStream);
} else {
printOrder();
String input = ScannerUtil.input();
switch (Integer.parseInt(input)) {
case MessageType.To_Server:
sendToServer(username, outputStream, inputStream);
break;
case MessageType.To_Friend:
sendToFriend( username,outputStream);
break;
case MessageType.To_All:
sendToAll( username,outputStream, inputStream);
break;
case MessageType.RECEIVE:
receive(inputStream );
break;
default:
System.out.println("你选的不对,重新选");
break;
}
}
}
}
private static void receive( InputStream inputStream) {
while (true){
Optional<Message> msg = MsgUtils.readMsg(inputStream);
msg.ifPresent(m -> System.out.println(m.getUserName() + ":" + m.getContent()));
}
}
private static void sendToServer(String username, OutputStream outputStream, InputStream inputStream) {
//给服务器发消息
System.out.println(username + ":");
String msg = ScannerUtil.input();
MsgUtils.writeMsg(outputStream, new Message(username, MessageType.To_Server, msg));
//接受消息
Optional<Message> message = MsgUtils.readMsg(inputStream);
message.ifPresent(m -> System.out.println(username + ":" + message.get().getContent()));
}
private static void sendToFriend(String username,OutputStream outputStream ) {
//给好友发消息
System.out.println("请输入好友的名字:");
String friend = ScannerUtil.input();
boolean flag = true;
while (flag) {
System.out.println(username + ":");
String msg = ScannerUtil.input();
if ("bye".equals(msg)) {
flag = false;
}
MsgUtils.writeMsg(outputStream, new Message(username, MessageType.To_Friend, msg, friend));
//接受消息 在用户接受消息的时候会卡在这里 因为他要一直读(一个用户已经静进入接受状态,未发出,但是这个用户一直在读,所以会卡住)
// Optional<Message> message = MsgUtils.readMsg(inputStream);
// message.ifPresent(m -> System.out.println(username + ":" + message.get().getContent()));
}
}
private static void sendToAll(String username,OutputStream outputStream, InputStream inputStream) {
//给全体发消息
boolean flag = true;
while (flag) {
System.out.println(username + ":");
String msg = ScannerUtil.input();
if ("bye".equals(msg)) {
flag = false;
}
MsgUtils.writeMsg(outputStream, new Message(username, MessageType.To_Friend, msg));
}
}
//打印的功能
private static void printOrder() {
System.out.println("请你选择功能" + "\n" +
MessageType.To_Server + "、给服务器发消息" + " " +
MessageType.To_Friend + "、给好友发消息" + " " +
MessageType.To_All + "、群发"+
MessageType.RECEIVE + "、接受信息");
}
//登陆的方法
private static String login(OutputStream outputStream, InputStream inputStream) {
//判断是否登录
System.out.println("请你先输入用户名");
String name = ScannerUtil.input();
System.out.println("请你先输入密码");
String pwd = ScannerUtil.input();
Message message = new Message();
message.setUserName(name);
message.setType(MessageType.To_Login);
message.setPassWord(pwd);
//发送给服务器的消息
MsgUtils.writeMsg(outputStream, message);
//接受来自服务器的消息
Optional<Message> msg = MsgUtils.readMsg(inputStream);
if (msg.isPresent() && Constant.SUCCESS.equals(msg.get().getContent())) {
return name;
}
return null;
}
}
服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
//创建服务器端口
try( ServerSocket socket = new ServerSocket()) {
//绑定端口
socket.bind(new InetSocketAddress(8080));
System.out.println("服务器已经启动" + "在" + socket.getLocalPort() + "端口");
//监听端口
//可建立多个链接,交给线程
while (true) {
Socket accept = socket.accept();
new ServerThread(accept).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(服务端)多线程处理客户端的请求
import Utils.MsgUtils;
import constant.Constant;
import constant.MessageType;
import message.Message;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Optional;
public class ServerThread extends Thread {
private Socket accept;
public ServerThread(Socket socket) {
this.accept = socket;
}
@Override
public void run() {
try (InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream()) {
while (true) {
Optional<Message> message = MsgUtils.readMsg(inputStream);
if (message.isPresent()) {
Message msg = message.get();
switch (msg.getType()) {
case MessageType.To_Login:
loginHandler(outputStream, inputStream, msg, accept);
break;
case MessageType.To_Server:
sendToClient(outputStream, inputStream, msg);
break;
case MessageType.To_Friend:
sendToTarget( msg);
break;
case MessageType.To_All:
sendToAll(msg);
break;
}
}
}
} catch (
IOException e) {
e.printStackTrace();
}
}
private void sendToAll (Message message) {
//遍历所有的在线用户,拿到他们的socket
for (Map.Entry<String,Socket> entry:Constant.Online_User.entrySet()) {
try {
MsgUtils.writeMsg(entry.getValue().getOutputStream(), message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendToTarget ( Message message) {
//先找到对应的socket
Socket socket = Constant.Online_User.get(message.getFriendUserName());
try {
MsgUtils.writeMsg(socket.getOutputStream(),message);
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendToClient (OutputStream outputStream, InputStream inputStream, Message message){
//接受消息并打印
System.out.println(message.getUserName() + "说:" + message.getContent());
// 已经读过消息了,就不要在读了,否则会阻塞在这里 Optional<Message> msg = MsgUtils.readMsg(inputStream);
//发送消息
MsgUtils.writeMsg(outputStream, new Message(Constant.SERVER_NAME, MessageType.Form_Server, Constant.OK));
}
private void loginHandler (OutputStream outputStream, InputStream inputStream, Message message, Socket socket) {
//判断是否已经登陆
if (Constant.Online_User.containsKey(message.getUserName())){
MsgUtils.writeMsg(outputStream, new Message(Constant.SERVER_NAME, MessageType.Form_Server, "已经登陆,不可重复登陆", Constant.DEFAULT_PWD));
return;
}
//判断登录的逻辑
if (!message.getPassWord().equals(Constant.DEFAULT_PWD) || message.getUserName() == null) {
MsgUtils.writeMsg(outputStream, new Message(Constant.SERVER_NAME, MessageType.Form_Server, Constant.FAIL));
} else {
//登录成功,放在ConcurrentHashMap里面
Constant.Online_User.put(message.getUserName(), socket);
System.out.println(message.getUserName() + "登录成功");
MsgUtils.writeMsg(outputStream, new Message(Constant.SERVER_NAME, MessageType.Form_Server, Constant.SUCCESS, Constant.DEFAULT_PWD));
}
}
}
读写工具包
package Utils;
import message.Message;
import java.io.*;
import java.util.Optional;
public class MsgUtils {
//向输入流读消息
public static Optional<Message> readMsg(InputStream inputStream) {
ObjectInputStream ois;
try {
ois = new ObjectInputStream(inputStream);
//封装成一个Optional防止空指针
return Optional.ofNullable((Message) ois.readObject());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return Optional.empty();
}
//向输出流写消息
public static void writeMsg(OutputStream outputStream, Message message) {
ObjectOutputStream oos;
try {
oos = new ObjectOutputStream(outputStream);
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
控制输入工具包
package Utils;
import java.util.Scanner;
public class ScannerUtil {
private static final Scanner scanner = new Scanner(System.in);
public static String input() {
return scanner.next();
}
}
实体类
package message;
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 7857916520131819133L;
private Integer type;
private String content;
private String UserName;
private String PassWord;
private String FriendUserName;
public Message() {
}
//登录入口,用来登录的入口
public Message(Integer type, String userName, String passWord) {
this.type = type;
UserName = userName;
PassWord = passWord;
}
public Message(String userName, Integer type, String content) {
this.type = type;
this.content = content;
UserName = userName;
}
public Message(String userName, Integer type, String content, String passWord) {
this.type = type;
this.content = content;
UserName = userName;
PassWord = passWord;
}
public Message(Integer type, String content, String userName, String passWord, String friendUserName) {
this.type = type;
this.content = content;
UserName = userName;
PassWord = passWord;
FriendUserName = friendUserName;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getUserName() {
return UserName;
}
public void setUserName(String userName) {
UserName = userName;
}
public String getPassWord() {
return PassWord;
}
public void setPassWord(String passWord) {
PassWord = passWord;
}
public String getFriendUserName() {
return FriendUserName;
}
public void setFriendUserName(String friendUserName) {
FriendUserName = friendUserName;
}
@Override
public String toString() {
return "Message{" +
"type=" + type +
", content='" + content + '\'' +
", UserName='" + UserName + '\'' +
", PassWord='" + PassWord + '\'' +
", FriendUserName='" + FriendUserName + '\'' +
'}';
}
}
常量池
package constant;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Constant {
//成功的常量
public static final String SUCCESS = "SUCCESS";
//失败的常量
public static final String FAIL = "FAIL";
//服务器名字
public static final String SERVER_NAME = "SERVER";
//开始发送
public static final String OK = "OK";
//默认密码
public static final String DEFAULT_PWD = "123";
//用来装取用户名 与专属socket的集合
public static final Map<String, Socket> Online_User = new ConcurrentHashMap<>(8);
}
用户状态常量
package constant;
public class MessageType {
public static final int To_Server = 1;
public static final int To_Friend = 2;
public static final int To_All = 3;
public static final int To_Login = 8;
public static final int Form_Server = 5;
public static final int RECEIVE =4 ;
}