小小聊天室,慢慢的回忆啊!(TCP 通信实现)

文章目录

  • 前言
  • 一、TCP 协议
  • 二、 TCP 通信 的实现
    • “ 请求- 响应 ” 模式:
    • 通过 Socket 的编程顺序:
    • 基于TCP协议的Socket编程,实现单向通讯
    • 通过数据流改进代码
    • 序列化涉及的类和接口(io流知识回顾一下)
    • 基于TCP协议的Socket编程,实现双向通讯(通过多线程模拟多个用户请求登录)
  • つづく


前言

之前学网络编程这块,就是感觉云里雾里,就想对老师说一句:你说啥呢?这两天学框架,遇到个bug怎么也是解决不了了;这两天还赶上元旦,老师们都放假;进行不下去就回来复习复习吧!看看最头疼的网络编程;重新在看一遍,还真有种知新而温故的感觉!废话不多说,我们看代码;


一、TCP 协议

TCP(Transfer Control Protocol)是面向连接的,所谓面向连接,就是当计算机双方通信时必需经过先建立连接,然后传送数据,最后拆除连接三个过程。

二、 TCP 通信 的实现

在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。

“ 请求- 响应 ” 模式:

Socket 类:发送 TCP 消息。
ServerSocket 类:创建服务器。

通过 Socket 的编程顺序:

  1. 创建服务器 ServerSocket,在创建时,定义 ServerSocket 的监听端口(在这个端口接收客户端发来的消息)。
  2. ServerSocket 调用 accept()方法,(accept():侦听要连接到此套接字并接受它。)使之处于阻塞状态。
  3. 创建客户端 Socket,并设置服务器的 IP 及端口。
  4. 客户端发出连接请求,建立连接。
  5. 分别取得服务器和客户端 Socket 的 InputStream 和 OutputStream。
  6. 利用 Socket 和 ServerSocket 进行数据传输。
  7. 关闭流及 Socket。

基于TCP协议的Socket编程,实现单向通讯

代码如下:

服务端


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务器类
 * */
public class ServerTest {
     
	public static void main(String[] args) throws IOException {
     
//		创建serverSocket对象,并指定端口;
		ServerSocket ss = new ServerSocket(9999);
//		利用accept()方法,进行监听 返回一个Socket对象
		Socket client = ss.accept();
//		接收数据(获取输入流)由于接收到是字节(也就是数字)需要转换成字符read:从输入流读取数据的下一个字节。 
		InputStream is = client.getInputStream();
		System.out.println((char)is.read());
//		发出响应(获取输出流)write():将 b.length字节从指定的字节数组写入此输出流。
		OutputStream os = client.getOutputStream();
		os.write("收到".getBytes());
//		关闭流
		if(os!=null) {
     
			os.close();
		}
		if(is!=null) {
     
			is.close();
		}
		if(client!=null) {
     
			client.close();
		}
	}

}

客户端


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 客户类
 * */
public class ClientTest {
     
	public static void main(String[] args) throws IOException {
     
//		创建Socket对象
		Socket socket = new Socket("192.168.10.102",9999);
//		输出数据
		OutputStream os = socket.getOutputStream();
//		write()中的参数只能是,字符类型的
		os.write('a');
//		接收响应
		InputStream is = socket.getInputStream();
		byte[] b = new byte[1024];//中转站,存储读到的数据
		int len = 0;//读到的个数,当个数为-1时,证明没有数据;
		if((len=is.read(b))!=-1) {
     
//			利用String的构造方法,将读到的数据输出到控制台
			System.out.println(new String(b,0,len));
		}
//		关闭流
		if(is!=null) {
     
			is.close();
		}
		if(os!=null) {
     
			os.close();
		}
		if(socket!=null) {
     
			socket.close();
		}
	}

}

输出效果

在这里插入图片描述

通过数据流改进代码

代码如下:

服务端

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务器类
 * */
public class ServerTest2 {
     
