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 }
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 }
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 }
作者:刘认真
-------------------------------------------
新人只求记录学习生活!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!