java socket实现服务端,客户端简单网络通信。Chat
之前写的实现简单网络通信的代码,有一些严重bug。后面详细写。
根据上次的代码,主要增加了用户注册,登录页面,以及实现了实时显示当前在登录状态的人数。并解决一些上次未发现的bug。(主要功能代码参见之前随笔 https://www.cnblogs.com/yuqingsong-cheng/p/12740307.html)
实现用户注册登录就需要用到数据库,因为我主要在学Sql Server。Sql Server也已支持Linux系统。便先在我的电脑Ubuntu系统下进行安装配置。
链接:https://docs.microsoft.com/zh-cn/sql/linux/quickstart-install-connect-red-hat?view=sql-server-ver15
Sql Server官网有各个系统的安装指导文档,所以按照正常的安装步骤,一切正常安装。
可放到服务器中却出现了问题。阿里云学生服务器是2G内存的(做活动外加学生证,真的很香。但内存有点小了)。sqlserer需要至少2G内存。所以只能放弃SqlServer,转向Mysql。
同样根据MySql的官方指导文档进行安装。但进行远程连接却需要一些“乱七八糟”的配置,于是开始“面向百度连接”,推荐一个解决方案,https://blog.csdn.net/ethan__xu/article/details/89320614 适用于mysql8.0以上版本。
数据库部分解决,开始写关于登录,注册类。登录注册部分新开了一个端口进行socket连接。由于功能较简单,所以只用到了插入,查询语句。
客户端读入用户输入的登录,注册信息,发送至服务端,服务端在连接数据库进行查询/插入操作,将结果发送至客户端。
实例代码
package logindata; import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList; public class LoginData implements Runnable{ static ArrayList<Socket> loginsocket = new ArrayList(); public LoginData() { } @Override
public void run() {
ServerSocket serverSocket=null;
try {
serverSocket = new ServerSocket(6567);
} catch (IOException e) {
e.printStackTrace();
}
while(true) {
Socket socket=null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
loginsocket.add(socket); Runnable runnable;
try {
runnable = new LoginDataIO(socket);
Thread thread = new Thread(runnable);
thread.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} class LoginDataIO implements Runnable{ String b="false";
Socket socket;
DataInputStream inputStream;
DataOutputStream outputStream;
public LoginDataIO(Socket soc) throws IOException {
socket = soc;
inputStream = new DataInputStream(socket.getInputStream());
outputStream = new DataOutputStream(socket.getOutputStream());
} @Override
public void run() {
String readUTF = null;
String readUTF2 = null;
String readUTF3 = null;
try {
readUTF = inputStream.readUTF();
readUTF2 = inputStream.readUTF();
readUTF3 = inputStream.readUTF();
} catch (IOException e) {
e.printStackTrace();
} // System.out.println(readUTF+readUTF2+readUTF3); SqlServerCon serverCon = new SqlServerCon();
try {
//判断连接是登录还是注册,返回值不同。
if(readUTF3.equals("login")) {
b=serverCon.con(readUTF, readUTF2);
outputStream.writeUTF(b);
}else {
String re=serverCon.insert(readUTF, readUTF2);
outputStream.writeUTF(re);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // System.out.println(b);
}
} class SqlServerCon { public SqlServerCon() {
// TODO Auto-generated constructor stub
} String name;
String password;
// boolean duge = false;
String duge = "false";
// String url = "jdbc:sqlserver://127.0.0.1:1433;"
// + "databaseName=TestData;user=sa;password=123456";
/**
* com.mysql.jdbc.Driver 更换为 com.mysql.cj.jdbc.Driver。
MySQL 8.0 以上版本不需要建立 SSL 连接的,需要显示关闭。
最后还需要设置 CST。
*/
//连接MySql数据库url格式
String url = "jdbc:mysql://127.0.0.1:3306/mytestdata?useSSL=false&serverTimezone=UTC";
public String con(String n,String p) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(url,"root","uu-7w3yfu?VX");
// System.out.println(connection); Statement statement = connection.createStatement();
// statement.executeUpdate("insert into Data values('china','123456')");
ResultSet executeQuery = statement.executeQuery("select * from persondata"); //登录昵称密码确认
while(executeQuery.next()) {
name=executeQuery.getString(1).trim();
password = executeQuery.getString(2).trim(); //"使用这个方法很重要" String trim() 返回值是此字符串的字符串,其中已删除所有前导和尾随空格。
// System.out.println(n.equals(name));
if(name.equals(n) && password.equals(p)) {
duge="true";
break;
}
}
statement.close();
connection.close();
// System.out.println(duge);
return duge;
} public String insert(String n,String p) throws SQLException, ClassNotFoundException {
boolean b = true;
String re = null;
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(url,"root","uu-7w3yfu?VX");
Statement statement = connection.createStatement(); ResultSet executeQuery = statement.executeQuery("select * from persondata");
while(executeQuery.next()) {
name=executeQuery.getString(1).trim();
// password = executeQuery.getString(2).trim();
if(name.equals(n)) {
b=false;
break;
}
} //返回登录信息
if(b && n.length()!=0 && p.length()!=0) {
String in = "insert into persondata "+"values("+"'"+n+"'"+","+"'"+p+"'"+")"; //这条插入语句写的很捞,但没想到更好的。
// System.out.println(in);
statement.executeUpdate(in);
statement.close();
connection.close();
re="注册成功,请返回登录";
return re;
}else if(n.length()==0 || p.length()==0 ) {
re="昵称或密码不能为空,请重新输入";
return re;
}else {
re="已存在该昵称用户,请重新输入或登录";
return re;
}
}
}
因为服务端需要放到服务器中,所以就删去了服务端的用户界面。
import file.File;
import logindata.LoginData;
import server.Server; public class ServerStart_View { private static Server server = new Server();
private static File file = new File();
private static LoginData loginData = new LoginData();
public static void main(String [] args) {
ServerStart_View frame = new ServerStart_View();
server.get(frame);
Thread thread = new Thread(server);
thread.start(); Thread thread2 = new Thread(file);
thread2.start(); Thread thread3 = new Thread(loginData);
thread3.start();
}
public void setText(String AllName,String string) {
System.out.println(AllName+" : "+string);
}
}
客户端,登录界面与服务带进行socket连接,发送用户信息,并读取返回的信息。
主要代码:
public class Login_View extends JFrame { public static String AllName=null;
static Login_View frame;
private JPanel contentPane;
private JTextField textField;
private JTextField textField_1;
JOptionPane optionPane = new JOptionPane();
private final Action action = new SwingAction();
private JButton btnNewButton_1;
private final Action action_1 = new SwingAction_1();
private JLabel lblNewLabel_2; /**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
frame = new Login_View();
frame.setVisible(true);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} ..................
..................
.................. private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "登录");
putValue(SHORT_DESCRIPTION, "点击登录");
}
public void actionPerformed(ActionEvent e) {
String text = textField.getText();
String text2 = textField_1.getText();
// System.out.println(text+text2);
// boolean boo=false;
String boo=null;
try {
boo = DataJudge.Judge(6567,text,text2,"login");
} catch (IOException e1) {
e1.printStackTrace();
}
if(boo.equals("true")) {
ClientStart_View.main1();
AllName = text; //保存用户名
frame.dispose(); //void dispose() 释放此this Window,其子组件和所有其拥有的子级使用的所有本机屏幕资源 。
}else {
optionPane.showConfirmDialog
(contentPane, "用户名或密码错误,请再次输入", "登录失败",JOptionPane.OK_CANCEL_OPTION);
}
}
} private class SwingAction_1 extends AbstractAction {
public SwingAction_1() {
putValue(NAME, "注册");
putValue(SHORT_DESCRIPTION, "点击进入注册页面");
}
public void actionPerformed(ActionEvent e) {
Registered_View registered = new Registered_View(Login_View.this);
registered.setLocationRelativeTo(rootPane);
registered.setVisible(true);
}
}
}
连接服务端:第一次写的时候连接方法是Boolean类型,但只适用于登录的信息判断,当注册时需要判断昵称是否重复,密码昵称是否为空等不同的返回信息,(服务端代码有相应的判断字符串返回,参上)于是该为将连接方法改为String类型。
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException; public class DataJudge { /*public static boolean Judge(int port,String name,String password,String judge) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", port);
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream()); outputStream.writeUTF(name);
outputStream.writeUTF(password);
outputStream.writeUTF(judge); boolean readBoolean = inputStream.readBoolean(); outputStream.close();
inputStream.close();
socket.close();
return readBoolean;
}*/ public static String Judge(int port,String name,String password,String judge) throws UnknownHostException, IOException { //连接服务端数据库部分
Socket socket = new Socket("127.0.0.1", port);
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream()); outputStream.writeUTF(name);
outputStream.writeUTF(password);
outputStream.writeUTF(judge); String read = inputStream.readUTF(); //登录是一次性的,所以要及时关闭socket
outputStream.close();
inputStream.close();
socket.close();
return read;
}
}
用户注册界面,主要代码:
public class Registered_View extends JDialog{
// DataJudge dataJudge = new DataJudge();
private JTextField textField_1;
private JTextField textField;
JLabel lblNewLabel_2;
private final Action action = new SwingAction(); public Registered_View(JFrame frame) {
super(frame, "", true); //使注册对话框显示在主面板之上。
.........
.........
.........
.........
} private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "注册");
putValue(SHORT_DESCRIPTION, "点击按钮进行注册");
}
public void actionPerformed(ActionEvent e) {
String b=null; //用于接收服务端返回的注册信息字符串
String name = textField.getText();
String password = textField_1.getText();
try {
b = DataJudge.Judge(6567, name, password, "registered");
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} lblNewLabel_2.setText(b);
}
}
用户登录,注册部分至此完毕。
实时显示人数,主要是向客户端返回存储socket对象的泛型数组大小。在当有新的客户端连接之后调用此方法,当有用户断开连接后调用此方法。
public static void SendInfo(String rece, String AllName, String num) throws IOException {
DataOutputStream outputStream = null;
for (Socket Ssocket : Server.socketList) {
outputStream = new DataOutputStream(Ssocket.getOutputStream());
outputStream.writeUTF(num);
outputStream.writeUTF(AllName);
outputStream.writeUTF(rece);
outputStream.flush();
}
}
说说Bug
用户每次断开连接之前都没有先进行socket的关闭,服务端也没有移除相应的socket对象,这就导致当服务端再逐个发送至每个客户端,便找不到那个关闭的socket对象,会产生"write error" 。
所以便需要再客户端断开时移除相应的socket对象,查看java API文档,并没有找到在服务端可以判断客户端socket是否关闭的方方法。
便想到了之前看的方法。(虽然感觉这样麻烦了一步,但没找到更好的办法)。于是在点击退出按钮,或关闭面板时向服务端发送一个"bye"字符,当服务端读取到此字符时便知道客户端要断开连接了,从而退出循环读取操作,移除对应的socket对象。
面板关闭事件监听 @Override
public void windowClosing(WindowEvent arg0) {
try {
chat_Client.send("bye");
File_O.file_O.readbye("bye");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
退出按钮事件监听 private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "退出");
putValue(SHORT_DESCRIPTION, "关闭程序");
}
public void actionPerformed(ActionEvent e) {
int result=optionPane.showConfirmDialog(contentPane, "是否关闭退出", "退出提醒", JOptionPane.YES_NO_OPTION);
if(result==JOptionPane.YES_OPTION) {
try {
chat_Client.send("bye");
File_O.file_O.readbye("bye");
System.exit(EXIT_ON_CLOSE); //static void exit(int status) 终止当前正在运行的Java虚拟机。即终止当前程序,关闭窗口。
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
客户端send方法,发送完bye字符后,关闭socket //send()方法,发送消息给服务器。 “发送”button 按钮点击事件,调用此方法
public void send(String send) throws IOException {
DataOutputStream stream = new DataOutputStream(socket.getOutputStream());
stream.writeUTF(Login_View.AllName);
stream.writeUTF(send); if(send.equals("bye")) {
stream.flush();
socket.close();
}
}
服务端读取到bye字符时,移除相应socket对象,退出while循环 if (rece.equals("bye")) {
judg = false;
Server.socketList.remove(socket);
Server_IO.SendInfo("", "", "" + Server.socketList.size());
/*
* for (Socket Ssocket:Server.socketList) { DataOutputStream outputStream = new
* DataOutputStream(socket.getOutputStream()); outputStream = new
* DataOutputStream(Ssocket.getOutputStream());
* outputStream.writeUTF(""+Server.socketList.size());
* outputStream.writeUTF(""); outputStream.writeUTF("");
* System.out.println("8888888888888888"); outputStream.flush(); }
*/
break;
}
文件的流的关闭,移除也是如此,不在赘述。
文件流还有一个问题,正常登录不能进行第二次文件传输。(第一次写的时候可能我只测试了一次,没有找到bug。哈哈哈哈)
解决这个问题耽搁了好久(太cai了,哈哈哈哈)
原来的代码,服务端读取并发送部分(也可参加看之前的随笔)
while((len=input.read(read,0,read.length))>0) {
for(Socket soc:File.socketList_IO) {
if(soc != socket)
{
output = new DataOutputStream(soc.getOutputStream());
output.writeUTF(name);
output.write(read,0,len);
output.flush();
// System.out.println("开始向客户机转发");
}
}
// System.out.println("执行");
// System.out.println(len);
}
read()方法:API文档的介绍
当读取到文件末尾时会返回-1,可以看到while循环也是当len等于-1时结束循环,然而事与愿违。在debug时(忘记截图)发现,只要客户端的输出流不关闭,服务端当文件的读取完毕后会一直阻塞在
while((len=input.read(read,0,read.length))>0),无法退出,从而无法进行下一次读取转发。也无法使用len=-1进行中断break;
修改如下:
int len=0;
while(true) {
len=0;
if(input.available()!=0)
len=input.read(read,0,read.length);
if(len==0) break;
for(Socket soc:File.socketlist_file) {
if(soc != socket)
{
output = new DataOutputStream(soc.getOutputStream());
output.writeUTF(name);
output.write(read,0,len);
// output.flush();
// System.out.println("开始向客户机转发");
}
// System.out.println("一次转发"+File.socketlist_file.size());
17 }
}
至此结束
感觉文件的传输读取仍然存在问题,下次继续完善。
部分界面截图
java socket实现服务端,客户端简单网络通信。Chat的更多相关文章
- WebSocket集成XMPP网页即时通讯1:Java Web Project服务端/客户端Jetty9开发初探
Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别频繁的应用尚能相安无事, ...
- TCP和UDP的区别以及使用python服务端客户端简单编程
一.TCP.UDP区别总结 1.TCP面向连接(如打电话要先拨号建立连接):UDP是无连接的,即发送数据之前不需要建立连接 2.TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失 ...
- JAVA Socket获取服务端信息
1.Socket.getInetAddress(),获取服务端地址. 2.Socket.getPort(),获取服务端端口.
- Java Tomcat SSL 服务端/客户端双向认证
借花献佛:http://www.blogjava.net/icewee/archive/2012/06/04/379947.html
- JAVA WEBSERVICE服务端&客户端的配置及调用(基于JDK)
前言:我之前是从事C#开发的,因公司项目目前转战JAVA&ANDROID开发,由于对JAVA的各种不了解,遇到的也是重重困难.目前在做WEBSERVICE提供数据支持,看了网上相关大片的资料也 ...
- TCP Socket服务端客户端(二)
本文服务端客户端封装代码转自https://blog.csdn.net/zhujunxxxxx/article/details/44258719,并作了简单的修改. 1)服务端 此类主要处理服务端相关 ...
- python网络编程:socket、服务端、客户端
本文内容: socket介绍 TCP: 服务端 客户端 UDP: 服务端 客户端 首发时间:2018-02-08 01:14 修改: 2018-03-20 :重置了布局,增加了UDP 什么是socke ...
- gprc-java与golang分别实现服务端,客户端,跨语言通信(一.java实现)
1.在pom中引入 <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty< ...
- TCP/IP网络编程之基于UDP的服务端/客户端
理解UDP 在之前学习TCP的过程中,我们还了解了TCP/IP协议栈.在四层TCP/IP模型中,传输层分为TCP和UDP这两种.数据交换过程可以分为通过TCP套接字完成的TCP方式和通过UDP套接字完 ...
随机推荐
- MTK Android 读取SIM卡参数,获取sim卡运营商信息
android 获取sim卡运营商信息(转) TelephonyManager tm = (TelephonyManager)Context.getSystemService(Context.TE ...
- NS网络仿真,小白起步版,模拟仿真之间注意的事项
FTP是基于TCP的,所以FTP应用不可以绑定UDP发送代理 FTP和CBR属于应用流,他们用来绑定TCP和UDP发送代理 TCP用于发送代理时,接收代理为TCPSink,可以绑定FTP应用.CBR流 ...
- 《闲扯Redis四》List数据类型底层编码转换
一.前言 Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要. ...
- Git应用详解第七讲:Git refspec与远程分支的重要操作
前言 前情提要:Git应用详解第六讲:Git协作与Git pull常见问题 这一节来介绍本地仓库与远程仓库的分支映射关系:git refspec.彻底弄清楚本地仓库到底是如何与远程仓库进行联系的. 一 ...
- shell执行${var:m:n}报错Bad substitution解决办法
Ubuntu系统下,执行字符串截取脚本时,总是报错:Bad substitution,脚本非常简单如下: #!/bin/sh str1="hello world!" :} 执行后报 ...
- AJ学IOS 之微博项目实战(8)用AFNetworking和SDWebImage简单加载微博数据
AJ分享,必须精品 一:效果 没有图文混排,也没有复杂的UI,仅仅是简单的显示出微博数据,主要介绍AFNetworking和SDWebImage的简单用法 二:加载数据AFNetworking AFN ...
- AJ学IOS 之小知识之_xcode插件的删除方法_自动提示图片插件KSImageNamed有时不灵_分类或宏之类不能自动提示,
AJ分享,必须精品 一:解决解决自动提示图片插件KSImageNamed有时不灵_分类或宏之类不能自动提示 其实,插件神马的我们自己也能写,并没有想象中的那么难,不过目前我们还是先解决当前问题 在做微 ...
- 如何可视化深度学习网络中Attention层
前言 在训练深度学习模型时,常想一窥网络结构中的attention层权重分布,观察序列输入的哪些词或者词组合是网络比较care的.在小论文中主要研究了关于词性POS对输入序列的注意力机制.同时对比实验 ...
- [De1CTF 2019]SSRF Me
原帖地址 : https://xz.aliyun.com/t/5927 De1CTF 2019 的一个题目总结 题目描述 直接查看页面源代码可以看到正确格式的代码 #! /usr/bin/env py ...
- react: typescript custom hooks useAsyncTable
define basic data: const SET_QUERY = "SET_QUERY"; const TOGGLE_LOADING = "TOGGLE_LOAD ...