勿以恶小而为之,勿以善小而不为--------------------------刘备
劝诸君,多行善事积福报,莫作恶
上一章简单介绍了UDP通信实现学生咨询(三),如果没有看过,请观看上一章
TCP/IP 通信是非常重要的,需要掌握。
一. TCP 通信所用到的类
一.一 ServerSocket 服务器类
是服务器端所要用到的类。
一.一.一 构造方法
一.一.一.一 方法
方法 |
作用 |
ServerSocket(int port) |
绑定到指定的端口,创建服务器套接字 |
一.一.一.二 演示构造方法
@Test
public void conTest() throws Exception{
//放置端口号, 监听9999端口
ServerSocket serverSocket=new ServerSocket(9999);
}
一.一.二 其他方法
方法 |
作用 |
Socket accept() |
阻塞式接收连接的那一个客户端对象 |
int getLocalPort() |
获取监听的那个端口 |
一.二 Socket 客户端类
一.二.一 构造方法
一.二.一.一 方法
方法 |
作用 |
Socket(String host, int port) |
传入主机名称包括ip地址 和端口号 |
Socket(InetAddress address, int port) |
传入地址对象和 端口号 |
一.二.一.二 演示构造方法
@Test
public void conTest() throws Exception{
//第一种, 传入主机名和端口号。 注意,这个端口号是服务器的那个监听端口
Socket socket=new Socket("localhost",9999);
//第二种,传入地址对象
InetAddress inetAddress=InetAddress.getByName("localhost");
Socket socket1=new Socket(inetAddress,9999);
}
一.二.二 其他方法
方法 |
作用 |
void close() |
关闭套接字 |
InputStream getInputStream() |
获取输入流 |
OutputStream getOutputStream() |
获取输出流 |
一.三 演示服务器和客户端
一.三.一 服务器端
@Test
public void serverTest() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("端口:"+serverSocket.getLocalPort());
System.out.println("连接一个客户端程序");
}
一.三.二 客户端
@Test
public void clientTest() throws Exception{
System.out.println("启动客户端");
Socket socket=new Socket("localhost",9999);
}
一.三.三 测试运行
先运行服务器端, 查看控制台输出,
线程一直在阻塞。
再运行客户端
这个时候,再去查询一下服务器端的控制台
会往下运行,接收到客户端的对象信息。
二. 演示各种 TCP 通信
TCP 通信的情况,与 UDP 通信的情况,基本是一样的。
二.一 演示单条字符串
二.一.一 客户端
@Test
public void client2Test() throws Exception{
System.out.println("启动客户端,发送单条数据");
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
//写入单条数据
outputStream.write("两个蝴蝶飞,你好啊".getBytes());
outputStream.close();
socket.close();
}
二.一.二 服务器端
@Test
public void server2Test() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序");
//得到输入流
InputStream inputStream=client.getInputStream();
byte[] bytes=new byte[1024];
//读取内容,放置到 bytes字节数组里面
int len=inputStream.read(bytes);
System.out.println("获取客户端传递过来的内容:"+new String(bytes,0,len));
}
二.一.三 运行程序
先启动服务器,再启动客户端
二.二 发送多条不同类型的数据
二.二.一 客户端
@Test
public void client3Test() throws Exception{
System.out.println("启动客户端,发送多条不同类型的数据");
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
DataOutputStream dataOutputStream=new DataOutputStream(outputStream);
//发送字符串
dataOutputStream.writeUTF("两个蝴蝶飞");
//发送int 类型
dataOutputStream.writeInt(24);
//发送字符串
dataOutputStream.writeUTF("一个快乐的程序员");
dataOutputStream.flush();
socket.close();
}
二.二.二 服务器端
@Test
public void server3Test() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序,接收多条数据");
InputStream inputStream=client.getInputStream();
DataInputStream dataInputStream=new DataInputStream(inputStream);
//接收字符串
String name=dataInputStream.readUTF();
//接收 int 类型
int age= dataInputStream.readInt();
//接收 字符串
String desc=dataInputStream.readUTF();
System.out.printf("姓名是:%s,年龄是:%d,描述是:%s",name,age,desc);
}
二.二.三 运行程序
二.三 发送对象数据
二.三.一 客户端
@Test
public void client3ObjectTest() throws Exception{
System.out.println("启动客户端,发送多条对象数据");
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new BufferedOutputStream(outputStream));
//放入对象
Person person=new Person();
person.setId(1);
person.setName("两个蝴蝶飞");
person.setSex('男');
person.setAge(24);
person.setDesc("一个快乐的程序员");
objectOutputStream.writeObject(person);
//放置日期
objectOutputStream.writeObject(new Date());
//一定不要忘记刷新
objectOutputStream.flush();
socket.close();
}
二.三.二 服务器端
@Test
public void server3ObjectTest() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序,接收对象数据");
InputStream inputStream=client.getInputStream();
ObjectInputStream objectInputStream=new ObjectInputStream(new BufferedInputStream(inputStream));
//读取数据
Object obj1=objectInputStream.readObject();
if(obj1 instanceof Person){
Person person=(Person)obj1;
System.out.println(person.toString());
}else{
System.out.println("接收格式有误");
}
Object obj2=objectInputStream.readObject();
if(obj2 instanceof Date){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
System.out.println("日期:"+sdf.format((Date)obj2));
}else{
System.out.println("接收格式有误");
}
}
二.三.三 测试运行
先启动服务器,再启动客户端
二.四 接收和响应客户端请求数据
二.四.一 客户端
public static void main(String[] args) {
try {
client4Test();
// 写多个 client5Test(), client6Test() 等
} catch (Exception e) {
e.printStackTrace();
}
}
public static void client4Test() throws Exception{
System.out.println("启动客户端");
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader((System.in)));
System.out.println("请输入用户名:");
String userName=bufferedReader.readLine();
System.out.println("请输入密码:");
String password=bufferedReader.readLine();
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
DataOutputStream dataOutputStream=new DataOutputStream(outputStream);
//拼接请求参数
String query="userName="+userName+"&password="+password;
dataOutputStream.writeUTF(query);
dataOutputStream.flush();
//获取内容
DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());
String content=dataInputStream.readUTF();
System.out.println("接收服务器端返回的内容:"+content);
dataInputStream.close();
dataOutputStream.close();
socket.close();
}
二.四.二 服务器端
@Test
public void server4Test() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序,响应数据");
InputStream inputStream=client.getInputStream();
DataInputStream dataInputStream=new DataInputStream(inputStream);
String query=dataInputStream.readUTF();
//拆分
String[] qArr=query.split("&");
String userName="";
String password="";
for(String str:qArr){
String[] tempArr=str.split("=");
if("userName".equals(tempArr[0])){
userName=tempArr[1];
}else if("password".equals(tempArr[0])){
password=tempArr[1];
}
}
String responseData="";
System.out.printf("接收的用户名为:%s,密码为%s",userName,password);
//返回数据
if("两个蝴蝶飞".equals(userName)&&"1234".equals(password)){
responseData="用户名和密码输入正确,登录成功";
}else{
responseData="用户名或者密码错误,登录失败";
}
DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());
dataOutputStream.writeUTF(responseData);
dataOutputStream.close();
dataInputStream.close();
}
二.四.三 测试运行
先运行服务器端
再运行客户端, 输入错误的密码
这时,查看服务器端
再次重新测试, 输入正确的用户名和密码
发现,服务器端可以接收客户端传递过来的数据,并且可以响应数据, 客户端也能够获取到响应的数据。
二.五 传输文件
IOUtils 仍然与第二章节的 工具类一致。
二.五.一 客户端
public static void client5Test() throws Exception{
System.out.println("启动客户端,发送文件");
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
DataOutputStream dataOutputStream=new DataOutputStream(outputStream);
String path="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"129.png";
//将文件转换成字节
byte[] bytes= IOUtils.fileToByteArray(path);
//写入图片
dataOutputStream.write(bytes);
dataOutputStream.flush();
DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());
String content=dataInputStream.readUTF();
System.out.println("接收服务器端返回的内容:"+content);
dataInputStream.close();
dataOutputStream.close();
socket.close();
}
二.五.二 服务器端
@Test
public void server5Test() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序");
InputStream inputStream=client.getInputStream();
DataInputStream dataInputStream=new DataInputStream(inputStream);
//图片大小为17k, 这儿接收时接收 20k
byte[] bytes=new byte[1024*20];
dataInputStream.read(bytes);
//将字节转换成图片
String path="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"129Tcp.png";
IOUtils.byteArrayToFile(bytes,path);
String responseData="传输文件成功";
DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());
dataOutputStream.writeUTF(responseData);
dataOutputStream.close();
dataInputStream.close();
}
二.五.三 测试运行
启动服务器端,再启动客户端, 客户端控制台打印输出
查看文件系统
图片,也可以正常的显示出来,没有破坏。
二.六 经典的 echo 程序
简单来说,就是响应数据 echo:客户端传递过来的数据
二.六.一 单次 echo
二.六.一.一 客户端
public static void client6Test() throws Exception{
System.out.println("启动客户端");
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader((System.in)));
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
DataOutputStream dataOutputStream=new DataOutputStream(outputStream);
String content=bufferedReader.readLine();
//写入数据
dataOutputStream.writeUTF(content);
DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());
//读取数据,并且打印
String respnseContent=dataInputStream.readUTF();
System.out.println(respnseContent);
dataInputStream.close();
dataOutputStream.close();
socket.close();
}
二.六.一.二 服务器
@Test
public void server6Test() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序");
InputStream inputStream=client.getInputStream();
DataInputStream dataInputStream=new DataInputStream(inputStream);
String content=dataInputStream.readUTF();
String responseData="echo:"+content;
DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());
dataOutputStream.writeUTF(responseData);
dataOutputStream.close();
dataInputStream.close();
}
二.六.一.三 测试运行
先运行服务器端,再运行客户端
二.六.二 多次echo
用while() 进行循环接收控制台传递过来的数据。
二.六.二.一 客户端
public static void client7Test() throws Exception{
System.out.println("启动客户端,多次echo 程序");
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader((System.in)));
Socket socket=new Socket("localhost",9999);
//发送数据
//客户端的输入
OutputStream outputStream= socket.getOutputStream();
DataOutputStream dataOutputStream=new DataOutputStream(outputStream);
DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());
boolean isRun=true;
while(isRun){
//循环接收数据
String content=bufferedReader.readLine();
dataOutputStream.writeUTF(content);
String respnseContent=dataInputStream.readUTF();
System.out.println(respnseContent);
if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
break;
}
}
dataInputStream.close();
dataOutputStream.close();
socket.close();
}
二.六.二.二 服务器端
@Test
public void server7Test() throws Exception{
System.out.println("服务器启动中");
ServerSocket serverSocket=new ServerSocket(9999);
boolean isRun=true;
while(isRun){
//阻塞式接收
Socket client=serverSocket.accept();
System.out.println("连接一个客户端程序");
InputStream inputStream=client.getInputStream();
DataInputStream dataInputStream=new DataInputStream(inputStream);
DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());
boolean isBye=false;
while(!isBye){
String content=dataInputStream.readUTF();
String responseData="";
if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
responseData="echo:欢迎下次再来";
isBye=true;
}else{
responseData= "echo:"+content;
}
dataOutputStream.writeUTF(responseData);
}
dataOutputStream.close();
dataInputStream.close();
}
}
二.六.二.三 测试运行
可以开多个客户端的控制台。 但 idea 默认是不能同时开启同一个程序的控制台的,需要进行设置。
老蝴蝶的 类 public 名称 是 SocketDemo
先运行服务器,再运行 客户端的 main()方法, 打开一个控制台, 再运行 客户端的 main()方法,打开新的控制台
对于第一个控制台
对于第二个控制台
两个客户端都关闭了, 此时服务器仍然是 true, 死循环,接收客户端连接的状态
连接一个客户端,就打印输出一下。
但是,发现有一个问题, 当客户端1 不关闭时,客户端2就无法响应。
客户端1
客户端2
需要用多线程去解决。
二.六.三 多线程 echo
二.六.三.一 关闭工具类
public class CloseUtils {
public static void close(Closeable...closeables){
for(Closeable closeable:closeables){
if(null!=closeable){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
二.六.三.二 客户端
public class Client {
//发送者多线程
static class Send implements Runnable{
private Socket client;
private DataOutputStream dataOutputStream;
private BufferedReader bufferedReader;
//是否继续运行的标识
private boolean isRunning;
public Send(Socket client){
this.client=client;
this.isRunning=true;
try {
this.dataOutputStream=new DataOutputStream(client.getOutputStream());
this.bufferedReader=new BufferedReader(new InputStreamReader(System.in));
} catch (IOException e) {
e.printStackTrace();
isRunning=false;
CloseUtils.close(client);
}
}
@Override
public void run() {
while(this.isRunning){
try {
//接收数据
String content=bufferedReader.readLine();
//发送数据
sendMsg(content);
if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
//修改标识
stop();
CloseUtils.close(dataOutputStream);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stop(){
this.isRunning=false;
}
//发送数据
public void sendMsg(String msg){
try {
dataOutputStream.writeUTF(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//接收者
static class Receiver implements Runnable{
private Socket client;
private DataInputStream dataInputStream;
private boolean isRunning;
public Receiver(Socket client){
this.client=client;
this.isRunning=true;
try {
this.dataInputStream=new DataInputStream(client.getInputStream());
} catch (IOException e) {
e.printStackTrace();
this.isRunning=false;
CloseUtils.close(client);
}
}
@Override
public void run() {
while(this.isRunning){
String content=readMsg();
System.out.println(content);
if("echo:欢迎下次再来".equalsIgnoreCase(content)){
stop();
}
}
try {
CloseUtils.close(this.dataInputStream,client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop(){
this.isRunning=false;
}
//接收数据
public String readMsg(){
String content="";
try {
content= dataInputStream.readUTF();
} catch (IOException e) {
e.printStackTrace();
}
return content;
}
}
public static void main(String[] args) {
try {
Socket socket=new Socket("localhost",9999);
System.out.println("***************连接客户端**************");
//客户端多线程运行发送和接收
new Thread(new Send(socket)).start();
new Thread(new Receiver(socket)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
二.六.三.三 服务器端
public class Server {
//客户连接类
static class Channel implements Runnable{
private Socket client;
private DataInputStream dataInputStream;
private DataOutputStream dataOutputStream;
private volatile boolean isRunning;
public Channel(Socket client){
this.client=client;
//标识位
this.isRunning=true;
try {
this.dataInputStream=new DataInputStream(client.getInputStream());
this.dataOutputStream=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
CloseUtils.close(dataInputStream,dataOutputStream);
}
}
@Override
public void run() {
while(this.isRunning){
String content=readMsg();
String responseData="";
if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
responseData="echo:欢迎下次再来";
this.isRunning=false;
}else{
responseData= "echo:"+content;
}
sendMsg(responseData);
}
}
//服务器往客户端发送数据
public void sendMsg(String msg){
try {
dataOutputStream.writeUTF(msg);
} catch (IOException e) {
// e.printStackTrace();
CloseUtils.close(dataInputStream,dataOutputStream);
}
}
//服务器读取客户端数据
public String readMsg(){
String content="";
try {
content= dataInputStream.readUTF();
} catch (IOException e) {
CloseUtils.close(dataInputStream,dataOutputStream);
//e.printStackTrace();
}
return content;
}
}
public static void main(String[] args) {
try {
ServerSocket serverSocket=new ServerSocket(9999);
System.out.println("********服务器开启************");
while(true){
Socket client=serverSocket.accept();//多线程运行
System.out.println("连接一个客户端");
new Thread(new Channel(client)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
二.六.三.四 测试运行
先运行服务器端,再运行创建客户端1,再运行创建客户端2
客户端1:
现在客户端1 还没有断开链接
看一下客户端2, 发送数据
是可以进行传递数据的。
要理解 多线程 echo 的应用,聊天室时会用得到。
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!