ElementPlus表单验证v-for循环问题

ElementPlus表单验证v-for循环问题

提供两个样例,主要注意<el-form-item>中的prop

样例一

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<el-form
ref="dynamicValidateForm"
:model="dynamicValidateForm"
label-width="120px"
class="demo-dynamic"
>
//重点关注el-form-item标签中的prop内容
<el-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<el-input v-model="domain.value"></el-input>
</el-form-item>
</el-form>
</template>

<script lang="ts">
export default {

data() {

return {

dynamicValidateForm: {

domains: [
{

key: 1,
value: '',
},
],
email: '',
},
}
},
}
</script>


样例二

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<el-form :model="queryParams" style="width: 100%">
<el-table :data="queryParams.items">
<el-table-column label="移动数量" width="100">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-form-item style="width: 100px;padding: 0px;margin: 0px" :prop="'items.'+scope.$index+'.moveAmount'" :rules="{ required: true, message: '不能为空', trigger: 'blur',}">
<el-input-number class="number" :controls="false" v-model="scope.row.moveAmount"></el-input-number>
</el-form-item>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</template>

<script setup>
const queryParams = reactive({

factory1: undefined,
factory2: undefined,
commentText: undefined,
items:[]
})
</script>
ElementPlus隐藏Scrollbar的横向滚动条
JavaWeb项目开发流程

JavaWeb项目开发流程

1. 需求分析

收集客户需求,明确项目功能,设计较为详细的实体关系图。

推荐:ProcessOn

2. 技术选型

确定开发框架、数据库、服务器等技术选型,这些选择应该与项目需求相匹配,同时也要考虑团队成员技术能力和经验。

推荐:RuoYi-Vue3

3. 数据库设计

根据需求分析结果,设计数据库模型,表结构,表关系。

推荐:dbdiagram.io

4. 模块划分和接口设计

将项目划分为多个小模块,并为每个模块的前后端设计api。

推荐:Apifox

4. UI设计

根据需求分析结果,设计原型模型,包括UI界面设计等。

推荐:Pixso

5. 编码

根据需求分析、技术选型、原型设计和数据库设计等结果,开始编写代码,包括前端代码和后端代码等。

推荐:前端Vue3+ElementPlus开发,后端SpringBoot+MybatisPlus开发

6. 调试和测试

在编码过程中,需要不断进行代码调试和测试,以确保程序的正确性和稳定性。

推荐:Junit

7. 部署和上线

完成测试后,将程序部署到服务器上,并进行上线运行,同时需要进行系统监控和数据备份等工作。

推荐:云服务器+Docker

8. 运维和维护

程序上线后,需要进行运维和维护工作,包括性能监控、安全维护、bug修复等。

(例子待完善)

ElementPlus-穿梭框长宽设置

ElementPlus-穿梭框长宽设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style scoped>
:deep(.el-transfer-panel) {
height: 400px; /* 穿梭框高度 */
width: 300px;
}

:deep(.el-transfer-panel__list.is-filterable) {
height: 400px; /* 穿梭框列表高度 */
}

:deep(.el-transfer-panel__body) {
height: 400px; /* 穿梭框视图层高度 */
}
:deep(.el-transfer-panel__filter) {
width: 300px; /* 修改搜索栏的宽度 */
}
</style>
Vue3 setup组合式语法优雅地使用Echarts库

Vue3 setup组合式语法优雅地使用Echarts库

1. 安装Echarts

npm或者yarn安装

npm install echarts
yarn add echarts

2.main.js全局挂载Echarts

language-javascript
1
2
3
4
5
6
7
8
9
import {
createApp } from 'vue'
import App from './App.vue'

import * as echarts from 'echarts'

const app = createApp(App)
app.config.globalProperties.$echarts = echarts // 全局挂载echarts
app.mount('#app')

3.Vue setup组合式语法使用案例

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<!-- 通过ref获取html元素 宽高必须设置 -->
<el-row>
<el-col :offset="8" :span="8">
<div ref="info" style="width: 100%; height: 600px"></div>
</el-col>
</el-row>
</template>
<script setup>
import {
onMounted, ref, inject } from "vue";
const {
proxy } = getCurrentInstance()//获取Vue3全局配置
const info = ref();//用来获取对应标签组件

