1. UDP协议基础编程 注意:UDP只有发送端和接收端,不区分客户端和服务端. 可以计算机之间随意的发送数据. 1) 接收端 [Java] 纯文本查看 复制代码 [/align][align=left] // 创建接收端套接字,接收端口号8888的数据
DatagramSocket ds = new DatagramSocket(8888);
byte[] bytes = new byte[1024];
// 打包数据
DatagramPacket dp = new DatagramPacket(bytes,0,bytes.length);
//接收数据
ds.receive(dp); //阻塞
// 解析数据
System.out.println("data from : " + dp.getAddress()); // 数据来源的ip地址
System.out.println(new String(dp.getData(), 0, dp.getLength())); // 打印数据
// 释放资源
ds.close();[/align][align=left] 2) 发送端 [Java] 纯文本查看 复制代码 // 创建发送端套接字
DatagramSocket ds = new DatagramSocket();
//打包数据
byte[] bytes = "hello,udp~~".getBytes();
// 发送给当前设备 (127.0.0.1), 指定端口号8888
DatagramPacket dp = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getByName("127.0.0.1"), 8888);
// 发送数据
ds.send(dp);
// 释放资源
ds.close(); 2. TCP协议基础编程注意:TCP协议与UDP不同的是:UDP只有发送端和接收端,不区分客户端和服务端. 可以计算机之间随意的发送数据. 而TCP,严格区分客户端和服务端,首先启动服务端,必须由客户端先去连接服务器,服务器予以响应才能实现通信。 1) 客户端 [Java] 纯文本查看 复制代码 [/align][align=left] // 创建socket对象,指定端口号,与访问主机ip,向服务器发送请求
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
// 获取socket的字节输出流
OutputStream os = s.getOutputStream();
// 数据
String str = "hello, TCP, my friend!";
// 写出数据
os.write(str.getBytes());
// 释放资源
s.close(); 2) 服务端 // 创建服务端ServerSocket对象,指定端口号,接收该端口发送的数据 ServerSocket ss = new ServerSocket(8888); // 接收数据,获得socket对象,客户端发送请求前处于阻塞状态 Socket accept = ss.accept(); // 获得socket的输入流对象 InputStream is = accept.getInputStream(); byte[] bytes = new byte[1024]; //接收数据 int len; //数据长度 len = is.read(bytes); // 读取数据(写入到程序) // 打印数据 System.out.println("data from: " + accept.getInetAddress().getHostAddress()); System.out.println(new String(bytes, 0, len)); // 释放资源 ss.close(); 5. TCP模拟用户登录(面向对象)1) 数据封装类 public class UserData implements Serializable { private static final long serialVersionUID = 1L; private String name;//姓名 private String passworld;//密码 public UserData(String name, String passworld) { ...} public UserData() { } @Override public int hashCode() {...} @Override public boolean equals(Object obj) {....} } 注意:方法里的内容省略了,注意的是使用了对象输入输出流,所以类必须实现Serializable接口,可序列化.equals(Object obj)必须重写,因为集合中的contains()方法,底层是equal()方法实现的(我们认为用户名和密码匹配上,就是可以登陆的). 2) 用户数据存储 public class UserDB { private static ArrayList<UserData> users = new ArrayList<>(); static { //静态代码块 users.add(new UserData("jean", "123456")); users.add(new UserData("admin", "password")); users.add(new UserData("anni", "qwerqwer")); users.add(new UserData("jack", "034235")); } public static ArrayList<UserData> getUsers() { return users; } } 注意:静态代码块是随着系统加载而加载的,虚拟机运行,只执行一次,并且优先于构造代码块和空参构造执行.随着虚拟机的终止而消失. 3) 模拟服务器 // 创建服务端套接字,指定端口号:8888 ServerSocket ss = new ServerSocket(8888); // 监听 Socket socket = ss.accept(); // 获取套接字输出流,并用对象输入流包装 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Object object = ois.readObject(); UserData use = (UserData) object; // 打印流包装,套接字输出流 PrintWriter pw = new PrintWriter(socket.getOutputStream(),true); // 判断信息是否存在 if(UserDB.getUsers().contains(use)) { pw.println("登录成功!!"); //反馈信息 }else { pw.println("登录失败"); //反馈信息 } // 释放资源 socket.close(); //ss.close(); 4) 模拟客户端 // 创建客户端套接字 指定访问主机ip,和端口号 Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8888); // 键盘录入信息 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入用户名: "); String name = br.readLine(); System.out.println("请输入密码: "); String password = br.readLine(); // 包装数据 UserData use = new UserData(name, password); // 获取客户端套接字输出流,并用对象输出流包装 ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream()); // 发送数据 oos.writeObject(use); // 接收反馈 BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream())); // 读取反馈 String line = br1.readLine(); //打印反馈信息 System.out.println(line); // 释放资源 s.close(); 6. 多线程实现服务器与多客户端之间通信以上从2-5的TCP服务器设计都是单个线程的,仅仅能够处理一个请求,当接收第二个请求时,服务器没有办法处理,但实际万万不能这样使用.正常的服务器,能够处理很多客户端发送的请求,实现这一点就要用到多线程方面的知识. 简单描述一下就是服务器开启处于等待请求的阻塞状态,当有客户端发送请求,创建一个新的线程来处理客户的需求,再来请求,再创建一个线程处理.我们仅仅需要对5设计修改一下. 我们依然使用设计5的客户端,UserDB和UserData类, 1) 服务器修改 public class Demo5_ThreadServer { public static int count = 0; // 请求次数 public static void main(String[] args) throws IOException, ClassNotFoundException { // 创建服务器套接字指定端口号 ServerSocket ss = new ServerSocket(8888); Socket socket = null; System.out.println("--------服务器启动,等待客户端发送请求--------"); while (true) { // 获取客户端套接字 socket = ss.accept(); // 阻塞 // 创建ThreadServer新线程,处理当前请求 new Thread(new ThreadServer(socket)).start(); } } } 当客户端请求时,我们创建一个新的线程来为客户需求服务,同时保证其他需求进来,一样可以处理.ThreadServer类为Runnable实现类,实现如下: 2) ThreadServer类的实现 class ThreadServer implements Runnable { // 创建当前线程socket套接字对象 private Socket socket = null; // 有参构造,传入socket套接字赋值给当前线程socket套接字 public ThreadServer(Socket socket) { this.socket = socket; } @Override public void run() { BufferedReader br = null; InputStreamReader isr = null; InputStream is = null; PrintWriter pw = null; OutputStream os = null; try { is = socket.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); String name = br.readLine(); // 读取姓名 String password = br.readLine(); // 读取密码 // br.close(); // 封装信息 UserData use = new UserData(name, password); // 获取scoket输出流,用打印流进行包装 os = socket.getOutputStream(); pw = new PrintWriter(os, true); System.out.println("----第" + (++Demo5_ThreadServer.count) + "请求来源ip : " + socket.getInetAddress().getHostAddress() + "----"); if (UserDB.getUsers().contains(use)) { System.out.println(name + "请求成功!"); pw.println("登录成功!"); } else { System.out.println(name + "请求失败!"); pw.println("登录失败!"); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } |