文章目录
P1 关于网络编程的几个知识点
1 TCP/IP协议
TCP/IP协议并非一个协议,由两个协议族组成,IP协议族和TCP协议族,其中IP协议工作在网络层,TCP工作在传输层
TCP协议是端对端的通信协议,通过IP地址和IP协议可以找到通信的对象,即某设备,但还需要知道是哪个进程在进行本次通信,为了解决这个问题,TCP提出了“端”的概念,即端口号,端口号是一个2B无符号编号,从0-65535,不同的通信双方需要先指定一个端口号,以便将信息传输给指定的应用程序
2 TCP和UDP
TCP协议族有两个重要的协议:TCP协议和UDP协议,前者称为面向连接后者称为面向无连接
面向连接是保证质量的数据传输,面向无连接是不保证质量的数据传输
对于速度要求更高的应用场景,可以选择面向无连接的数据传输方式(UDP),对于每个字节都不能出错的应用场景,使用面向连接的数据传输方式(TCP)
3 MAC地址和IP地址
连入网络的计算机或其它设备,都必须要有至少一块联网电路板,这块电路板叫做MAC,俗称网卡,现在的计算机网卡一般被集成到了主板上,每块网卡都有全世界唯一的6字节编号,这个编号即MAC地址,也称局域网地址,以太网地址,物理地址
但是MAC地址很难记,如果网卡可以拆卸下来,那么MAC地址也会变化,这对网络通信是很棘手的问题,为此出现了IP地址,操作系统会将IP地址和MAC地址绑定,由此避免了MAC地址变化的麻烦
127.0.0.1在网络编程中代替本地地址
P2 简单网络通信形式
1 P2P模式
P2P(对等网络模式):对端到对端,这是一种对等网络通信形式,在P2P网络中,各个联网的节点的身份都是一样的,没有高低主从之分,没有权限差异
实现P2P通信,通信双方必须知道对方的IP,并且事先确定好端口号,因为这种方式不存在管理者和被管理者,通信方式比较自由,其编程逻辑也比较简单
但这种通信形式,不利于集群管理,如对于聊天室一类的应用程序,需要一个“管理员”,能够统一管理各个接入聊天室的终端,以便完成类似群发消息,屏蔽消息,强制下线等操作
2 C/S模式
C/S(服务器/客户机模式):与P2P工作模式不同,C/S对接入网络的终端将分为服务器和客户机两种,它们的身份不同,权限不同,有主有从
服务器和客户机指的都是软件,运行着服务器软件的机器就是服务器,运行客户机软件的机器就是客户机,并不是由硬件决定,也有机器可以同时作为服务器和客户机
(1) 服务器
服务器是C/S网络里的核心,如果服务器没有运行,或者服务器出现故障,那么整个网络就会瘫痪
服务器的几个特点:
1,服务器是必须众所周知的,服务器的IP地址和端口号必须公之于众,否则客户机找不到服务器
2,服务器需要响应客户机的联网请求,服务器需要不断地侦听来自客户机的联网请求,并将客户机加入到网络中,才能形成网络
3,服务器与多个客户机连接并通信,对于每一个客户机,服务器要用一个独立的线程实现与该客户机的通信
4,服务器负责处理来自客户机的信息,包括信息转发,资源请求等等
5,服务器也可以主动和客户机通信,实现对客户机的主动管理,如强制下线某客户机,通知一个客户机其它客户机的状态等等
6,服务器一旦终止工作,整个C/S网络服务崩溃
(2) 客户机
客户机的几个特点:
1,客户机必须知道服务器的IP地址和端口号,以便向服务器发出连接请求
2,客户机必须先连接到服务器,才能获得服务
3,客户机只能从服务器那里得知其它客户机的状态
4,客户机之间的通信,必须由服务器进行转发
5,客户机可以随时断开与服务器的连接,下线的客户机可以再次与服务器连接,只要服务器仍在正常工作
P3 简单的一对一C/S连接
对于C/S模式,一定有两套软件:Server和Client,无论是Server还是Client,都是基于网络通信,网络通信基于通信信道,通信信道基于端对端的TCP工作协议
配置信息接口:
package com.mec.net.one2one.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public interface INetConfig {
//默认ip地址
String ip = "127.0.0.1";
//默认端口号,要避免使用已经被使用过的端口号
int port = 54188;
//关闭输入输出流,关闭连接
default void close
(InputStream is, OutputStream os, Socket socket) throws IOException {
if(is != null) {
is.close();
}
is = null;
if(os != null) {
os.close();
}
os = null;
if(socket != null && !socket.isClosed()) {
socket.close();
}
socket = null;
}
}
1 Server类
package com.mec.net.one2one.core;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server implements INetConfig{
//服务器端口
private ServerSocket server;
//服务器端口号,建议不要使用10000号前的端口号
private int port;
//连接需要用到的输入,输出流
private DataInputStream dis;
private DataOutputStream dos;
public Server() {
this.port = INetConfig.port;
}
public void setPort(int port) {
this.port = port;
}
public void startServer() throws IOException {
//初始化服务器连接
server = new ServerSocket(port);
//侦听连接请求,建立连接
//这里在没有等到连接请求时,37行后的代码会被阻塞
Socket socket = server.accept();
//从建立好的连接中获取输入,输出流,类似于电话必须需要麦克风和耳机
//接收者的接收流接收的是发送者发送流传来的消息
//即你的耳机输出的是和你通信的人的麦克风的输入
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
//连接成功后,且建立输入,输出流后就可以通信
String mess = this.dis.readUTF();
System.out.println("来自客户端的消息:" + mess);
this.dos.writeUTF("[" + "成功建立连接!" + "]");
//关闭输入输出流,断开连接
close(this.dis, this.dos, socket);
}
}
2 Client类
package com.mec.net.one2one.core;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client implements INetConfig{
//连接端口
private Socket socket;
//确定服务器的ip地址
private String ip;
//客户端连接端口号
private int port;
//连接需要用到的输入,输出流
private DataInputStream dis;
private DataOutputStream dos;
public Client() {
this.ip = INetConfig.ip;
this.port = INetConfig.port;
}
public void setIp(String ip) {
this.ip = ip;
}
public void setPort(int port) {
this.port = port;
}
public void startConnect()
throws UnknownHostException, IOException {
//发出连接到服务器的请求,建立连接
socket = new Socket(ip, port);
//从连接中获取输入,输出流
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
//连接成功后,且建立输入,输出流后就可以通信
this.dos.writeUTF("hello,server!");
String mess = this.dis.readUTF();
System.out.println("来自服务器的消息" + mess);
//关闭输入输出流,断开连接
close(this.dis, this.dos, socket);
}
}
3 测试,小结
(1) 连接测试
对于Server和Client类,各自都有一个测试类:
TestServer:
TestClient:
启动时,需要先启动服务器,在启动客户端,如果先启动客户机,那么将会抛出找不到服务器IP地址的异常,拒绝连接,测试结果:
先启动服务器:
此时的服务器在侦听来自客户机的连接
启动客户机,服务器的控制台:
成功建立连接,服务器收到了来自客户机的消息
客户机的控制台:
服务器收到客户机的消息后,向客户机发送一条消息
(2) 小结
单对单的C/S连接,很像两个人打电话时的情况,A向B拨打电话,A是客户机,B是服务器,A发起连接,B侦听到连接后与A通信 ,这里的“连接 socket”可以理解为电话线,二者间存在一个连接,这个连接socket内包含了双方各自的输入,输出流,A发送消息需要使用输出流,B接收消息需要使用输入流,通信结束后,需要关闭输入输出流,即断开连接
但是上述的程序非常简单,没有实用意义,以聊天室为例,通信中的会话是由用户来决定的,且一个服务器将要面对多个客户端,它需要为每个客户端创建一个线程专门用来解决与该客户端的通信