onMounted(() => {

var infoEl = info.value;//获取ref="info"的标签组件
var userEc = proxy.$echarts.init(infoEl, "light");//proxy.$echarts是在获取全局配置中的echarts,这样就不需要在每个vue中import echarts了

//此处为图表配置区,可参考Echarts官网替换各种图表
var option = {

tooltip: {

trigger: 'item'
},
legend: {

top: '5%',
left: 'center'
},
series: [
{

name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {

borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {

show: false,
position: 'center'
},
emphasis: {

label: {

show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {

show: false
},
data: [
{
value: 1048, name: 'Search Engine' },
{
value: 735, name: 'Direct' },
{
value: 580, name: 'Email' },
{
value: 484, name: 'Union Ads' },
{
value: 300, name: 'Video Ads' }
]
}
]
};
userEc.setOption(option);//将图标挂载到标签组件
});
</script>

(完)

云服务器Docker部署SpringBoot+Redis(Ubuntu)

云服务器Docker部署SpringBoot+Redis(Ubuntu)

参考文件夹结构

language-bash
1
2
3
4
5
6
7
MyTest01
├── javaDocker.sh
├── redisDocker.sh
├── Redis
│ └── data
├── SpringBoot
└── MyWeb01-SpringBoot-0.0.1-SNAPSHOT.jar

一、起手式:配置环境

1.镜像

拉取以下两个镜像

language-bash
1
2
docker pull openjdk:17
docker pull redis

二、启动Redis容器

redisDocker.sh脚本写入以下内容,然后bash运行
脚本意思是将redis数据映射到主机

language-bash
1
2
3
4
5
6
7
8
9
#!/bin/bash

containerName="RedisTest01"
RedisData="/root/MyTest01/Redis/data"

docker run -d --name "$containerName" \
-v "$RedisData":/data \
-p 6379:6379 \
redis

三、配置SpringBoot容器并简单测试

添加如下配置到application.yml

language-yaml
1
2
3
4
5
6
spring:
data:
redis:
host: redisdb
port: 6379
password:

写一个简单的测试如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.myweb01springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;
import java.sql.SQLException;

@RestController
@RequestMapping("/Home")
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class TestController {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@GetMapping("/Kmo")
public String test() throws SQLException {

// 设置一个键值对
redisTemplate.opsForValue().set("name", "张三");

// 获取一个键值对
String name = redisTemplate.opsForValue().get("name");

System.out.println(name);

return "Success!"+name;
}
}

然后maven打包成jar,参考文件夹结构,将jar放入指定位置。
将以下内容写入javaDocker.sh脚本并bash运行
脚本意思是,向名为MySQLTest01的容器建立网络链接(单向的),它的名字(IP,主机名)为db,于是此容器可以通过db:3306访问MySQLTest01容器的mysql服务。

language-bash
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

containerName="JavaTest01"
SpringBootPath="/root/MyTest01/SpringBoot/MyWeb01-SpringBoot-0.0.1-SNAPSHOT.jar"

docker run -d --name "$containerName" \
-p 8081:8081 \
--link RedisTest01:redisdb \
-v "$SpringBootPath":/app/your-app.jar \
openjdk:17 java -jar /app/your-app.jar

开放云服务器安全组入站规则8081端口,浏览器访问云服务器IP:8081/Home/Kmo验证。

(完)

云服务器Docker部署SpringBoot+MySQL(Ubuntu)

云服务器Docker部署SpringBoot+MySQL(Ubuntu)

参考文件夹结构

language-bash
1
2
3
4
5
6
7
MyTest01
├── javaDocker.sh
├── mysqlDocker.sh
├── MySQL
│ └── data
├── SpringBoot
└── MyWeb01-SpringBoot-0.0.1-SNAPSHOT.jar

一、起手式:配置环境

1.镜像

拉取以下两个镜像

language-bash
1
2
docker pull openjdk:17
docker pull mysql:8

二、启动Mysql容器

mysqlDocker.sh脚本写入以下内容,然后bash运行
脚本意思是将mysql数据映射到主机并设置密码为xxxx,

language-bash
1
2
3
4
5
6
7
8
9
10
#!/bin/bash

containerName="MySQLTest01"
MySQLData="/root/MyTest01/MySQL/data"

docker run -d --name "$containerName" \
-p 3306:3306 \
-v "$MySQLData":/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=xxxx \
mysql:8

在云服务器安全组开放入站规则3306端口
此时可以用你喜欢的数据库连接软件来连接这个mysql容器,并创建test01数据库和一张student表,结构随意,随便插入几条数据。

三、配置SpringBoot容器并简单测试

添加如下配置到application.yml
补充你的云服务器IP和刚才设置的密码

language-yaml
1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/test01?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: xxxx

写一个简单的测试如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.myweb01springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;
import java.sql.SQLException;

@RestController
@RequestMapping("/Home")
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class TestController {

@Autowired
DataSource dataSource;

@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/Kmo")
public String test() throws SQLException {

System.out.println("默认数据源为:" + dataSource.getClass());
System.out.println("数据库连接实例:" + dataSource.getConnection());
//访问数据库user表,查询user表的数据量
Integer i = jdbcTemplate.queryForObject("SELECT count(*) from `student`", Integer.class);
System.out.println("user 表中共有" + i + "条数据。");

return "Success!"+i;
}
}

然后本地运行打开浏览器访问localhost:8081/Home/Kmo验证是否从远程数据库取得连接。
然后在application.yml中将url的服务器IP改成db

language-yaml
1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://db:3306/test01?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: xxxx

然后maven打包成jar,参考文件夹结构,将jar放入指定位置。
将以下内容写入javaDocker.sh脚本并bash运行
脚本意思是,向名为MySQLTest01的容器建立网络链接(单向的),它的名字(IP,主机名)为db,于是此容器可以通过db:3306访问MySQLTest01容器的mysql服务。

language-bash
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

containerName="JavaTest01"
SpringBootPath="/root/MyTest01/SpringBoot/MyWeb01-SpringBoot-0.0.1-SNAPSHOT.jar"

docker run -d --name "$containerName" \
-p 8081:8081 \
--link MySQLTest01:db \
-v "$SpringBootPath":/app/your-app.jar \
openjdk:17 java -jar /app/your-app.jar

开放云服务器安全组入站规则8081端口,浏览器访问云服务器IP:8081/Home/Kmo验证。

结束后记得关闭云服务器的3306入站规则

(完)

Vue-Router-编程式路由跳转

Vue-Router-编程式路由跳转

历届办法

  1. path+query
    参数会暴露在url
  2. name+params
    官方在2022–8-22已禁用params传参,具体看这This
  3. state
    参数不会暴露在url,但刷新页面会失效,和以前的params一样
  4. store
    用额外的插件来store,顾名思义存储数据,此本章不做讲解。

起手式:配置路由

无论是path还是name,都需要在路由配置中指定每个路径对应的组件。将配置都写到一个配置文件中,然后在vue的main.js中挂载配置它,具体流程是这样的:

  1. 首先,您需要在项目的src目录下创建一个router文件夹,用来存放路由相关的文件。
  2. 然后,在router文件夹中创建一个index.js文件,用来编写路由配置。例如:
language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// router/index.js
import {
createRouter, createWebHashHistory } from 'vue-router'

// 导入路由组件
import Home from '.../components/Home.vue'
import About from '.../components/About.vue'

// 定义路由
//path
const routes = [
{
path: '/', component: Home },
{
path: '/about', component: About },
]
/*
//name
const routes = [
{ name: "Home", path: "/", component: Home }
{ name: "About ", path: "/about", component: About }
]

*/
// 创建路由实例
const router = createRouter({
history: createWebHashHistory(), routes, })

// 导出路由实例
export default router
  1. 最后,在main.js文件中导入路由器对象,并将其挂载到Vue实例上。例如:
language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main.js
// 在main.js中
import {
createApp } from 'vue'
import App from './App.vue'

// 导入路由实例
import router from './router'

// 创建并挂载根实例
const app = createApp(App)
// 使用路由实例
app.use(router)
app.mount('#app')

1. path+query

发送方

language-typescript
1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import {
useRouter } from "vue-router";
const router = useRouter();
const goPath = (id: number) => {

router.push({
path: "/about", query: {
id: id } });
};
</script>

接收方

language-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts">
import {
useRoute } from "vue-router";
const route = useRoute();
const id = route.query.id;
</script>

<template>
<div>Id: {
{
id }}</div>
</template>

2. state

发送方

language-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts">
import {
useRouter } from "vue-router";
const router = useRouter();
const goPath = (id: number) => {

router.push({
path: "/about", state: {
id: id } });
//或者
//router.push({ name: "About", state: { id: id } });
};
</script>

接收方

language-typescript
1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
const id = history.state.data;
</script>

<template>
<div>Id: {
{
id }}</div>
</template>

Vue-ts-优雅的响应式对象数组(reactive型数组)

Vue-ts-优雅的响应式对象数组(reactive型数组)

需求

绘制如下画面,每一行绑定的数据都是独立的,并且还需要完成”增加行”按钮。

Vue前端代码

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<el-button @click="addLine">增加行</el-button>
<el-row>
<el-col :span="4" align="middle">产品</el-col>
<el-col :span="4" align="middle">仓库</el-col>
<el-col :span="4" align="middle">批次号</el-col>
<el-col :span="4" align="middle">库存数量</el-col>
<el-col :span="4" align="middle">隔离数量</el-col>
</el-row>
<el-form>
<el-row v-for="(item, i) in inItemNums">
<el-col :span="4">
<el-select v-model="inQueryParams[i].product" placeholder="请选择产品" @change="inSelectProductChange(i)">
<el-option
v-for="item in inSelectOption[i].product"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="inQueryParams[i].factory" placeholder="Select" @change="inSelectFactoryChange(i)">
<el-option
v-for="item in inSelectOption[i].factory"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="inQueryParams[i].batchNum" placeholder="Select" @change="inSelectBatchNumChange(i)">
<el-option
v-for="item in inSelectOption[i].batchNum"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-input v-model="inQueryParams[i].stockQuantity" readonly></el-input>
</el-col>
<el-col :span="4">
<el-input v-model="inQueryParams[i].isolateQuantity"></el-input>
</el-col>
</el-row>
</el-form>

Vue逻辑实现

language-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
interface IselectOptionItem {

product: [],
factory: [],
batchNum: [],
isolateQuantity: []
}
const selectOptionItem = {

product: [],
factory: [],
batchNum: [],
isolateQuantity: []
}
interface IqueryParamsItem {

product: undefined,
factory: undefined,
batchNum: undefined,
stockQuantity: undefined,
isolateQuantity: undefined
}
const queryParamsItem = {

product: undefined,
factory: undefined,
batchNum: undefined,
stockQuantity: undefined,
isolateQuantity: undefined
}
const inItemNums = ref(5);
const inSelectOption = reactive<IselectOptionItem []>([])
for (let i = 0; i < inItemNums.value; i++) {

inSelectOption.push({
...selectOptionItem})
}
const inQueryParams = reactive<IqueryParamsItem[]>([]);
for (let i = 0; i < inItemNums.value; i++) {

inQueryParams.push({
...queryParamsItem})
}
const addLine=()=>{

inSelectOption.push({
...selectOptionItem})
inQueryParams.push({
...queryParamsItem})
inItemNums.value = inItemNums.value+1;
}

注意点

往数组添加元素时,不使用selectOptionItem而是{...selectOptionItem},区别是前者是引用,而后置是复制,如果写前者,那么数组里的元素都指向一个对象,那么就会出现下拉框的值一起联动。

JavaSE-网络通信

JavaSE-网络通信

一、概述

基本的通信架构有2种形式:

  • CS架构( Client客户端/Server服务端)
  • BS架构(Browser浏览器/Server服务端) IPV4共32bit,4字节,形如192.168.000.001
    IPV6共128bit,16字节,形如2001:0db8:0000:0023:0008:0800:200c:417a
    IPV4形为4位十进制数,每个数字基于8bit,范围0~255
    IPV6形为8位十六进制数,每个数基于16bit,范围0000~FFFF(0 ~ 65535)
    使用Java自带的java.net.InetAddress 可以互动IP 前两个为构造器,后三个为方法

二、端口和协议

UDP

不事先建立连接,发送方直接发送,不管对面状态,是不可靠连接。但是通信效率高,适合诸如视频直播、语音通话等。单个数据包最大限制为64KB。

TCP

事先通过三次握手建立连接,可靠连接,四次挥手断开连接,在不可靠信道做到可靠连接

三次握手是要Client和Server知道对方可以’收’和’发’
第一次握手:S知道C可以发
第二次握手:C知道S可以收发
第三次握手:S知道C可以收发
之后传输数据时,C也不断向S发送确认消息,S需要回复确认,如果没有,那么C会采取重发等措施。

三、UDP编程

S和C都需要先创建数据包类,用来接收信息。
S代码如下

language-cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Server {

public static void main(String[] args) throws Exception {

//1. create server socket
DatagramSocket socket = new DatagramSocket(8889);
//2. create packet
byte[] bytes = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {

//3.receive
socket.receive(packet);

System.out.println("服务端收到了长度为 "+packet.getLength()+" bytes的数据");
System.out.println("数据为: "+new String(bytes,0, packet.getLength()));
System.out.println("消息来自:\nIP->"+packet.getAddress().getHostAddress());
System.out.println("Name->"+packet.getAddress().getHostName());
System.out.println("Port->"+packet.getPort());
System.out.println("-----------------------------");
}

// socket.close();
}
}

S会在socket.receive一直等待C的消息,同时通过packet也可以查看发送方的一些信息。
C代码如下

language-cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Client {

public static void main(String[] args) throws Exception {

//1. create client
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true){

System.out.println("请说:");
String s = sc.nextLine();
if ("exit".equals(s)){

System.out.println("欢迎下次使用~");
break;
}
//2. create data package
byte[] bytes = s.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(),8889);
//3.send
socket.send(packet);
System.out.println("客户端发送了长度为 "+bytes.length+" bytes的数据");
System.out.println("数据为: "+new String(bytes));
System.out.println("----------------------");
}
socket.close();
}
}

你可能会好奇为什么S可以在循环外创建packet重复使用而C每次循环都要创建新的?
因为C每次发送的信息长度是未知的,而packet创建需要给出length所以C每次发送都需要重新创建,同时S对C发送的消息长度也是未知的,但是由于UDP对数据包有64KB上限制约,所以我们可以直接创建个64KB大的packet来接收,然后截断字节就可以。

四、TCP编程

TCP中C由Socket类创建,而S由ServerSocket类创建,然后通过accept方法再次获得Socket类。

S代码如下

language-cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public static void main(String[] args) throws Exception {

//1.create serversocket
ServerSocket serverSocket = new ServerSocket(8889);
//2.wait client link request
Socket socket = serverSocket.accept();
//3.get input is for receive message
InputStream is = socket.getInputStream();
//4.package low is to high is
DataInputStream dis = new DataInputStream(is);
while (true) {

try {

//5.receive message
System.out.println("消息来自: "+socket.getRemoteSocketAddress());
String rs = dis.readUTF();
System.out.println("消息为:"+rs);
} catch (IOException e) {

System.out.println("客户端离线了");
break;
}
}
//close
dis.close();
socket.close();
}
}