	public static void main(String[] args) throws IOException {
     
//		创建serverSocket对象,并指定端口;
		ServerSocket ss = new ServerSocket(9999);
//		利用accept()方法,进行监听 返回一个Socket对象
		Socket client = ss.accept();
//		接收数据    获取数据输入流  数据流的参数需要一个字节流
		DataInputStream dis = new DataInputStream(client.getInputStream());
		System.out.println(dis.readUTF());
//		发出响应(获取数据输出流)
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		dos.writeUTF("收到");
//		关闭流
		if(dos!=null) {
     
			dos.close();
		}
		if(dis!=null) {
     
			dis.close();
		}
		if(client!=null) {
     
			client.close();
		}
		if(ss!=null) {
     
			ss.close();
		}
	}

}

客户端

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 客户类
 * */
public class ClientTest2 {
     
	public static void main(String[] args) throws IOException {
     
//		创建Socket对象
		Socket socket = new Socket("192.168.10.102",9999);
//		输出数据 使用数据流进行数据的传输  参数需要一个字节流
		DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//		writeUTF()是写入的方法;
		dos.writeUTF("hello world");
//		接收响应
		DataInputStream dis = new DataInputStream(socket.getInputStream());
//		readUTF()是读取数据的方法
		System.out.println(dis.readUTF());
//		关闭流
		if(dis!=null) {
     
			dis.close();
		}
		if(dos!=null) {
     
			dos.close();
		}
		if(socket!=null) {
     
			socket.close();
		}
	}

}

运行效果

小小聊天室,慢慢的回忆啊!(TCP 通信实现)_第1张图片

序列化涉及的类和接口(io流知识回顾一下)

  1. ObjectOutputStream 代表对象输出流,它的 writeObject(Object obj)方法可对参数指定的obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
  2. ObjectInputStream 代表对象输入流,它的 readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  3. 只有实现了 Serializable 接口的类的对象才能被序列化。Serializable 接口是一个空接口,只起到标记作用。
  4. 不要忘了给实现类添加序列化编号。(为了防止读和写的序列化 ID 不一致,一般指定一个固定的序列化 ID。)

基于TCP协议的Socket编程,实现双向通讯(通过多线程模拟多个用户请求登录)

创建两个项目(服务器项目、客户端项目)


服务器项目


User类

import java.io.Serializable;

public class User implements Serializable{
     
//	必须要继承Serializable接口 , 否则不能被序列化
	//为了防止读写的序列化id不同,一定要指定一个固定的序列id  最好不要选择默认的id
	private static final long serialVersionUID = 8251855228567334134L;
	private String name;
	private String password;
	public String getName() {
     
		return name;
	}
	public void setName(String name) {
     
		this.name = name;
	}
	public String getPassword() {
     
		return password;
	}
	public void setPassword(String password) {
     
		this.password = password;
	}
	public static long getSerialversionuid() {
     
		return serialVersionUID;
	}
	public User(String name, String password) {
     
		super();
		this.name = name;
		this.password = password;
	}
	public User() {
     
		super();
	}

}

服务器类

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import com.bjsxt.thread.ServerThread;

/**
 * 服务器类
 * */
public class Server {
     
	public static void main(String[] args) throws IOException, ClassNotFoundException{
     
//		常见ServerSocket对象; 并指定端口号
		ServerSocket ss = new ServerSocket(10000);
//		监听客户端  accept()方法进行监听  并返回一个Socket对象
//		需要使用循环来监听,不能只监听一次
		while(true) {
     
			Socket client = ss.accept();
//			创建线程对象
			ServerThread st = new ServerThread(client);
//			开启线程
			new Thread(st).start();
		}
		

	}
}

添加线程类ServerThread

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import com.bjsxt.pojo.User;

/**
 * 将获取每一个客户端都要执行的代码,封装到该类中,并实现Runnable接口,完成多线程
 * */

public class ServerThread implements Runnable{
     
	
	private Socket client;
	
