欢迎来到刘认真的博客

It's not me that's wrong. It's the whole world

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

 

posted @ 2019-06-11 12:56  刘认真  阅读(290)  评论(0编辑  收藏  举报