写在前面
网络通信是计算机同学必须要了解的一项内容,因为我们的数据都是通过网络进行传递和获取的,那么今天的这篇博客呢,算是一个计算机网络的入门,边实践,边认识我们的数据是怎么通过网络一步步传送到其他人的主机上的。
之前写的画图板的博客,想要复习的同学戳这里:关于画图板的介绍
一、认识客户端与服务端
服务端:服务于客户端的计算机程序。 客户端:运行在我们的手机,电脑中的应用程序,例如QQ,微信。为客户提供本地服务。 服务端说白了就是一个中介,将客户端发送过来的数据转发给其他客户端。 客户端与客户端之间不相互连接,客户端连接上服务端之后,进行数据的传送和收取。 如下图所示:
先了解了两者是什么,就进入正题,怎么实现网络画图板呢? 1、先创建一个服务端,作用是转发从客户端中传递过来的数据给其他客户端,等待客户端的连接。 2、创建一个客户端画板,监听用户的动作,数据发送给服务端。 3、客户端连接上服务端,接收服务端发来的数据,然后显示在画图板上。
二、IP地址与端口
要想客户端连接上服务器,那么我们就先要指导两个概念,首先是IP地址,这个就相当于计算机的门牌号码,根据这个就可以找到对应的计算机,但是一般的计算机的IP地址都不是固定的,在不同的局域网下面IP地址就会发生变化 想要知道自己主机的IP地址,可以在cmd里面输入ipconfig 命令,其中的ipV4地址就是当前的IP地址 我们知道了计算机的门牌号码之后,就可以进入这个房子,但是房子里面的房间才是我们想要访问的对象,所以我们还需要知道房间的号码,但是一个房子的房间不是无穷的,所以号码的范围是0-65535,且必须是整数。这里所说的房间号码就是端口号
二、网络画图
1.创建服务端
我们先创建一个服务端,设置连接的端口为9999,当然,这个数字在0-65535之间都可以,但是有些端口已经被系统应用占用了。
ServerSocket ss= new ServerSocket(9999);
ArrayList<Socket> socketList = new ArrayList<Socket>();
创建好了之后,服务端就需要等到客户端的连接,而且需要一直进行等待,所以这里需要一个while循环,为了实现对客户端的管理,我们将连接进的客户端放进链表里面
while(true){
Socket socket = ss.accept();
socketList.add(socket);
}
2、输入流与输出流
我们通过输入流接收服务器发来的数据,通过输出流接收数据。 其中输入流类为InputStream,输入流类为OutputStream。
InputStream ins = ss.getInputStream();
int i = ins.read();
OutputStream ins = ss.getOutputStream();
ous.write(i);
注意:通过查看源代码,我们可以知道InputStream是一个一个字节的读取,一个字节为8位,所以返回的数值的范围为0-255,但是我们传送的数据的范围可能超过这个数值,所以现在就有两种方法: 1、处理接收的数据,将数据拼接成一个int 2、使用DataInputStream,DataOutputStream,这两个类进行了封装,我们可以直接调用其中的方法对某个类型的数据进行接收。
DataInputStream dins = new DataInputStream(ins);
int i = dins.readInt();
DataOutputStream dous = new DataOutputStream(ous);
dous.writeInt(i)
3、服务端转发数据
由于客户端发送的数据具有突发性,所以我们需要一直接收数据,来应对客户端数据的发送。由于对每个客户端的数据进行接收转发,所以这里我们运用到了线程,每连接一个客户端,就创建一个线程,使用客户端的输入流接收他传过来的数据,再使用其他客户端的输出流将数据进行转发。
另外我们现在实现的是对客户端再画图板上画出来的直线的转发,所以接收两个点的数据就可以了,
public class SendThread extends Thread {
private Socket socket;
private InputStream ins;
private DataInputStream dins;
private OutputStream ous;
private DataOutputStream dous;
public SendThread(Socket socket) {this.socket = socket;}
@Override
public void run() {
try {
ins = socket.getInputStream();
dins = new DataInputStream(ins);
int x1, y1, x2, y2;
while (true) {
System.out.println("服务器在读取数据");
x1 = dins.readInt();
y1 = dins.readInt();
x2 = dins.readInt();
y2 = dins.readInt();
for (int i = 0; i < Server.socketList.size(); i++) {
Socket socket = Server.socketList.get(i);
ous = socket.getOutputStream();
dous = new DataOutputStream(ous);
if (socket != this.socket) {
dous.writeInt(x1);
dous.writeInt(y1);
dous.writeInt(x2);
dous.writeInt(y2);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
所以之前创建服务端的代码变成了这样:
public class Server {
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public void waitConnect() throws IOException {
try{
ServerSocket ss= new ServerSocket(9999);
while(true){
Socket socket = ss.accept();
socketList.add(socket);
System.out.println("一个新的客户机连接进来了");
SendThread thread = new SendThread(socket);
thread.start();
}
}catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
Server server = new Server();
server.waitConnect();
}
}
以上就是服务端的两个类。
4、客户端画板
关于画图板的代码,这里就不再赘述了,之前我写过的博客里面进行了详细的介绍,这里需要两个类,一个是ClientUI(画图板界面),ClientUIListener(画图板监听器) 我们想要的效果就是在左边的输入框输入IP,右边的输入框输入端口,点击连接,就连接服务端。这里对按钮进行监听,因为输入框里面的内容是字符型的,所以需要处理一下。
@Override
public void actionPerformed(ActionEvent e) {
String str = e.getActionCommand();
if (str.equals("连接")) {
String ip = ipField.getText();
String port = portField.getText();
conn = new Clientconn(ip, Integer.parseInt(port), g);
conn.init();
if(conn.getConnState()){
listener.setConn(conn);
}
}
}
5、客户端连接并接收数据
其中我们创建Clientconn类连接服务器 其中最关键的代码是
Socket socket = new Socket(ip, port);
其中定义一个函数conn2Server,连接服务器,并且返回连接的状态:
public boolean conn2Server() {
try {
socket = new Socket(ip, port);
Boolean connState = true;
ous = socket.getOutputStream();
dous = new DataOutputStream(ous);
ins = socket.getInputStream();
dins = new DataInputStream(ins);
System.out.println("我已经连接上服务器了");
} catch (IOException e) {
e.printStackTrace();
}
return connState;
}
我们对这个连接的状态进行弹出框显示:
public void connect() {
if (!conn2Server()) {
JOptionPane.showMessageDialog(null, "连接失败", "Title", JOptionPane.ERROR_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "连接成功");
}
}
与服务端一样,我们客户端的接收也需要一直进行,所以这里单开一个线程。
public class ReceiveThread extends Thread {
private Graphics g;
private Clientconn conn;
private DataInputStream dins;
public ReceiveThread(Graphics g, Clientconn conn) {
this.g = g;
this.conn = conn;
}
@Override
public void run() {
int x1, y1, x2, y2;
while (true) {
try {
dins = conn.getDataInputStream();
System.out.println("我在接受数据");
x1 = dins.readInt();
y1 = dins.readInt();
x2 = dins.readInt();
y2 = dins.readInt();
g.drawLine(x1, y1, x2, y2);
} catch (IOException e) {
e.printStackTrace();
conn.setConnState(false);
}
}
}
}
所以连接类的init()函数:
public void init() {
connect();
new CheckThread(this).start();
new ReceiveThread(g, this).start();
}
现在只差监听画板时,将两个坐标传送过去了
public void mousePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
}
public void mouseReleased(MouseEvent e) {
int x2 = e.getX();
int y2 = e.getY();
g.drawLine(x1, y1, x2, y2);
if(conn!=null){
conn.send2Server(x1, y1, x2, y2);
}
}
写在最后
通过这个项目,我们了解了通信的基本过程,下一篇博客我会介绍客户端与服务端断开如何重连。 欢迎各位小伙伴私信我交流相关的技术实现。
|