从简单到复杂实现TCP客户端服务器通信

                                                    **

首先我们要知道服务器客户端的概念。
客户端-服务器(Client/Server)结构,简称C/S结构,是一种软件系统体系结构,它把客户端 (Client) 与服务器 (Server) 区分开来,每一个客户端软件的实例都可以向一个服务器或应用程序服务器发出请求。由美国Borland公司最早研发,目前最流行的两大主流软件体系结构之一,另一个是美国微软公司研发的浏览器-服务器(Browser/Server,B/S)结构。服务器端一般使用高性能的计算机,并配合Oracle、Sybase等大型数据库;客户端需要安装专门的软件。
socket通信模型
Socket通信模型
客户端套接字是Socket,绑定了服务器的IP地址和端口号。
服务器套接字是ServerSocket,绑定了端口号。
然后通过TCP或者UDP协议通过io流进行通信。

几个名词解释
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。所以可以把客户端/服务器看作一种特殊的文件,客户端/服务器从流中读、写数据可以看成是从文件中“读、写”数据。
ip地址,每一个网络、每一台主机都具有唯一的ip地址,可以把它看成是电话通信中的电话号码,电话号码可以唯一指定是那台电话。
端口:可以唯一指定是哪个应用,范围0-65535,1024前一般为系统应用所使用。
TCP协议,一种传输协议,与UDP区别是它必须建立连接才可通信,可从accept方法看出,以数据流的方式传输数据,深层原理不太清楚。
UDP协议,传输协议,不必建立连接,以数据包的形式传输数据,数据容易丢失。
TCP协议通信模型
UDP通信协议模型

TCP协议通信-代码流程
客户端:
1、创建套接字(指定ip和端口)
2、创建输出流OutputStream对象,发送数据
3、创建输入流InputStream对象,接收服务器发来的数据
4、释放资源
服务器:
1、创建套接字(指定端口)
2、创建输入流InputStream对象,接收客户端发送的信息
3、创建输出流OutputStream对象,发送数据到客户端
4、释放资源

到此,我们就可以写一个简单的客户端和服务器通信的简单代码了。
简单程序1

客户端

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client{
    public static void main(String[] args) throws IOException {
        // 创建Socket套接字对象
        Socket s = new Socket("localhost", 8888);
        // 获取输出流对象,写数据
        OutputStream os = s.getOutputStream();
        os.write("hello,世界".getBytes());
        // 释放资源
        s.close();
    }
}

服务器

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server{
    public static void main(String[] args) throws IOException {
        // 创建接收端的套接字对象
        ServerSocket ss = new ServerSocket(8888);
        // 监听客户端连接,返回相应的Socket对象
        Socket s = ss.accept();
        // 获取输入流对象,从输入流中获取数据
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String str = new String(bys, 0, len);
        System.out.println("From Client" + "(" + s.getInetAddress().getHostAddress() + "):" + str);
        // 释放资源
        s.close();
    }
}

这个程序就可以看到当客户端连接到服务器时,服务器就可以接收到客户端发送来的数据。
程序结果1

但是这个程序只能看到客户端发送的一句话,并不能从客户端输入什么服务器就接收什么。要实现这个效果就必须假如多线程,一个线程负责发送数据,另一个线程负责接收数据。

客户端

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("localhost", 8080);
        // 标准读入流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("请输入用户名:");
        String name = br.readLine();
        // 启动线程,监听读入流
        ClientRunable cr = new ClientRunable(s);
        new Thread(cr).start();
        // 发送消息到服务器
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String str = null;
        bw.write(name);
        bw.newLine();
        bw.flush();
        while (true) {
            str = br.readLine();
            bw.write(name + ":" + str);
            bw.newLine();
            bw.flush();
        }
    }
}

class ClientRunable implements Runnable {
    private Socket s;

    private BufferedReader br;

    public ClientRunable(Socket s) throws IOException {
        this.s = s;
        br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    }

    public void run() {
        while (true) {
            try {
                String s = br.readLine();
                System.out.println(s);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

服务器

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8080);
        System.out.println("等待客户端连接...");
        while (true) {
            // 等待客户端连接
            Socket s = ss.accept();
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
            System.out.println(br.readLine() + "已连接");
            // 启动线程,处理输入、输出流对象
            ServerRunnable sr = new ServerRunnable(s);
            Thread t1 = new Thread(sr);
            t1.start();
        }
    }
}

class ServerRunnable implements Runnable {
    Socket s;

    public ServerRunnable(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
            bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream(), "GBK"));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        while (true) {
            String s;
            try {
                s = br.readLine();
                System.out.println(s);
                bw.write(s);
                bw.newLine();
                bw.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

程序结果2
这样的话就可以实现客户端输入什么服务器就显示什么了,但是当多个客户端连接进来时,并不能实现多个客户端同时通信。所以为了实现多个客户端通信,我们可以有一下思路:在服务器新建一个队列,当有客户端连接进来时就把他加到该队列中,然后服务器发送数据时就遍历这个队列,向这个队列的每个Socket的输出流都写入数据。

客户端

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("localhost", 8080);
        // 标准读入流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in, "GBK"));
        System.out.print("请输入用户名:");
        String name = br.readLine();
        // 启动线程,监听读入流
        ClientRunable cr = new ClientRunable(s);
        new Thread(cr).start();
        // 发送消息到服务器
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream(), "GBK"));
        String str = null;
        bw.write(name);
        bw.newLine();
        bw.flush();
        while (true) {
            str = br.readLine();
            bw.write(name + ":" + str);
            bw.newLine();
            bw.flush();
        }
    }
}

class ClientRunable implements Runnable {
    private Socket s;

    private BufferedReader br;

    public ClientRunable(Socket s) throws IOException {
        this.s = s;
        br = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
    }

    public void run() {
        while (true) {
            try {
                String s = br.readLine();
                System.out.println(s);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

服务器

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
    private static ArrayList<Socket> arraySocket = new ArrayList<Socket>();
    static ArrayList<ServerRunnableReader> arrayThread = new ArrayList<ServerRunnableReader>();
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8080);
        System.out.println("等待客户端连接...");
        while (true) {
            // 等待客户端连接
            Socket s = ss.accept();
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK"));
            System.out.println(br.readLine() + "已连接");
            // 将连接的客户端加入到队列
            arraySocket.add(s);
            Start(new BufferedReader(new InputStreamReader(s.getInputStream(), "GBK")));
        }
    }

    private static void Start(BufferedReader br) {
        BufferedWriter[] bw = new BufferedWriter[arraySocket.size()];
        int i = 0;
        try {
            for (Socket as : arraySocket) {
                bw[i] = new BufferedWriter(new OutputStreamWriter(as.getOutputStream(), "GBK"));
                i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        ServerRunnableReader srr = new ServerRunnableReader(br);
        Thread t1 = new Thread(srr);
        t1.start();
        arrayThread.add(srr);
        for (ServerRunnableReader thread : arrayThread) {
            thread.setBw(bw);
        }
    }
}

class ServerRunnableReader implements Runnable {
    BufferedReader br = null;
    BufferedWriter[] bw = null;

    public ServerRunnableReader(BufferedReader br) {
        this.br = br;
    }

    public void setBw(BufferedWriter[] bw) {
        this.bw = new BufferedWriter[bw.length];
        for (int j = 0; j < bw.length; j++) {
            this.bw[j] = bw[j];
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                String s = br.readLine();
                System.out.println(s);
                for (BufferedWriter bufferedWriter : bw) {
                    bufferedWriter.write(s);
                    bufferedWriter.newLine();
                    bufferedWriter.flush();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

程序结果3

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值