1、IP地址:InetAddress
2、port端口号
3、通信协议 —— 七层模型
Java底层封装了网络层和物理链路层的协议,如果想通过Java实现网络编程,我们只需要考虑应应用层和传输层的操作方式即可。
TCP传输数据的方式
Socket:客户端
绑定IP地址和端口号,采用TCP协议连接ServerSocket服务端
ServerSocket:服务端
绑定端口号,然后再当前主机上开启一个应用程序
UDP传输数据的方法
version 1
package com.kang.v1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
/**
* ServerSocket中的accept方法是用来接收客户端的请求,这个方法在执行的时候会进入线程阻塞的状态
* 直到客户端连接上服务端,这个代码才会向下继续执行
* 这个方法每调用一次,就可以接收一个客户端的请求
*
* 这个方法执行成功返回一个Socket,这个Socket是服务端创建的一个专门用来和客户端socket之间进行数据传输的网络套接字
*/
Socket socket = ss.accept();
System.out.println("服务端接收到了一个客户端的连接。客户端的IP地址为:" + Arrays.toString(socket.getInetAddress().getAddress()));
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
String line = null;
while((line = br.readLine()) != null){
System.out.println("客户端给服务端发送了:" + line);
}
}
}
package com.kang.v1;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) throws IOException {
/**
* 这一行代码就代表客户端连接服务端 建立TCP网络传输连接
*/
Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
OutputStream outputStream = socket.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
String line = null;
while ((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
}
}
version2
package com.kang.v2;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//客户端连接上服务端
Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
/**
* 首先先开启一个用来去接收服务端回应消息的线程
*/
Thread receiveMessageThread = new Thread(){
@Override
public void run() {
/**
* 客户端接收服务端返回的数据
*/
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
BufferedReader br1 = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
String line1 = null;
while((line1 = br1.readLine()) != null){
System.out.println("服务端回应了:" + line1);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
receiveMessageThread.start();
/**
* 客户端接收键盘输入,将键盘输入的数据发送给服务端
*/
OutputStream outputStream = socket.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
String line = null;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
}
}
package com.kang.v2;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
/**
* 版本:
* 客户端可以给服务端发送消息
* 服务端收到客户端消息后,给客户端回应一个服务端收到xxxx消息
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
System.out.println("服务端接收到一个客户端的连接,客户端IP地址为:" + Arrays.toString(socket.getInetAddress().getAddress()));
//socket可以获取输出流 输出流是服务端给客户端发送的消息
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
//socket可以获取输入流 输入流是客户端给服务端发送的消息
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
String line = null;
while((line = br.readLine()) != null){
System.out.println("客户端给服务端发送了:" + line);
bw.write("服务端收到:" + line);
bw.newLine();
bw.flush();
}
}
}
version3
package com.kang.v3;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//客户端连接服务端
Socket socket = new Socket(InetAddress.getByName("localhost"),9999);
Thread reveiceMessageThread = new Thread(){
@Override
public void run() {
//客户端接收消息
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
String line1 = null;
while((line1 = br.readLine()) != null){
System.out.println("服务端回应了:" + line1);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
reveiceMessageThread.start();
//客户端发送消息
BufferedReader br1 = new BufferedReader(new InputStreamReader(System.in));
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
String line = null;
while((line = br1.readLine())!= null) {
bw.write(line);
bw.newLine();
bw.flush();
}
}
}
package com.kang.v3;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
/**
* v3版本:
* 服务端可以接收多个客户端的连接,同时可以和多个客户端之间进行通信
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9999);
System.out.println("============================服务端v3版本开始运行============================");
/**
* 核心是想让服务端接收多个客户端的请求
*/
while(true){
//接收到一个客户端的请求
Socket socket = ss.accept();
System.out.println("服务端接收到一个客户端的连接,客户端的IP地址为:" + Arrays.toString(socket.getInetAddress().getAddress()));
Thread handlerClientThread = new Thread(){
@Override
public void run() {
//服务端接收消息
try {
InputStream inputStream = socket.getInputStream();
BufferedReader br = null;
br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
//服务端发送消息
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
String line = null;
while ((line = br.readLine()) != null){
System.out.println("客户端" +Arrays.toString(socket.getInetAddress().getAddress()) + "给服务端发送的消息:" + line);
bw.write("服务端收到:" + line);
bw.newLine();
bw.flush();
}
} catch (SocketException e) {
System.out.println("有一个客户端下线了,客户端的IP地址为:" + Arrays.toString(socket.getInetAddress().getAddress()));
}catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
handlerClientThread.start();
}
}
}
version4
package com.kang.v4;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//客户端连接服务端
Socket socket = new Socket(InetAddress.getByName("localhost"),9999);
Thread reveiceMessageThread = new Thread(){
@Override
public void run() {
//客户端接收消息
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
String line1 = null;
while((line1 = br.readLine()) != null){
System.out.println(line1);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
reveiceMessageThread.start();
//客户端发送消息
BufferedReader br1 = new BufferedReader(new InputStreamReader(System.in));
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
String line = null;
while((line = br1.readLine())!= null) {
bw.write(line);
bw.newLine();
bw.flush();
}
}
}
package com.kang.v4;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* v4版本:
* 实现思路:将其中一个socket接收到的消息发送给所有服务端在线的socket
* 核心逻辑:服务端没接收到一个客户端的连接,需要把客户端对应的socket缓存起来
* 缓存池只能保存在线的socket,如果下线了 那么需要从缓存中移除了
*/
public class Server {
//定义一个静态属性
public static List<Socket> onlineSockets = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9999);
System.out.println("============================服务端v3版本开始运行============================");
/**
* 核心是想让服务端接收多个客户端的请求
*/
while(true){
//接收到一个客户端的请求
Socket socket = ss.accept();
System.out.println("服务端接收到一个客户端的连接,客户端的IP地址为:" + Arrays.toString(socket.getInetAddress().getAddress()));
//需要把客户端缓存起来
onlineSockets.add(socket);
Thread handlerClientThread = new Thread(){
@Override
public void run() {
//服务端接收消息
try {
InputStream inputStream = socket.getInputStream();
BufferedReader br = null;
br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
//服务端发送消息
// OutputStream outputStream = socket.getOutputStream();
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
String line = null;
while ((line = br.readLine()) != null){
System.out.println("客户端" +Arrays.toString(socket.getInetAddress().getAddress()) + "给服务端发送的消息:" + line);
// bw.write("服务端收到:" + line);
// bw.newLine();
// bw.flush();
/**
* 同时我们还需要把数据给所有用户广播一份
*/
for (Socket onlineSocket : onlineSockets) {
OutputStream outputStream = onlineSocket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
bw.write("有一个客户端" + Arrays.toString(socket.getInetAddress().getAddress()) + "发送了一个消息:" + new Date());
bw.newLine();
bw.write(" " + line);
bw.newLine();
bw.flush();
}
}
} catch (SocketException e) {
System.out.println("有一个客户端下线了,客户端的IP地址为:" + Arrays.toString(socket.getInetAddress().getAddress()));
//下线移除缓存,保证缓存均是在线用户
onlineSockets.remove(socket);
}catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
handlerClientThread.start();
}
}
}
version5
package com.kang.v5.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* v5版本:实现私聊功能
* 实现思路:
* 1、要能找到对应用户的socket---服务端找
* 2、客户端给指定客户端发送消息的时候,你得告诉服务端给谁发送消息-----自定义协议
* 实现流程:
* 1、客户端在启动的时候,必须有唯一的账号帮助我们启动,账号先给服务端发送消息,账号要登录,服务端会把账号和账号对应的socket缓存起来--map集合
* 2、客户端在发送消息的时候,需要加上自定义的协议,协议告诉我们服务端需要做什么样的功能
* 0,username.password 代表的是注册逻辑
* 1,username,password 代表的是登录请求
* 2,123456,1234567,message 代表的就是给123456给1234567账号发送一个message消息
* 3,123456,message 代表的是123456账号发送了群聊的消息message
*/
public class Server {
//静态属性集合当作我们的用户信息的存储容器
public static List<User> users = new ArrayList<>();
public static Map<String,Socket> onlineUsers = new HashMap<>();
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket ss = new ServerSocket(9999);
while(true){
//一旦服务端的接收到客户端的请求,不是立马缓存客户端的socket,而是等到用户登录成功才缓存的。
Socket socket = ss.accept();
//自定义一个线程用来让服务端接收客户端的请求,处理客户端的消息
Thread receiveClientMessageThread = new Thread(){
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while ((line = br.readLine()) != null){
//line就是客户端给服务端发送的消息,消息自定义的协议类型
handlerClientMessage(line,socket);
}
} catch (SocketException e) {
//先遍历map集合 找到socket对应的key
}catch (IOException e) {
throw new RuntimeException(e);
}
}
};
receiveClientMessageThread.start();
}
}
/**
* 根据客户端发送的协议数据进行数据处理
* @param line
* @param socket
*/
private static void handlerClientMessage(String line, Socket socket) throws IOException {
//这个数组里面放的就是我们所需要的协议数据,其中数组的第一个位置代表的是协议的编号
String[] data = line.split(",");
String protocol = data[0];
switch (protocol){
case "0":
//注册协议
String nickname = data[1];
String password = data[2];
String number = ServerUtil.generatorNumber();
User user = new User(nickname,password,number);
Server.users.add(user);
//服务端应该给客户端回应一个信息:0,注册成功,number
String message = "0,注册成功," + number;
ServerSendMessageUtil.sendMessage(socket,message);
break;
case "1":
//登录协议
String loginNumber = data[1];
String loginPassword = data[2];
String message1 = handlerLogin(loginNumber,loginPassword);
ServerSendMessageUtil.sendMessage(socket,message1);
//根据账号和socket把当前用户缓存起来
Server.onlineUsers.put(loginNumber,socket);
break;
default:
break;
}
}
private static String handlerLogin(String loginNumber, String loginPassword) {
String message = "1,";
boolean flag = false;
for (User user : Server.users) {
if(user.getNumber().equals(loginNumber)){
if (user.getPassword().equals(loginPassword)){
message += "ok";
flag = true;
break;
}else {
message += "fail,password is valid";
}
}
}
if (!flag){
message += "fail,user is not exists";
}
return message;
}
}
package com.kang.v5.client;
import sun.nio.cs.ext.ISO2022_CN_CNS;
import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
/**
* 客户端可以正常连接服务端,只不过现在客户端给服务端发送的消息不再是随便的一条数据了,而是我们自定义的协议类型的数据
* 自定义的协议类型的数据可以告诉服务端需要做什么样的逻辑
*/
Socket client = new Socket(InetAddress.getByName("localhost"),9999);
/**
* 先开启一个线程,专门让客户端接收服务端返回的数据的线程
*/
Thread receiveServerMessageThread = new Thread(){
@Override
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line = null;
while ((line = br.readLine()) != null){
//line就是服务端给客户端返回的信息 返回的信息也是自定义的协议
handlerServerMessage(line,client);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
receiveServerMessageThread.start();
a:while(true) {
System.out.println("=========================================================");
System.out.println("|| 1、注册 2、登录 3、关闭程序 ||");
System.out.println("=========================================================");
Scanner scanner = new Scanner(System.in);
int menu = scanner.nextInt();
switch (menu) {
case 1:
register(client,scanner);
break;
case 2:
login(client,scanner);
break;
case 3:
System.out.println("程序关闭成功,还有您的继续使用!");
client.close();
break a;
default:
System.out.println("没有该菜单选项,请重新选择!");
break;
}
}
}
/**
* 客户端用来处理服务端发送的消息
* @param line
* @param client
*/
private static void handlerServerMessage(String line, Socket client) {
String[] data = line.split(",");
String protocol = data[0];
switch (protocol){
case "0":
//注册信息返回的逻辑
String number = data[2];
JOptionPane.showMessageDialog(null,"注册成功,您的账号为:" + number);
break;
case "1":
/**
* 登录功能服务端返回的信息
* 1,ok
* 1,fail,failMessage
*/
String flag = data[1];
if (flag.equals("ok")){
enterMainPage();
}else {
JOptionPane.showMessageDialog(null,data[2]);
}
break;
default:
break;
}
}
private static void enterMainPage() {
while (true){
System.out.println("=========================================================");
System.out.println("|| 1、群聊 2、私聊 3、我的好友列表 4、添加好友 5、退出系统 ||");
System.out.println("=========================================================");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
}
}
/**
* 客户端的登录功能
*/
private static void login(Socket client,Scanner scanner) throws IOException {
System.out.println("请输入您的账号:");
String number = scanner.next();
System.out.println("请输入您的密码");
String password = scanner.next();
/**
* 封装一个自定义的协议数据给服务端发送校验
* 1,number,password
*/
String message = "1," + number + "," + password;
ClientSendMessageUtils.sendMessage(client,message);
}
/**
* 客户端的注册功能
*/
private static void register(Socket socket,Scanner scanner) throws IOException {
System.out.println("请输入您的昵称:");
String nickname = scanner.next();
//数据校验
if (nickname == null || nickname.equals("")){
System.out.println("七七微聊提示您:昵称不能为空!");
return;
}
System.out.println("请输入您的密码:");
String password = scanner.next();
System.out.println("请再次输入您的密码:");
String repass = scanner.next();
//数据校验
if (password == null || repass ==null || password.equals("") || repass.equals("") || password.length() < 6 || repass.length() < 6){
System.out.println("七七微聊提示您:您输入的密码格式不正确,密码不能为空,密码字段不能小于6位!");
return;
}
if (!password.equals(repass)){
System.out.println("七七微聊提示您:两次密码输入不一致!");
return;
}
/**
* 代表用户可以注册,拼接一个自定义协议 让服务端做注册地点逻辑
* 协议为0代表注册
* 0,nickname,password
*/
String message = "0," + nickname + "," + password;
ClientSendMessageUtils.sendMessage(socket,message);
}
}
package com.kang.v5.client;
import java.io.*;
import java.net.Socket;
/**
* 客户端发送数据的封装工具类
*/
public class ClientSendMessageUtils {
private ClientSendMessageUtils(){
}
/**
* 借助某个socket给服务端发送一个消息
* @param socket
* @param message
*/
public static void sendMessage(Socket socket, String message) throws IOException {
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
bw.write(message);
bw.newLine();
bw.flush();
}
}
package com.kang.v5.server;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* User实体类————JavaBean
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
private String nickname;
private String password;
private String number;
}
package com.kang.v5.server;
import java.util.List;
import java.util.Random;
public class ServerUtil {
private ServerUtil(){
}
/**
* 账号必须是唯一的
* @return
*/
public static String generatorNumber(){
//生成一个账号
Random random = new Random();
String number = "" + random.nextInt(10) + random.nextInt(10) + random.nextInt(10) + random.nextInt(10) + random.nextInt(10) + random.nextInt(10);
List<User> users = Server.users;
boolean flag = false;
for (User user : users) {
if (user.getNumber().equals(number)){
flag = true;
break;
}
}
if (flag) {
number = generatorNumber();
}
return number;
}
}
package com.kang.v5.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class ServerSendMessageUtil {
private ServerSendMessageUtil(){
}
public static void sendMessage(Socket socket, String message) throws IOException {
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,"UTF-8"));
bw.write(message);
bw.newLine();
bw.flush();
}
}