聊一聊

网络通信是服务器与客户端之间的数据传输。
客户端连接服务器时需要知道服务器的IP(哪一台电脑)和端口(电脑上具体的应用)。

在Java中,网路传输服务器需要干的事情有:
1.建立基站,表示通过服务器的端口通信:

创建绑定到本机的特定端口的服务器套接字
public ServerSocket(int port) throws IOException

2.等待客户端请求连接:

监听并接收连接到本服务器的客户端连接,此方法会一直阻塞,直至有客户端请求连接
public Socket accept() throws IOException

3.获取客户端的输入输出流
4.关闭流

在Java中,客户端网络传输需要干的事情有:
1.建立与服务器之间的连接:
首先介绍下Socket:
Socket类代表客户端和服务器都用来互相沟通的套接字。客户端通过实例化来获取一个Socket对象,服务器通过accept()的返回值获取一个Socket对象。

//host是服务器的IP地址
//port是主机的端口号 
public Socket(String host, int port)      throws UnknownHostException, IOException

2.利用输入输出流进行数据传输。
3.关闭流

基于单线程的服务端与客户端通信
客户端:

package CODE.Socket聊天室;


import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

//单线程客户端
//1.建立连接
//2.进行数据输入输出
public class SingleTreadClient {
    public static void main(String[] args) {
        try {
            //1.建立连接
            Socket client=new Socket("127.0.0.1",9999);
            //2.进行数据输入输出
            //客户端首先进行从服务器的数据读
            Scanner clientInput=new Scanner(client.getInputStream());
            if(clientInput.hasNext())
            {
                System.out.println("服务器say:"+clientInput.nextLine());
            }

            //客户端进行数据输出,但是先从键盘获取标准输入
            System.out.println("请输入向服务器发送的消息");
            Scanner in=new Scanner(System.in);
            String str="";
            if(in.hasNext())
            {
                str=in.nextLine();
            }
            //向服务器输出
            PrintStream clientOut=new PrintStream(client.getOutputStream(),true,"UTF-8");
            clientOut.println(str);

            //3.关闭流
            clientInput.close();
            in.close();
            clientOut.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

服务端:

package CODE.Socket聊天室;


import Work.Exception.Ex;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

//单线程服务端
public class SingleThreadServer {
    public static void main(String[] args) throws Exception {
        //1.建立基站类
        ServerSocket serverSocket=new ServerSocket(9999);
        //2.等待客户端连接
        System.out.println("等待客户端连接ing");
        Socket client=serverSocket.accept();  //阻塞监听客户端连接
        //3.连接成功后,获取客户端输入输出流
        System.out.println("有新的客户端连接,客户端端口号为:"+client.getPort());

        //客户端输出流
        PrintStream clientOut=new PrintStream(client.getOutputStream(),true,"UTF-8");
        clientOut.println("hello i am server");

        //客户端输入流
        Scanner clientInput=new Scanner(client.getInputStream());
        if(clientInput.hasNext())
        {
            System.out.println("客户端端口号为"+client.getPort()+"say:"+clientInput.nextLine());
        }

        //4.关闭流
        clientInput.close(); //关闭客户端输入流
        clientOut.close();  //关闭客户端输出流
        serverSocket.close(); //关闭基站

    }

}

服务端:
在这里插入图片描述
客户端:
在这里插入图片描述
单线程:也就是说只有服务器只能连接一个客户端,并且读写数据顺序有限制,如果写在读之前,只有写了才能读,放在现实中就是在给好友发消息时,不能接受好友发过来的消息,这明显不合实际,所以,进入多线程,即读写线程并发。

多线程服务端和客户端通信

多线程客户端:

在多线程客户端中接受数据和写入数据可以并发。在实际通信中,对应在给好友发消息时可以接受到好友的消息。

package CODE.Socket聊天室;

//多线程客户端  发送和读取是并行
//读和写是2个线程,但是是一个socket,通过构造

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

//关闭socket是写线程关闭,读线程只读
class ReadFromServer implements  Runnable
{
    //读线程
    private Socket client;
    public ReadFromServer(Socket client)
    {
        this.client=client;
    }

    @Override
    public void run() {
        //获取输入流来取得服务器发来的信息
        try {
            Scanner in=new Scanner(client.getInputStream());
            while(true)
            {
                if(client.isClosed())
                {
                    System.out.println("客户端已关闭");
                    in.close();
                    break;
                }
                else if(in.hasNext())
                {
                    String msgFromServer=in.nextLine();
                    System.out.println("服务器发来的信息为:"+msgFromServer);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//写线程
class WriteToServer implements Runnable
{
    private Socket client;

    public WriteToServer(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        //从键盘获取标准输入
        Scanner in=new Scanner(System.in);
        PrintStream clientOut= null;
        try {
            clientOut = new PrintStream(client.getOutputStream(),true,"UTF-8");
            while(true)
            {
                //获取向服务端发送的信息
                String msgToServer="";
                if(in.hasNext())
                {
                    msgToServer=in.nextLine();
                }
                //向服务端输出信息
                clientOut.println(msgToServer);
                if(msgToServer.contains("bye"))
                {
                    System.out.println("客户端"+client.getInetAddress()+"退出聊天室");
                    //客户端输出bye,关闭该客户端连接
                    clientOut.close();  //关闭输出流
                    in.close();
                    client.close();  //关闭socket,根据输入终止,
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class MultiThreadClient {
    public static void main(String[] args) {
        try {
            //1.建立连接
            Socket client=new Socket("127.0.0.1",9999);
            //2.启动读写线程,进行数据输入输出
            Thread readThread=new Thread(new ReadFromServer(client));
            Thread writeThread=new Thread(new WriteToServer(client));
            readThread.start();
            writeThread.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

多线程服务端:
在多线程中,服务端需要做到:
1.一个服务器可以接收多个客户端请求;
2.保存多个客户端连接
3.服务端中可以将客户端的请求保存,可以将客户端的数据发送指定客户端或所有客户端,可以在客户端退出时及时作出反应。在实际通信中,分别对应私聊、群聊、用户下线。

package CODE.Socket聊天室;


import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

//多线程的服务器
//需要接收多个连接
public class MuliThreadServer {
    //用ConcurrentHashMap是为了保证线程安全,存储每个连接的客户端
    private static Map<String,Socket> clientMap=new ConcurrentHashMap<>();

    //具体执行与每个客户端的通信  内部类,
    private static class RealExcuteClient  implements Runnable
    {
        private Socket client;

        public RealExcuteClient(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            String msgFromClient="";
            try {
                Scanner in=new Scanner(client.getInputStream());  //获取客户端输入流
                while(true)
                {
                    if(in.hasNext())
                    {
                        msgFromClient=in.nextLine();
                        //由于在Windows在\r\n是换行,所以需要消除自带的\r,用" "替换掉
                        Pattern pattern=Pattern.compile("\r"); //表示识别"\r"的格式
                        Matcher matcher=pattern.matcher(msgFromClient); //msgFromClient是否有\r
                        //若有
                        msgFromClient=matcher.replaceAll(" ");

                        //注册:将连接的客户端添加到Map集合中
                        if(msgFromClient.startsWith("userName"))
                        {
                            String name=msgFromClient.split(":")[1];
                            if(addClientToMap(name,client))
                                continue;
                            else
                                break; //该客户端注册失败
                        }

                        //Group代表群聊,comment是内容,Group:comment
                        if(msgFromClient.startsWith("Group")){
                            String comment=msgFromClient.split(":")[1];
                            groupChat(comment);
                            continue;
                        }

                        //private代表私聊,name代表私聊对象,comment代表私聊的内容  private:name-comment
                        if(msgFromClient.startsWith("Private"))
                        {
                            String name=msgFromClient.split(":")[1].split("-")[0];
                            String comment=msgFromClient.split(":")[1].split("-")[1];
                            privateChat(name,comment);
                                continue;
                        }

                        //用户下线,从Map集合中删除该信息   用户名:内容
                        if(msgFromClient.contains("再见,下次再聊"))
                        {
                            String name=msgFromClient.split(":")[0];
                            quitChat(name);
                            break;  //客户端自己会将连接关闭
                        }

                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        //注册
        public static  boolean addClientToMap(String name,Socket client)
        {
            //判断用户名是否已经存在
            if(clientMap.get(name)!=null || client.isClosed())
            {
                System.out.println("用户名已经存在或连接已关闭,注册失败");
                return false;
            }
            clientMap.put(name,client);
            System.out.println("客户端"+name+"已经进入嗨聊空间");
            System.out.println("客户端注册成功,"+"当前嗨聊空间人数为:"+clientMap.size());
            try {
                PrintStream out=new PrintStream(client.getOutputStream());
                out.println(name+"注册成功");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return true;
        }

        //群聊
        public static void groupChat(String comment)
        {
            //将Map集合变为Set集合
            Set<Map.Entry<String,Socket>> clientSet=clientMap.entrySet();
            for(Map.Entry<String,Socket> entry:clientSet)
            {
                //取得每一个客户端的socket
                Socket sigClient=entry.getValue();
                try {
                    PrintStream outToClient=new PrintStream(sigClient.getOutputStream());
                    outToClient.println(comment);
                } catch (IOException e) {
                    System.out.println("群聊异常,错误为:"+e);
                }
            }
        }

        //私聊
        public static void privateChat(String name,String comment)
        {
            Socket privateClient=clientMap.get(name);  //获取对应的客户端socket
            if(privateClient==null)
                System.out.println("私聊失败,私聊对象不存在");
            else{
                try {
                    PrintStream privateOut=new PrintStream(privateClient.getOutputStream());
                    privateOut.println(comment);
                } catch (IOException e) {
                    System.out.println("私聊异常,错误为:"+e);
                }
            }
        }

        //用户下线
        public static void quitChat(String name)
        {
            System.out.println(name+"下线");
            clientMap.remove(name);
            System.out.println("当前嗨聊空间人数为:"+clientMap.size());
        }
    }

    public static void main(String[] args) {
        try {
            //建立基站
            ServerSocket serverSocket=new ServerSocket(9999);
            //线程池,也就是最多允许15个客户端连接服务器
            ExecutorService executorService= Executors.newFixedThreadPool(15);
            for(int i=0;i<15;i++)
            {
                System.out.println("等待客户端连接");
                //返回的是连接的客户端信息
                Socket client=serverSocket.accept();
                System.out.println("有新的客户端连接,客户端端口号为"+client.getPort());
                executorService.submit(new RealExcuteClient(client));
            }
            executorService.shutdown();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

启动3个客户端,结果如下:
服务端:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/86552352