• java网络编程基础——TCP网络编程一


    基于TCP协议的网络编程

    TCP/IP协议是一种可靠的网络协议,它的通信的两端各自建立一个Socket,从而在通信的两端之间形成网络虚拟链路。

    Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。

    1、ServerSocket 

    java中能接收其他通信实体连接请求的类是ServerSocket,他会监听来自客户端Socket连接,如果没有连接,它将一直处于等待状态。

    ServerSocket常用方法:

    Socket accept():如果接收到客户端Socket的连接请求,返回一个与客户端Socket对应的Socket(每个TCP连接有两个Socket),否则一直处于等待状态,线程被阻塞。

    ServerSocket构造器:

    ServerSocket(int port):指定端口创建ServerSocket.

    ServerSocket(int port ,int backlog):增加一个用来改变连接队列长度的参数backlog.

    ServerSocket(int port ,int backlog ,InetAddress localAddr):机器有多IP时候,可以指定IP创建ServerSocket.

    2、使用Socket进行通信

    客户端可以使用Socket的构造器连接到指定服务器,Socket有两个构造器:

    Socket(InetAddress/String remoteAddress,int port):创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本机默认ip,默认使用系统动态分配的端口。

    Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):穿件Socket带有本地IP和port

    package net;
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
     
    public class SocketServer {
     
        public static void main(String[] args) throws IOException {
            //定义服务端口号
            int port = 7000;
            //获取ServerSocket对象
            ServerSocket serverSocket = new ServerSocket(port);
            while(true) {
                //监听来自客户端的连接
                Socket socket =serverSocket.accept();
                PrintStream pis = new PrintStream(socket.getOutputStream());
                pis.print("Hi:来自服务端的问候");
                pis.close();
                socket.close();
            }
                     
        }
    }
     
    package net;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    import java.net.Socket;
    import java.net.UnknownHostException;
    public class SocketClient {
     
        public static void main(String[] args)  {
             
            //定义客户端端口
            int port = 7000;
            //获取Socket
            Socket socket = null;
            BufferedReader br = null;
            try {
                socket = new Socket("localhost",port);
                //设置10s超时
                socket.setSoTimeout(10000);
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //获取数据
                System.out.println(br.readLine());
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    br.close();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
             
             
             
             
             
        }
    }

    3、加入多线程

    上面程序只是简单的实现了Server和Client的简单通信:服务器接收到客户端的连接后想客户端发送了一个字符串,客户端读取字符串后就退出了。

    实际应用中客户端可能需要跟服务器保持长时间通信,在使用传统的BufferReader的readLine方法读取数据时,在该方法返回成功之前,线程被阻塞,考虑到此点,所以服务器端应该为每个Socket单独启动一个线程,负责与客户端通信。

    下面程序实现了命令行聊天大厅功能(参考疯狂java讲义)

    服务端:

    package chatServer;
     
    /**
     * 协议字符
     * @author rdb
     *
     */
    public interface ChatProtocol {
     
        //协议字符串长度
        int PROTOCOL_LEN = 2;
        //公发前后端字符
        String MSG_ROUND = "☾△";
        //用户前后字符
        String USER_ROUND = "△▽";
        //登录成功表示
        String LOGIN_SUCCESS = "1";
        //用户重复标识
        String NAME_REP = "-1" ;
        //私发用户标识
        String PRIVATE_ROUND = "◆◆" ;
         
        String SPLIT_SIGN = "◆" ;
    }
     
    package chatServer;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    /**
     * 
     * ChaMap:保存用户和对应的输出流,一一对应,不允许出现value重复
     * @author rdb
     * @param <K>
     * @param <V>
     */
    public class ChatMap<K,V> {
     
        //创建一个线程安全的HashMap
        public Map<K,V> map = Collections.synchronizedMap(new HashMap<>());
         
        //获取value的set集合
        public synchronized Set<V> valueSet(){
            Set<V> set = new HashSet<>();
            map.forEach((key,value) -> set.add(value));
            return set ;
        }
         
        //实现put方法,不允许出现重复value
        public synchronized  V put(K key,V value) {
            for(V v : valueSet()) {
                if(v.equals(value) && v.hashCode() == value.hashCode()) {
                    throw new RuntimeException("ChatMap中不允许有重复value");
                }
            }
            return map.put(key, value);
        }
         
        //根据value查找key
        public synchronized K getKeyByValue(V value) {
            for(K k : map.keySet()) {
                if(map.get(k) == value && map.get(k).equals(value)) {
                    return k;
                }
            }
            return null;
        }
         
        //根据value删除
        public synchronized void removeByValue(V value){
            for(K k : map.keySet()) {
                if(map.get(k) ==  value) {
                    map.remove(k);
                    break;
                }
            }
        }
    }
     
    package chatServer;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    public class ChatServer {
     
        private static final int SERVER_PORT = 30000 ;
        //用户保存客户端用户和对应socket输出流
        public static ChatMap<String, PrintStream> chatMap = new ChatMap<>();
         
        public void init() {
            try {
                ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
                while(true) {
                    Socket socket = serverSocket.accept();
                    System.out.println("******");
                    //启动对应的线程处理对应的客户端
                    new Thread(new ServerThread(socket)).start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
         
        public static void main(String[] args) {
            ChatServer chatServer = new ChatServer();
            chatServer.init();
        }
    }
     
    package chatServer;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    import java.net.Socket;
    public class ServerThread implements Runnable {
     
        private Socket socket = null;
        private BufferedReader br = null;
        private PrintStream ps = null;
     
        public ServerThread(Socket socket) {
            this.socket = socket;
        }
     
        @Override
        public void run() {
            try {
                // 获取socket对应的输入流
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                // 获取socket对应的输出流
                ps = new PrintStream(socket.getOutputStream());
                // 获取客户端的消息
                String line = null;
                while ((line = br.readLine()) != null) {
                    // 判断信息类型
                    // 用户登录
                    if (line.startsWith(ChatProtocol.USER_ROUND) && 
                    line.endsWith(ChatProtocol.USER_ROUND)) {
                        String userName = getRealMsg(line);
                        // 如果包含该用户,返回重复登录
                        if (ChatServer.chatMap.map.containsKey(userName)) {
                            System.out.println("重复");
                            ps.println(ChatProtocol.NAME_REP);
                        } else {
                            System.out.println("成功");
                            ps.println(ChatProtocol.LOGIN_SUCCESS);
                            ChatServer.chatMap.put(userName, ps);
                        }
                    }
                    // 私聊
                    else if (line.startsWith(ChatProtocol.PRIVATE_ROUND) && 
                    line.endsWith(ChatProtocol.PRIVATE_ROUND)) {
                        String userAndMsg = getRealMsg(line);
                        // 私聊时 SPLIT_SIGN前是私聊对象,后面是私聊信息
                        String user = userAndMsg.split(ChatProtocol.SPLIT_SIGN)[0];
                        String msg = userAndMsg.split(ChatProtocol.SPLIT_SIGN)[1];
                        // 向对应的客户端发送消息
                        ChatServer.chatMap.map.get(user).
                        println(ChatServer.chatMap.getKeyByValue(ps) + "悄悄的对你说:" + msg);
                    }
                    // 公聊
                    else {
                        String msg = getRealMsg(line);
                        // 向每个用户发送消息
                        ChatServer.chatMap.map.forEach((key, value) -> 
                        value.println(ChatServer.chatMap.getKeyByValue(ps) + "说:" + msg));
                    }
                }
            }
            // 捕获到异常表示对应的Socket的客户端出现问题,在map中移除该客户端信息
            catch (IOException e) {
                ChatServer.chatMap.removeByValue(ps);
                System.out.println(ChatServer.chatMap.map.size());
                // 关闭相关资源
                try {
                    if (br != null) {
                        br.close();
                    }
                    if (ps != null) {
                        ps.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
     
        private String getRealMsg(String msg) {
            return msg.substring(ChatProtocol.PROTOCOL_LEN, msg.length() - ChatProtocol.PROTOCOL_LEN);
        }
     
    }

    客户端:

    package charClient;
     
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    import java.net.Socket;
     
    import javax.swing.JOptionPane;
     
    /**
     * 客户端
     * @author rdb
     *
     */
    public class ChatClient {
     
        private static final int SERVER_PORT = 30000;
        private static final String SERVER_IP = "127.0.0.1";
        private Socket socket = null ;
        private BufferedReader br = null;
        private PrintStream ps = null ;
        //键盘输入
        private BufferedReader keyIn = null ;
         
        public void init() {
            try {
                socket = new Socket(SERVER_IP,SERVER_PORT);
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                ps = new PrintStream(socket.getOutputStream());
                keyIn = new BufferedReader(new InputStreamReader(System.in));
                 
                String tip = "";
                //要求输入用户名登录
                while(true) {
                    String userName =JOptionPane.showInputDialog(tip + "输入用户名");
                    //将用户名发送到服务器
                    ps.println(ChatProtocol.USER_ROUND + userName + ChatProtocol.USER_ROUND);
                    //获取服务器返回的消息
                    String result = br.readLine();
                    if(result.equals(ChatProtocol.NAME_REP)) {
                        System.out.println("用户名重复,请重试");
                        continue;
                    }
                    if(result.equals(ChatProtocol.LOGIN_SUCCESS)) {
                        break ;
                    }
                }
            } catch (IOException e) {
                System.out.println("网络异常,请重新登录");
                closeRs();
                System.exit(1);
            }
            //启动对应socket的读取服务器消息的线程
            new Thread(new ClientThread(br)).start();
             
            String line = null;
            try {
                while((line = keyIn.readLine()) != null) {
                    //如果发送信息中有:号,且以//开头,则认为是发送私聊信息://张三:你最近怎么样
                    if(line.indexOf(":") >0 && line.startsWith("||")) {
                        line = line.substring(2);
                        System.out.println(ChatProtocol.PRIVATE_ROUND+line.split(":")[0]
                        +ChatProtocol.SPLIT_SIGN+line.split(":")[1]+ChatProtocol.PRIVATE_ROUND);
                        ps.println(ChatProtocol.PRIVATE_ROUND+line.split(":")[0]
                        +ChatProtocol.SPLIT_SIGN+line.split(":")[1]+ChatProtocol.PRIVATE_ROUND);
                    }else {
                        ps.println(ChatProtocol.MSG_ROUND + line + ChatProtocol.MSG_ROUND);
                    }
                     
                }
            } catch (IOException e) {
                System.out.println("网络异常,请重新登录");
                closeRs();
                System.exit(1);
            }
             
        }
         
        private void closeRs() {
            try {
                if(br != null) {
                    br.close();
                }
                if(ps != null) {
                    ps.close();
                }
                if(socket != null) {
                    socket.close();
                }
                if(keyIn != null) {
                    keyIn.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
         
        public static void main(String[] args) {
            ChatClient chatClient = new ChatClient();
            chatClient.init();
        }
    }
     
    package charClient;
     
    import java.io.BufferedReader;
    import java.io.IOException;
     
    public class ClientThread implements Runnable{
        private BufferedReader br = null;
        public ClientThread(BufferedReader br)  {
            this.br = br ;  
        }
        @Override
        public void run() {
            String line = null;
            try {
                while((line = br.readLine()) != null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    if(br != null) {
                        br.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     
     
    package charClient;
     
    /**
     * 协议字符
     * @author rdb
     *
     */
    public interface ChatProtocol {
     
        //协议字符串长度
        int PROTOCOL_LEN = 2;
        //公发前后端字符
        String MSG_ROUND = "☾△";
        //用户前后字符
        String USER_ROUND = "△▽";
        //登录成功表示
        String LOGIN_SUCCESS = "1";
        //用户重复标识
        String NAME_REP = "-1" ;
        //私发用户标识
        String PRIVATE_ROUND = "◆◆" ;
         
        String SPLIT_SIGN = "◆" ;
    }
  • 相关阅读:
    JS数组存储(两个数组相等,一个改变,另一个跟着改变)
    图片404加载失败后如何处理
    为什么重写equals方法,还必须要重写hashcode方法
    Java中HashMap和TreeMap的区别深入理解
    java中String数组和List的互相转化
    log4j重复打印的解决方法
    mysql 允许在唯一索引的字段中出现多个null值
    elasticsearch 常见查询及聚合的JAVA API
    A记录(主机名解析)、CNAME(别名解析)和URL转发(域名转发)
    域名解析中的cname解析和显性URL跳转和隐性URL跳转三者有什么区别
  • 原文地址:https://www.cnblogs.com/jnba/p/10636750.html
Copyright © 2020-2023  润新知