S会在accept等待C
C代码如下

language-cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Client {

public static void main(String[] args) throws Exception {

//1.create socket and send link request to server
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8889);
//2.get socket output for write message to server
OutputStream os = socket.getOutputStream();
//3.package low os to high os
DataOutputStream dos = new DataOutputStream(os);

Scanner sc = new Scanner(System.in);
while (true) {

System.out.println("请说");
String s = sc.nextLine();
if (s.equals("exit")){

break;
}
//4.write message
dos.writeUTF(s);
dos.flush();//立刻发送
System.out.println("已发送~");
}

//close
dos.close();
socket.close();
}
}

通过高级流包装原始流,方便编程,这里用的是数据流,将字符串以UTF-8的编码形式传输。

五、QA

TCP怎么实现多个C对一个S?

一个S应对多个C的TCP通信,S需要线程池,每个通信都抛给子线程处理即可。在ServerSocket.accept到一个C的适合,就把这个socket抛到子线程。

TCP怎么实现群聊?

C除了需要发送信息,还需要接收信息,并且两个行为需要持续开启,所以C需要多线程,一个从用户键盘读取信息发送,一个从S接收其他C发送的信息。
S除了线程池外,还需要一个子线程都能访问共享数组,来保存所有socket,子线程收到对应的C的消息,就抄一份发给所有socket。