目录
一,网络编程入门
1.1 软件框架
C/S结构:全称Client/Server结构,是指客户端和服务器结构。
B/S结构:全称为Browser/Server结构,是指浏览器和服务器结构。
1.2 网络通信协议
网络通信协议:通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
1.3 网络编程的三要素
1.3.1 协议
计算机网络通信必须遵守的规则。
1.3.2 IP地址
IP地址:指互联网协议地址,俗称IP。
IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
1.3.3 端口号
端口号:用两个字节表示的整数,它的取值范围是0~65535。
二,TCP通信程序
2.1概述
TCP通信能够实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端与服务端。
两端通信时的步骤:
服务端程序,需要事先启动,等待客户端的连接。
客户端主动连接服务端,连接成功才能通信。服务端不可以主动连接客户端。
在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
2.2 Socket类
Socket类:该类实现了客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法 :
Socket client = new Socket("127.0.0.1", 6666);
成员方法 :
public OutputStream getOutputStream() : 返回此套接字的输出流。
public InputStream getInputStream() : 返回此套接字的输入流。
public void close() :关闭此套接字。
public void shutdownOutput() : 禁用此套接字的输出流。
2.3 ServerSocket类
ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法:
ServerSocket server = new ServerSocket(6666);
成员方法:
public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
2.4 简单的TCP网络程序
TCP通信分析
1,服务端启动,创建ServerSocket对象,等待连接。
2,客户端启动,创建Socket对象,请求连接。
3,服务端接收连接,调用accept方法,并返回一个Socket对象。
4,客户端Socket对象,获取OutputStream,向服务端写出数据。
5,服务端Scoket对象,获取InputStream,读取客户端发送的数据。
6,服务端Socket对象,获取OutputStream,向客户端回写数据。
7,客户端Scoket对象,获取InputStream,解析回写数据。
8,客户端释放资源,断开连接。
三, UDP通信
3.1 概述
UDP:用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。
3.2 UDP协议的特点
不需要利用IO流实现数据的传输;
要严格区分为发送端与接收端。
DatagramSocket 类和DatagramPacket类进行UDP协议的编程
3.3 UDP通信分析
1.【接收端】创建DatagramSocket对象,使用socket通信
2.【发送端】,创建DatagramSocket对象,使用socket通信
3.【接收端】指定数据包DatagramPacket,等到发送端发送
4.【发送端】socket对象,调用send()方法,向接收端写数据。
5.【接收端】socket对象,调用receive()方法,接收客户端数据
到此,发送端向接收端发送数据成功。
自此,接收端向发送端回写数据。
6.【发送端】在DatagramSocket中指定回写数据时所用端口。
7.【接收端】在DatagramPacket中准备回写数据并连接,调用send()方法回写数据
8.【发送端】在发送端调用receive()方法接收数据
3.4 UDP协议的弊端
发送数据时,极容易丢包,导致数据不完整,没办法进行正常使用
四,TCP与UDP协议的区别
TCP(Transfer control protocol):是面向连接的协议
优势:TCP提供可靠的服务
UDP(User Datagram Protocol):是无连接的协议
优势:工作效率比TCP高
五、I/O流、线程、网络编程综合运用
仿QQ聊天
public class UserLogin {
/**
* 用户登录类
* 功能
* 1.具备账户信息初始化功能
* 2.具备账户登录校验功能
* @param args
*/
public static void main(String[] args) {
boolean state = login("zhangsan","123456");
System.out.println(state);
}
/**
*账户校验登录的功能
* 登录之前,需要模拟一个数据库(容器)存储账户
* 依据登录的账户名和密码,从数据库中进行校验,如果正确,则显示成功
* @param username
* @param password
* @return
*/
public static boolean login(String username, String password) {
boolean state = false;
Map<String,String> userMap = initUser();
//依据登录的账号和密码,从数据库中进行校验,如果正确,显示登录成功
if(userMap.containsKey(username) && password.equals(userMap.get(username))){
System.out.println("登录成功");
return state = true;
}else{
System.out.println("登录失败");
}
return false;
}
/**
* 模拟一个数据库(容器)存储账户
* Map
* 键代表的是用户名
* 值代表的是用户密码
*
* */
private static Map<String,String> initUser() {
Map<String,String> userMap = new HashMap<>();
userMap.put("zhangsan","123456");
userMap.put("lisi","123456");
userMap.put("wangwu","123456");
userMap.put("zhaoliu","123456");
return userMap;
}
}
public static void main(String[] args) throws IOException {
//创建客户端Socket对象并指定服务器地址和端口号
Socket socket = new Socket("localhost",6666);
//依据需求,进行读写功能(发送用户名和密码)
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String username = scanner.next();
System.out.println("请输入密码");
String password = scanner.next();
//调用Socket对象的getOutstream方法获得字节输出流对象
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println(username+"-"+password);
pw.flush();
//调用Socket对象的getInputStream方法获得字节输入流对象
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String state = br.readLine();
System.out.println("state:"+state);
//如果登录成功,就可以开始聊天了
if(Boolean.valueOf(state)){
//对聊天服务器进行请求
chat(username);
}
}
/**
* 开始聊天
* 连接服务器
*
*/
private static void chat(String username) throws IOException {
//创建客户端Socket对象并指定服务器地址和端口号
Socket socket = new Socket("localhost",7777);
//可以和聊天服务器进行通信
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println(username);
pw.flush();
//分别创建读的线程,进行读取操作
new ReaderThread(socket).start();
//创建一个写的线程,进行写出操作
new WriterThread(socket).start();
}
}
public class ChatServer {
/**
* 服务器实现步骤
* 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)
* 调用ServerSocket对象的accept()方法等待客户端连接并获得对应的Socket对象
* 依据需求,进行读写功能
*/
static Map<String, Socket> socketMap = new HashMap<>();
public static void main(String[] args) throws IOException {
//创建ServerSocket对象并指定端口(相当于个服务器)
ServerSocket server = new ServerSocket(7777);
while(true){
System.out.println("聊天服务器已启动,等待客户端连接");
//调用ServerSocket对象的accept方法等待客户端连接并获得对应的Socket对象
Socket socket = server.accept();
System.out.println("连接成功");
//依据需求,进行读写功能
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String username = br.readLine();
System.out.println("读取到的用户名:" + username);
socketMap.put(username, socket);
//每接收到一个客户端要求,就素响应,并开启一个线程与之进行交互
new ChatThread(socket).start();
}
}
}
public class ChatThread extends Thread{
private Socket socket;
public ChatThread(Socket socket){
this.socket = socket;
}
/**
* 聊天线程,
* 群聊
* 默认
* 结果:将用户发送的消息,转发给所有的用户
*
* 私聊:
*/
//任务:聊天
@Override
public void run() {
//一定是循环多次的
while(true){
//获取用户发送的信息,判断:群聊还是私聊
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String message = br.readLine();
System.out.println("读取到的信息为:" + message);
//依据用户发送的消息,判断是群聊还是私聊
//依据-切割符获取values,通过判断values长度
//举例说明:"hello" ,values 长度为1,内容为 "hello" ==> 没有标记 ==> 群聊
//举例说明:"lisi-哈哈哈哈" values长度为2 ===》私聊
String[] values = message.split("-");
//将该用户发送的消息,转发给所有的用户(socket)
//从socketMap中获取socket,消息的发送
//socketMap(ABCD)==>keySet ==>key(n……) ===> socket(n……)
//遍历获取每一个socket,只要不是当前用户的socket,就将当前的信息进行发送
Map<String,Socket> socketMap = ChatServer.socketMap;
Set<String> keySet = socketMap.keySet();
if(values.length == 1){//群聊,将用户发送的消息,转发给所有的用户
for(String username : keySet){
Socket socket2 = socketMap.get(username);
if(this.socket != socket2){
//发送信息message给所用的用户(除了当前用户)
OutputStream os = socket2.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println(message);
pw.flush();
}
}
}else{
//私聊,将该用户发送的消息,转发给指定的用户
//标记中一定要包含指定用户的values[0]--->usename,
//通过username获取对应的socket
//username-聊天信息
String username = values[0];
//通过username获取对应的socket
Socket socket2 = socketMap.get(username);
//私聊,将该用户发送的消息,发送给指定的用户
OutputStream os = socket2.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println(values[1]);
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class LoginServer {
/**
* 服务器实现步骤
* 创建ServerSocket对象并指定端口(相当于开启了一个服务器)
* 调用ServerSocket对象的accept方法等待客户端连接并获得对应Socket对象
* 依据需求,进行读写功能
* 调用Socket对象的getInputStream方法获得字节输入流对象
* 调用Socket对象的getOutputStream方法获得字节输出流对象
*
*
* 登录服务器
* 1.基于网络编程实现与客户端进行一次通信(只能读)
* 2.基于网络编程实现与客户端进行一次通信(可以读写)
* 3.将服务器与客户端进行一对一通信升级为一对多通信
* 4.将普通的通信内容,替代为登录用户名和密码校验功能
*
* @param args
*/
public static void main(String[] args) throws IOException {
//创建ServerSocket对象并指定端口(相当于个服务器)
ServerSocket server = new ServerSocket(6666);
while (true) {//服务器需要不断的进行响应和通信
System.out.println("登录服务器已启动,等待客户端连接");
//调用ServerSocket对象的accept方法等待客户端连接并获得对应的Socket对象
Socket socket = server.accept();
System.out.println("连接完毕");
//依据需求,进行读写功能
//调用Socket对象的getInputStream方法获得字节输入流对象
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//将格式为zhangsan-123456的信息,转换为使用UserLogin类登录功能的参数
String message = br.readLine();
System.out.println("message:" + message);
String[] values = message.split("-");
String username = values[0];
String password = values[1];
System.out.println(username+":"+password);
boolean state = new UserLogin().login(username,password);
System.out.println("state:"+state);
//调用Socket对象的getOutputStream方法获得字节输出流对象
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println(state + "");
pw.flush();
}
}
}
public class ReaderThread extends Thread {
private Socket socket;
public ReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
//进行写的操作(循环多次)
while(true){
OutputStream os = null;
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = br.readLine();
System.out.println("客户端读取到的信息:" + line );
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class WriterThread extends Thread{
private Socket socket;
public WriterThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入聊天内容:");
//进行写的操作(循环多次)
while(true){
OutputStream os = null;
try {
os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
Scanner sc = new Scanner(System.in);
/* System.out.println("请输入聊天内容:");*/
String message = sc.next();
pw.println(message);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}