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套接字完 ...
随机推荐
- javascript入门 之 ztree(四 自定义Icon)
<!DOCTYPE html> <HTML> <HEAD> <TITLE> ZTREE DEMO - Standard Data </TITLE& ...
- HBase Shell 十大花式玩儿法
前言:工欲善其事必先利其器,今天给大家介绍一下HBase Shell十大花式利器,在日常运维工作中,可以试着用起来. 1. 交互模式 也就是我们最常用到的Shell命令行的方式. $ hbase sh ...
- 最长公共子窜和最长公共子序列(LCS)
他们都是用dp做;复杂度都是O(N方) 有一个大佬的博客写的很详细,是关于最长公共子序列的:https://blog.csdn.net/hrn1216/article/details/51534607 ...
- C - N皇后问题 DFS
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上. 你的任务是,对于给定的N,求出有多少种合法的放置方法. Inpu ...
- vue2.x学习笔记(二十)
接着前面的内容:https://www.cnblogs.com/yanggb/p/12631279.html. 循环引用 递归组件 组件是可以在它们自己的模板中调用自身的,不过它们只能通过[name] ...
- Java SE —— 专栏总集篇
前言: Java 语言,是相对于其他语言而言,门槛低,而且功能还强大的一门编程语言,本人十分看好这一门语言,但是,它也是有深度的,看过本人的<数据结构与算法>专栏的同学们有福了,因为本人在 ...
- web测试流程
1.立项后测试需要拿到文档(需求说明书,原型图,接口文档,) 2.需求评审 3.用例编写(主流程,备流程,异常流,业务规则,正常类,异常类,页面检查) 测试用例编写方法(等价类划分,边界值分析法,错误 ...
- [yii2] 实现所有action方法之前执行一段代码或者方法
我做的是在执行任何方法之前,验证用户登陆状态! 其实就是在controller中写beforeaction()方法, 然后我的方案就是做一个基类,然后让你所有控制器继承你的基类, 如果控制器的基类用_ ...
- [整理]svn常见问题汇总
1.’.’ is not a working copy.Can’t open file‘.svn/entries’: 系统找不到指定的路径.解答:原因是输入的访问路径不正确,如svn://192.16 ...
- Redis的三大问题
一般我们对缓存读操作的时候有这么一个固定的套路: 如果我们的数据在缓存里边有,那么就直接取缓存的. 如果缓存里没有我们想要的数据,我们会先去查询数据库,然后将数据库查出来的数据写到缓存中. 最后将数据 ...