	public ServerThread(Socket client) {
     
		super();
		this.client = client;
	}
//   线程中的异常必须处理掉,不能抛出;
	@Override
	public void run() {
     
//		想要看到谁在请求登录必须要在线程类的方法中添加 ,如果在服务其中添加会出现main请求登录的字样
		System.out.println(Thread.currentThread().getName()+"正在请求登录");
		//		获取对象输入流 需要一个字节流
		ObjectInputStream ois = null;
		//		获取数据输出流    响应是字符串  所以用数据输出流 参数需要一个字节流
		DataOutputStream dos = null;
		try {
     
			ois = new ObjectInputStream(client.getInputStream());
			//		获取信息 返回一个object对象,将其转换为user对象
					User user = (User)ois.readObject();
			//		通过getInetAddress()方法获取InetAddress对象,并获取客户端信息;
					System.out.println(client.getInetAddress().getHostAddress()+"请求登录"+"\t用户名:"+user.getName()+"\t密码:"+user.getPassword());
			dos = new DataOutputStream(client.getOutputStream());
			//		产生响应  判断用户名与密码是否正确
					String str = null;
					if("miwa".equals(user.getName())&&"miwa".equals(user.getPassword())) {
     
						str = "登录成功";
					}else {
     
						str = "用户名或密码错误";
					} 
					dos.writeUTF(str);
		} catch (ClassNotFoundException e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
//		关闭流    倒着关
 		if(dos!=null) {
     
			try {
     
				dos.close();
			} catch (IOException e) {
     
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(ois!=null) {
     
			try {
     
				ois.close();
			} catch (IOException e) {
     
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(client!=null) {
     
			try {
     
				client.close();
			} catch (IOException e) {
     
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	

}

客户端项目


User类

import java.io.Serializable;

public class User implements Serializable{
     
//	必须要继承Serializable接口 , 否则不能被序列化
	//为了防止读写的序列化id不同,一定要指定一个固定的序列id  最好不要选择默认的id
	private static final long serialVersionUID = 8251855228567334134L;
	private String name;
	private String password;
	public String getName() {
     
		return name;
	}
	public void setName(String name) {
     
		this.name = name;
	}
	public String getPassword() {
     
		return password;
	}
	public void setPassword(String password) {
     
		this.password = password;
	}
	public static long getSerialversionuid() {
     
		return serialVersionUID;
	}
	public User(String name, String password) {
     
		super();
		this.name = name;
		this.password = password;
	}
	public User() {
     
		super();
	}
	
	
}

客户端类

import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;

import com.bjsxt.pojo.User;

/**
 * 客户端
 * */
public class Client {
     
	public static void main(String[] args) throws IOException {
     
//		获取Socket对象 并给定服务器地址,以及端口号
		Socket socket = new Socket("192.168.10.102",10000);
//		获取 对象输出流  为了实现登录 ,需要将登录信息封装成对象 参数需要一个字节流对象
		ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//		创建用户对象
		User user = getUser();
//		发送信息  
		oos.writeObject(user);
//		接收响应  响应是字符串类型,所以用数据流接收 参数需要一个字节流
		DataInputStream dis = new DataInputStream(socket.getInputStream());
//		将响应输出到控制台 readUTF()方法直接返回一个String类型,可以直接输出;
		System.out.println(dis.readUTF());
//		关闭流
		if(dis!=null) {
     
			dis.close();
		}
		if(oos!=null) {
     
			oos.close();
		}
		if(socket!=null) {
     
			socket.close();
		}
	}
	
	public static User getUser() {
     
//		获取键盘输入对象 
		Scanner s = new Scanner(System.in);
		System.out.println("请输入用户名:");
		String username = s.next();
		System.out.println("请输入密码:");
		String password = s.next();
		
		return new User(username,password);
		
	}

}

运行效果

小小聊天室,慢慢的回忆啊!(TCP 通信实现)_第2张图片


つづく

回首看看之前感觉很难的题目;其实也不过如此; 加油,每一个拼搏的你; 谢谢观看; 最后会以一个小小的聊天室项目结束;敬请期待……

你可能感兴趣的:(JAVA!我跟你拼了……,socket,java,ServerSocket,网络编程,后端)