• java 网络编程Socket


    TCP:

    通过TCP协议传输,得到的是一个顺序的无差错的数据流。

    发送方和接收方的成对的两个socket之间必须建立连接,

    以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,

    另一个socket可以要求进行连接,一旦这两个socket连接起来,

    它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。

    1.建立服务器端连接(MyServer):

    ServerSocket serverSocket = new ServerSocket(6666);//设置端口号6666

    2.等待传输:

    Socket socket = serverSocket.accept();

    3.建立客户端(MyCilent):

    Socket socket=new Socket("127.0.0.1",6666);//127.0.0.1本机地址

    4.编写输入输出流:

    ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                System.out.println("服务端等待消息");
                while (true) {
                    Msg msg = (Msg) ois.readObject();
                    System.out.println("服务端收到消息:" + msg);
    
                    for (Socket s : MyService.clients) {
                        ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
                        oos.writeObject(msg);
                        oos.flush();// 刷新就会发送
                    }

    所有源码:

    客户端  MyCilent:

     1 package com.etc;
     2 
     3 import java.io.IOException;
     4 import java.io.ObjectInputStream;
     5 import java.io.ObjectOutputStream;
     6 import java.net.Socket;
     7 import java.net.UnknownHostException;
     8 import java.util.Scanner;
     9 
    10 public class MyCilent {
    11     public static void main(String[] args) throws UnknownHostException, IOException {
    12         //设置服务器IP和端口号
    13         Socket socket=new Socket("127.0.0.1",6666);
    14         System.out.println("客户端已连接");
    15         
    16         //开启客户端发送消息线程
    17         new Thread(new ClientThread(socket)).start();
    18         
    19         //输出流
    20         ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());
    21         
    22         System.out.println("客户端准备发送消息");
    23         Scanner sc=new Scanner(System.in);
    24         
    25         System.out.println("请输入用户名:");
    26         String name= sc.next();
    27         //无限循环输出
    28         while(true) {
    29             System.out.println("请输入你传的消息");
    30             String i=sc.nextLine();
    31             //传入msg对象
    32             Msg msg=new Msg(name,i);
    33             //写入数据
    34             oos.writeObject(i);
    35             //刷新
    36             oos.flush();
    37             System.out.println(name+":"+i);
    38         }
    39     }
    40 }
    41 class ClientThread implements Runnable {
    42 
    43     private Socket socket;
    44 
    45     public ClientThread(Socket socket) {
    46         this.socket = socket;
    47     }
    48 
    49     @Override
    50     public void run() {
    51         while (true) {
    52             try {
    53                 //输入流
    54                 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
    55                 //将输入的信息传入msg对象
    56                 Msg msg = (Msg) ois.readObject();
    57                 System.out.println("服务端响应:" + msg);
    58             } catch (Exception e) {
    59                 e.printStackTrace();
    60             }
    61         }
    62     }
    63 
    64 }

    服务端 MyService:

     1 package com.etc;
     2 
     3 import java.io.IOException;
     4 import java.io.ObjectInputStream;
     5 import java.io.ObjectOutputStream;
     6 import java.net.InetAddress;
     7 import java.net.ServerSocket;
     8 import java.net.Socket;
     9 import java.net.UnknownHostException;
    10 import java.util.ArrayList;
    11 import java.util.List;
    12 
    13 public class MyService {
    14     public static List<Socket> clients=new ArrayList<>();
    15     
    16     public static void main(String[] args) throws UnknownHostException, IOException {
    17         //建立服务器端口号
    18         ServerSocket serverSocket = new ServerSocket(6666);
    19         System.out.println("服务器启动,监听6666端口");
    20         while (true) {
    21             //等待消息传入
    22             Socket socket = serverSocket.accept();
    23             //将客户端添加到服务器
    24             clients.add(socket);
    25             // 获取对方ip
    26             InetAddress inetAddress = socket.getInetAddress();
    27             System.out.println("服务器等待" + inetAddress + ":"+socket);
    28             //开启服务器输入输出线程
    29             new Thread(new ServerThread(socket)).start();
    30         }
    31     
    32     }
    33 
    34 }
    35 
    36 class ServerThread implements Runnable {
    37 
    38     private Socket socket;
    39 
    40     public ServerThread(Socket socket) {
    41         this.socket = socket;
    42     }
    43 
    44     @Override
    45     public void run() {
    46         try {
    47             //输入流
    48             ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
    49             System.out.println("服务端等待消息");
    50             while (true) {
    51                 //将输入信息添加到msg对象中
    52                 Msg msg = (Msg) ois.readObject();
    53                 System.out.println("服务端收到消息:" + msg);
    54                 
    55                 
    56                 for (Socket s : MyService.clients) {
    57                     //输出客户端发送的消息
    58                     ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
    59                     //写入消息
    60                     oos.writeObject(msg);
    61                     oos.flush();// 刷新就会发送
    62                 }
    63 
    64             }
    65 
    66         } catch (IOException | ClassNotFoundException e) {
    67             e.printStackTrace();
    68         }
    69     }
    70 
    71 }

    消息对象:

     1 package com.etc;
     2 
     3 import java.io.Serializable;
     4 
     5 public class Msg implements Serializable {
     6     
     7     private String name;
     8     private String body;
     9 
    10     public String getName() {
    11         return name;
    12     }
    13 
    14     public void setName(String name) {
    15         this.name = name;
    16     }
    17 
    18     public String getBody() {
    19         return body;
    20     }
    21 
    22     public void setBody(String body) {
    23         this.body = body;
    24     }
    25 
    26     public Msg(String name, String body) {
    27         super();
    28         this.name = name;
    29         this.body = body;
    30     }
    31 
    32     public Msg() {
    33         super();
    34     }
    35 
    36     @Override
    37     public String toString() {
    38         return "Msg [name=" + name + ", body=" + body + "]";
    39     }
    40 
    41 }
    View Code

          1.客户端:     

      ① 创建Socket对象,指明需要连接的服务器的地址和端口号

      ② 连接建立后,通过输出流想服务器端发送请求信息

      ③ 通过输入流获取服务器响应的信息

      ④ 关闭响应资源 

      2.应用多线程实现服务器与多客户端之间的通信

            ① 服务器端创建ServerSocket,循环调用accept()等待客户端连接

            ② 客户端创建一个socket并请求和服务器端连接

            ③ 服务器端接受苦读段请求,创建socket与该客户建立专线连接

            ④ 建立连接的两个socket在一个单独的线程上对话

            ⑤ 服务器端继续等待新的连接   

     

    小小测试:如有需求下面附一个更好的完整版

      1 package com.etc;
      2 
      3 import java.io.BufferedReader;
      4 import java.io.IOException;
      5 import java.io.InputStream;
      6 import java.io.InputStreamReader;
      7 import java.io.OutputStream;
      8 import java.io.OutputStreamWriter;
      9 import java.io.PrintWriter;
     10 import java.net.Socket;
     11 import java.util.Scanner;
     12 
     13 /**
     14  * 控制台聊天程序 客户端应用程序
     15  * 
     16  *
     17  */
     18 public class ChatClient {
     19 
     20     // 客户端用于与服务端连接的Socket
     21     private Socket clientSocket;
     22 
     23     /**
     24      * 构造方法,客户端初始化
     25      */
     26     public ChatClient() {
     27         try {
     28             /*
     29              * socket(String host, int port) 地址: IP地址,用来定位网络上的计算机 端口: 用来找到远端计算机上用来连接的服务端应用程序
     30              */
     31             clientSocket = new Socket("localhost", 12580);
     32         } catch (Exception e) {
     33             e.printStackTrace();
     34         }
     35     }
     36 
     37     /**
     38      * 客户端昵称验证方法
     39      * 
     40      * @param 为Scanner
     41      */
     42     private void inputNickName(Scanner scan) throws Exception {
     43         String nickName = null;
     44         // 创建输出流
     45         PrintWriter pw = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);
     46         // 创建输入流
     47         BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
     48         while (true) {
     49             System.out.println("请创建您的昵称:");
     50             nickName = scan.nextLine();
     51             if (nickName.trim().equals("")) {
     52                 System.out.println("昵称不得为空");
     53             } else {
     54                 pw.println(nickName);
     55                 String pass = br.readLine();
     56                 if (pass != null && !pass.equals("OK")) {
     57                     System.out.println("昵称已经被占用,请更换!");
     58                 } else {
     59                     System.out.println("你好!" + nickName + "可以开始聊天了");
     60                     break;
     61                 }
     62             }
     63         }
     64     }
     65 
     66     /*
     67      * 客户端启动的方法
     68      */
     69     public void start() {
     70         try {
     71             /*
     72              * 创建Scanner,读取用户输入内容 目的是设置客户端的昵称
     73              */
     74             Scanner scanner = new Scanner(System.in);
     75             inputNickName(scanner);
     76             /*
     77              * 将用于接收服务器端发送过来的信息的线程启动
     78              */
     79             Runnable run = new GetServerMsgHandler();
     80             Thread t = new Thread(run);
     81             t.start();
     82             /*
     83              * 建立输出流,给服务端发信息
     84              */
     85             OutputStream os = clientSocket.getOutputStream();
     86             OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
     87             PrintWriter pw = new PrintWriter(osw, true);
     88             while (true) {
     89                 pw.println(scanner.nextLine());
     90             }
     91         } catch (Exception e) {
     92             e.printStackTrace();
     93         } finally {
     94             if (clientSocket != null) {
     95                 try {
     96                     clientSocket.close();
     97                 } catch (IOException e) {
     98                     e.printStackTrace();
     99                 }
    100             }
    101         }
    102     }
    103 
    104     /**
    105      * 该线程体用来循环读取服务端发送过来的信息 并输出到客户端的控制台
    106      * 
    107      * @param args
    108      */
    109     class GetServerMsgHandler implements Runnable {
    110         @Override
    111         public void run() {
    112             try {
    113                 InputStream is = clientSocket.getInputStream();
    114                 InputStreamReader isr = new InputStreamReader(is, "UTF-8");
    115                 BufferedReader br = new BufferedReader(isr);
    116                 String msgString = null;
    117                 while ((msgString = br.readLine()) != null) {
    118                     System.out.println("服务端提示:" + msgString);
    119                 }
    120             } catch (Exception e) {
    121                 e.printStackTrace();
    122             }
    123         }
    124     }
    125 
    126     public static void main(String[] args) {
    127         ChatClient client = new ChatClient();
    128         client.start();
    129     }
    130 }
    View Code
      1 package com.etc;
      2 
      3 import java.io.BufferedReader;
      4 import java.io.IOException;
      5 import java.io.InputStream;
      6 import java.io.InputStreamReader;
      7 import java.io.OutputStream;
      8 import java.io.OutputStreamWriter;
      9 import java.io.PrintWriter;
     10 import java.net.ServerSocket;
     11 import java.net.Socket;
     12 import java.util.HashMap;
     13 import java.util.Map;
     14 import java.util.concurrent.ExecutorService;
     15 import java.util.concurrent.Executors;
     16 
     17 /**
     18  * 控制台聊天程序 服务端应用程序
     19  * 
     20  *
     21  */
     22 public class chatServer {
     23     /**
     24      * ServerSocket 是运行在服务端的Socket 用来监听端口,等待客户端的连接, 一旦连接成功就会返回与该客户端通信的Socket
     25      */
     26     private ServerSocket serverSocket;
     27     /**
     28      * 创建线程池来管理客户端的连接线程 避免系统资源过度浪费
     29      */
     30     private ExecutorService threadPool;
     31     /**
     32      * 该属性用来存放客户端之间私聊的信息
     33      */
     34     private Map<String, PrintWriter> allOut;
     35 
     36     /**
     37      * 构造方法,服务端初始化
     38      */
     39     public chatServer() {
     40         try {
     41             /*
     42              * 创建ServerSocket,并申请服务端口 将来客户端就是通过该端口连接服务端程序的
     43              */
     44             serverSocket = new ServerSocket(12580);
     45             /*
     46              * 初始化Map集合,存放客户端信息
     47              */
     48             allOut = new HashMap<String, PrintWriter>();
     49             /*
     50              * 初始化线程池,设置线程的数量
     51              */
     52             threadPool = Executors.newFixedThreadPool(10);
     53             /*
     54              * 初始化用来存放客户端输出流的集合, 每当一个客户端连接,就会将该客户端的输出流存入该集合; 每当一个客户端断开连接,就会将集合中该客户端的输出流删除;
     55              * 每当转发一条信息,就要遍历集合中的所有输出流(元素) 因此转发的频率高于客户端登入登出的频率,
     56              * 还是应该使用ArrayList来存储元素,仅限群聊,私聊不行 allOut = new ArrayList<PrintWriter>();
     57              */
     58         } catch (Exception e) {
     59             e.printStackTrace();
     60         }
     61     }
     62 
     63     /*
     64      * 将客户端的信息以Map形式存入集合中
     65      */
     66     private void addOut(String key, PrintWriter value) {
     67         synchronized (this) {
     68             allOut.put(key, value);
     69         }
     70     }
     71 
     72     /*
     73      * 将给定的输出流从共享集合中删除 参数为客户端nickName,作为Map的key键
     74      */
     75     private synchronized void removeOut(String key) {
     76         allOut.remove(key);
     77         System.out.println("当前在线人数为:" + allOut.size());
     78     }
     79 
     80     /*
     81      * 将给定的消息转发给所有客户端
     82      */
     83     private synchronized void sendMsgToAll(String message) {
     84         for (PrintWriter out : allOut.values()) {
     85             out.println(message);
     86             System.out.println("当前在线人数为:" + allOut.size());
     87         }
     88     }
     89 
     90     /*
     91      * 将给定的消息转发给私聊的客户端
     92      */
     93     private synchronized void sendMsgToPrivate(String nickname, String message) {
     94         PrintWriter pw = allOut.get(nickname); // 将对应客户端的聊天信息取出作为私聊内容发送出去
     95         if (pw != null) {
     96             pw.println(message);
     97             System.out.println("当前在线私聊人数为:" + allOut.size());
     98         }
     99     }
    100 
    101     /**
    102      * 服务端启动的方法
    103      */
    104     public void start() {
    105         try {
    106             while (true) {
    107                 /*
    108                  * 监听10086端口
    109                  */
    110                 System.out.println("等待客户端连接... ... ");
    111                 /*
    112                  * Socket accept() 这是一个阻塞方法,会一直在10086端口进行监听
    113                  * 直到一个客户端连接上,此时该方法会将与这个客户端进行通信的Socket返回
    114                  */
    115                 Socket socket = serverSocket.accept();
    116                 System.out.println("客户端连接成功! ");
    117                 /*
    118                  * 启动一个线程,由线程来处理客户端的请求,这样可以再次监听 下一个客户端的连接了
    119                  */
    120                 Runnable run = new GetClientMsgHandler(socket);
    121                 threadPool.execute(run); // 通过线程池来分配线程
    122             }
    123         } catch (Exception e) {
    124             e.printStackTrace();
    125         }
    126     }
    127 
    128     /**
    129      * 该线程体用来处理给定的某一个客户端的消息,循环接收客户端发送 的每一个字符串,并输出到控制台
    130      * 
    131      * @author Jacob
    132      *
    133      */
    134     class GetClientMsgHandler implements Runnable {
    135         /*
    136          * 该属性是当前线程处理的具体的客户端的Socket
    137          * 
    138          * @see java.lang.Runnable#run()
    139          */
    140         private Socket socket;
    141         /*
    142          * 获取客户端的地址信息 private String hostIP;
    143          */
    144         /*
    145          * 获取客户端的昵称
    146          */
    147         private String nickName;
    148 
    149         /*
    150          * 创建构造方法
    151          */
    152         public GetClientMsgHandler(Socket socket) {
    153             this.socket = socket;
    154             /*
    155              * 获取远端客户的Ip地址信息 保存客户端的IP地址字符串 InetAddress address = socket.getInetAddress();
    156              * hostIP = address.getHostAddress();
    157              */
    158         }
    159 
    160         /*
    161          * 创建内部类来获取昵称
    162          */
    163         private String getNickName() throws Exception {
    164             try {
    165                 // 服务端的输入流读取客户端发送来的昵称输出流
    166                 InputStream iin = socket.getInputStream();
    167                 InputStreamReader isr = new InputStreamReader(iin, "UTF-8");
    168                 BufferedReader bReader = new BufferedReader(isr);
    169                 // 服务端将昵称验证结果通过自身的输出流发送给客户端
    170                 OutputStream out = socket.getOutputStream();
    171                 OutputStreamWriter iosw = new OutputStreamWriter(out, "UTF-8");
    172                 PrintWriter ipw = new PrintWriter(iosw, true);
    173                 // 读取客户端发来的昵称
    174                 String nameString = bReader.readLine();
    175                 while (true) {
    176                     if (nameString.trim().length() == 0) {
    177                         ipw.println("FAIL");
    178                     }
    179                     if (allOut.containsKey(nameString)) {
    180                         ipw.println("FAIL");
    181                     } else {
    182                         ipw.println("OK");
    183                         return nameString;
    184                     }
    185                     nameString = bReader.readLine();
    186                 }
    187             } catch (Exception e) {
    188                 throw e;
    189             }
    190         }
    191 
    192         @Override
    193         public void run() {
    194             PrintWriter pw = null;
    195             try {
    196                 /*
    197                  * 通过客户端的Socket获取客户端的输出流 用来将消息发送给客户端
    198                  */
    199                 OutputStream os = socket.getOutputStream();
    200                 OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
    201                 pw = new PrintWriter(osw, true);
    202                 /*
    203                  * 将客户昵称和其所说的话作为元素存入共享集合HashMap中
    204                  */
    205                 nickName = getNickName();
    206                 addOut(nickName, pw);
    207                 Thread.sleep(100);
    208                 /*
    209                  * 服务端通知所有客户端,某用户登录
    210                  */
    211                 sendMsgToAll("[系统通知]:欢迎**" + nickName + "**登陆聊天室!");
    212                 /*
    213                  * 通过客户端的Socket获取输入流 读取客户端发送来的信息
    214                  */
    215                 InputStream is = socket.getInputStream();
    216                 InputStreamReader isr = new InputStreamReader(is, "UTF-8");
    217                 BufferedReader br = new BufferedReader(isr);
    218                 String msgString = null;
    219                 while ((msgString = br.readLine()) != null) {
    220                     // 验证是否是私聊
    221                     if (msgString.startsWith("@")) {
    222                         /*
    223                          * 私聊格式:@昵称:内容
    224                          */
    225                         int index = msgString.indexOf(":");
    226                         if (index >= 0) {
    227                             // 获取昵称
    228                             String name = msgString.substring(1, index);
    229                             String info = msgString.substring(index + 1, msgString.length());
    230                             info = nickName + "对你说:" + info;
    231                             // 将私聊信息发送出去
    232                             sendMsgToPrivate(name, info);
    233                             // 服务端不在广播私聊的信息
    234                             continue;
    235                         }
    236                     }
    237                     /*
    238                      * 遍历所有输出流,将该客户端发送的信息转发给所有客户端
    239                      */
    240                     System.out.println(nickName + "说:" + msgString);
    241                     sendMsgToAll(nickName + "说:" + msgString);
    242                 }
    243             } catch (Exception e) {
    244                 /*
    245                  * 因为Win系统用户的客户端断开连接后,br.readLine()方法读取 不到信息就会抛出异常,而Linux系统会持续发送null;
    246                  * 因此这里就不在将捕获的异常抛出了。
    247                  */
    248             } finally {
    249                 /*
    250                  * 当执行到此处时,说明客户端已经与服务端断开连接 则将该客户端存在共享集合中的输出流删除
    251                  */
    252                 removeOut(nickName);
    253                 /*
    254                  * 通知所有客户端,某某客户已经下线
    255                  */
    256                 sendMsgToAll("[系统通知]:" + nickName + "已经下线了。");
    257                 /*
    258                  * 关闭socket,则通过Socket获取的输入输出流也一同关闭了
    259                  */
    260                 if (socket != null) {
    261                     try {
    262                         socket.close();
    263                     } catch (IOException e) {
    264                         e.printStackTrace();
    265                     }
    266                 }
    267             }
    268         }
    269     }
    270 
    271     public static void main(String[] args) {
    272         chatServer server = new chatServer();
    273         server.start();
    274     }
    275 }
    View Code
  • 相关阅读:
    mysql 行号
    java 异常链
    springsecurity密码加密
    java 四舍五入
    ArrayList的使用及原理
    java 匿名内部类
    java 克隆
    logback的配置
    信号量 Semaphore
    障碍器 CyclicBarrier
  • 原文地址:https://www.cnblogs.com/LiuOOP/p/11003024.html
Copyright © 2020-2023  润新知