1、通信首先要有服务端和客户端,并且服务端和客户端要分开设计(一般用是一个工程用于设计服务器,
另建一个工程设计客户端)。运行时先运行服务器,在运行客户端。
2、通信中两个重要的内容:Socket 和 ServerSocket 。
Socket:在客户端通过建立Socket对象,并根据ip和端口连接服务器,还可以通过Socket获取输入输
出流对象,用于读取从服务器发送过来的消息和往服务器发送消息。
ServerSocket:在服务端用于监听服务器设置的端口,等待接受客户端Socket对象的连接。
这两个内容把服务端和客户端连接起来
3、通信中两个主要的内容:发送消息和接收消息。
无论是服务端还是客户端,都是通过Socket对象获取输入输出流对象。并且都是通过字节的形式
发送和接受消息的,发送前先把发送的内容转换成字节的形式,然后再发送,接受到的发送的内容是字
节的形式,接收后要转换成自己需要的形式。
发送消息:OutputStream,它有write写入的方法。
接收消息:InputStream,它有read读取的方法。
4、主要实现的功能:注册账号(注册已存在的账号时,会提示此账号已存在)、登录(不可同时登录两
个相同的账号)、聊天(群聊和点对点(在线的用户可进行的功能))。
5、服务端:
用于创建ServerSocket对象和接受Socket的访问的类:MyServer
package com.fuwuduan;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MyServer {
//创建一个队列用于存储每个客户端访问时创建的线程
public static ArrayList<ServerThread> list = new ArrayList<ServerThread>();
public void setServer(int port){
try {
//创建ServerSocket套接字对象用于监听port端口
ServerSocket ss = new ServerSocket(port);
while(true){
System.out.println("服务器等待客户端的访问");
//循环等待接受客户端的访问,如果没有客户端的访问,循环就在这停止等待客户端的访问
Socket socket = ss.accept();
System.out.println("已连有客户接服务器");
//创建ServerThread线程对象,把客户端的连接放到线程里处理,并启动线程
ServerThread st = new ServerThread(socket);
st.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new MyServer().setServer(8888);
}
}
用于处理客户端的连接的线程类:ServerThread
package com.fuwuduan;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
public class ServerThread extends Thread {
private String userName;
private InputStream input;
private OutputStream output;
private Socket socket; //把socket对象通过构造方法传过来
public static HashMap<String, String> map = new HashMap<String, String>();//哈希表用于存储账号和密码
/**
* 构造方法把socket传过来
* @param socket
*/
public ServerThread(Socket socket){
this.socket = socket;
}
/**
* 读文件,把文件中的用户和密码读出来,并放到哈希表中
*/
public void readFile(){
map.clear(); // 往哈希表中村用户和密码前,先清空哈希表
int count = 0; // 用于计数多少用户
try {
//由于往文件中写入账号和密码后用\n结尾,可以根据\n先读取文件中有多少用户
FileInputStream fis1 = new FileInputStream("D:/Users/Adminstrator/workspace/通信服务器0826/src/com/fuwuduan/cunchu");
int n = fis1.read();
while(n != -1){
if(n == '\n'){
count++;
}
n = fis1.read();
}
fis1.close(); //读完文件直接关闭文件输入流
//在次读取文件,根据写入的方式读出用户和密码
FileInputStream fis2 = new FileInputStream("D:/Users/Adminstrator/workspace/通信服务器0826/src/com/fuwuduan/cunchu");
for(int i=0; i<count; i++){
readFileLine(fis2);
String userName = readFileLine(fis2); //读取用户名
readFileLine(fis2);
readFileLine(fis2);
String passWord = readFileLine(fis2); //读取密码
map.put(userName, passWord); //把用户和密码存到哈希表中
}
fis2.close(); //读完文件直接关闭文件输入流
} catch (Exception e) {
e.printStackTrace();
}
}
public void run(){
try {
//得到网络连接的输入输出流对象
input = socket.getInputStream();
output = socket.getOutputStream();
//从客户端读取是注册还是确定
String cmd = readLine(input);
if("注册".equals(cmd)){
while(true){
//读文件里的账号和密码
readFile();
//读取注册的账号
String suer = readLine(input);
// if("注册窗口已经关闭".equals(suer)){
// break;
// }
//读取注册的密码
String spass = readLine(input);
//初始化一个布尔类型的变量,用于标示注册的用户是否存在,true表示注册的用户不存在,false表示注册的用户存在
boolean Flag = true;
Set<String> set = map.keySet(); //创建Set对象,并获取哈希表中的用户名集合,Set中存的内容是无序的且不可重复的
Iterator<String> iter = set.iterator(); // 创建迭代对象,
while(iter.hasNext()){
String key = iter.next();
if(suer!=null&&suer.equals(key)){
//往客户端写入内容
String msg = "此账号已存在,请重新注册#";
System.out.println(msg);
output.write(msg.getBytes());
Flag = false;
break;
}
}
if(Flag == true){
//往客户端写入内容
String msg = "注册成功#";
output.write(msg.getBytes());
//往文件里写注册的账号和密码
FileOutputStream fos = new FileOutputStream("D:/Users/Adminstrator/workspace/通信服务器0826/src/com/fuwuduan/cunchu",true);
//往文件里写账号和密码
fos.write("账号: ".getBytes());
fos.write((suer+" , ").getBytes());
fos.write("密码: ".getBytes());
fos.write((spass+"\n ").getBytes());
fos.close();
}
}
}
if("确定".equals(cmd)){
//读文件里的账号和密码
readFile();
//往客户端写入内容
String msg = "请输入账号 #";
output.write(msg.getBytes());
//从客户端读取账号
userName = readLine(input);
//往客户端写入内容
msg = "请输入密码 #";
output.write(msg.getBytes());
//从客户端读取密码
String passWord = readLine(input)+"\n";
//判断账号和密码是否正确
if(passWord.equals(map.get(userName))&&eqUser()){
//把每个客户访问创建的线程存储到队列中
MyServer.list.add(this);
msg = "服务器登录成功#";
System.out.println(msg);
output.write(msg.getBytes());
while(true){
msg = readLine(input); //从客户端读取一行
while(true){
if((1+"").equals(msg)){ //群发消息
msg = readLine(input); //从客户端读取一行
while(!(2+"").equals(msg)){
String msg1 = userName+"说:\n "+msg+"#";
sendAllMsg(msg1);
msg = readLine(input);
}
}
if((2+"").equals(msg)){ //点对点发消息
msg = readLine(input); //从客户端读取一行
while(!(1+"").equals(msg)){
for(int i=0; i<MyServer.list.size(); i++){
if(MyServer.list.get(i).getUserName().equals(msg)){
String msg1 = userName+"说:\n "+readLine(input)+"#";
sendMsg(msg1); //往发出信息的客户端发送这条信息
ServerThread sst = MyServer.list.get(i);
sst.sendMsg(msg1); //往要接受的用户发送信息
}
}
msg = readLine(input);
}
}else{
break;
}
}
if(!(1+"").equals(msg)&&!(2+"").equals(msg)){
String strname = "请点击群聊还是点对点#";
output.write(strname.getBytes());
}
}
}else if(passWord.equals(map.get(userName))&&!eqUser()){
msg = "账号已经登录#";
System.out.println(msg);
output.write(msg.getBytes());
}else{
msg = "账号或密码输入不正确,请重新输入 #";
output.write(msg.getBytes());
System.out.println(msg);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
//关闭流和socket
try {
if(socket != null){
socket.close();
}
if(input != null){
input.close();
}
if(output != null){
output.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发送一条消息
*/
public void sendMsg(String msg){
try {
output.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 群发消息
* @param msg
*/
public void sendAllMsg(String msg){
for(int i=0; i<MyServer.list.size(); i++){
ServerThread st = MyServer.list.get(i);
st.sendMsg(msg);
}
}
/**
* 每次读取一行
* @param input 输入流对象
* @return
* @throws IOException
*/
public String readLine(InputStream input) throws IOException{
//创建一个队列
ByteArrayOutputStream intent = new ByteArrayOutputStream();
while(true){
//每次读一个字节
int n = input.read();
if(n == '#'){
break;
}
//把每次读取的字节存到队列中
intent.write(n);
}
//把队列转换成字节数组
byte[] bytes = intent.toByteArray();
//把字节转换成字符串
String str = new String(bytes, "GB2312");
return str;
}
/**
* 读取文件时每次读取一行
* @param input 输入流对象
* @return
* @throws IOException
*/
public static String readFileLine(FileInputStream input) throws IOException{
//创建一个队列
ByteArrayOutputStream intent = new ByteArrayOutputStream();
while(true){
//每次读一个字节
int n = input.read();
if(n == ' '){
break;
}
//把每次读取的字节存到队列中
intent.write(n);
}
//把队列转换成字节数组
byte[] bytes = intent.toByteArray();
//把字节转换成字符串
String str = new String(bytes, "GB2312");
return str;
}
/**
* 获取用户名
* @return
*/
public String getUserName() {
return userName;
}
/**
* 比较此用户是否登录
* @return
*/
public boolean eqUser(){
for(int i=0; i<MyServer.list.size(); i++){
ServerThread st = MyServer.list.get(i);
if(st.getUserName().equals(userName)){
return false;
}
}
return true;
}
}
6、客户端:
登录界面:Cilent
package com.kehuiduan;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
public class Cilent {
/**
* 开始登录界面
*/
public void getUI(){
JFrame jf = new JFrame("登录界面");
jf.setLayout(new FlowLayout());
JLabel jb1 = new JLabel("账号");
jf.add(jb1);
//实例化一个JComboBox类的对象
JComboBox jcbName = new JComboBox();
//设置下拉框对象可以编辑
jcbName.setEditable(true);
//设置jcbName的大小
jcbName.setPreferredSize(new Dimension(220,25));
jf.add(jcbName);
JLabel jb2 = new JLabel("密码");
jf.add(jb2);
//实例化一个JPasswordField类的对象
JPasswordField jpaPwd = new JPasswordField();
//设置jpaPwd的大小
jpaPwd.setPreferredSize(new Dimension(220,25));
jf.add(jpaPwd);
JButton jb3 = new JButton("确定");
jf.add(jb3);
JButton jb4 = new JButton("注册");
jf.add(jb4);
// jf.setUndecorated(true);
jf.setSize(300,290); //给窗体设置大小
jf.setResizable(false); //设置窗体的大小是否改变
jf.setLocationRelativeTo(null); //居中
jf.setDefaultCloseOperation(3); //关闭窗体时,关闭程序
jf.setVisible(true); //显示窗体
//创建事件监听器对象,给按钮添加监听器
ClientAction cal = new ClientAction(jf,jcbName,jpaPwd);
jb3.addActionListener(cal);
jb4.addActionListener(cal);
}
public static void main(String[] args) {
new Cilent().getUI();
}
}
登录界面的事件监听器类:ClientAction
package com.kehuiduan;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import 对话界面.MyDialog;
import 注册.ZhuCe;
public class ClientAction implements ActionListener{
private JComboBox jcbName;
private JPasswordField jpaPwd;
private JFrame jf;
private InputStream input;
private OutputStream output;
private Socket client;
public ClientAction(JFrame jf, JComboBox jcbName, JPasswordField jpaPwd){
this.jf = jf;
this.jcbName = jcbName;
this.jpaPwd = jpaPwd;
}
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
try {
//连接服务器
client = new Socket("192.168.0.133",8888);
//得到输入输出流对象
input = client.getInputStream();
output = client.getOutputStream();
if("确定".equals(cmd)){
//往服务器发一条消息
output.write(("确定"+"#").getBytes());
//得到下拉框的内容
String userName = (String) jcbName.getSelectedItem();
//得到密码框的内容
char[] charpwd = jpaPwd.getPassword();
String passWord = new String(charpwd);
//从服务器读取一条信息
String msg = readLine(input);
System.out.println(msg);
//往服务器书写账号
System.out.println("userName :"+userName);
output.write((userName+"#").getBytes());
//从服务器读取一条信息
msg = readLine(input);
System.out.println(msg);
//往服务器书写密码
System.out.println("passWord :"+passWord);
output.write((passWord+"#").getBytes());
//从服务器读取一条信息
msg = readLine(input);
System.out.println(msg);
if(msg.equals("服务器登录成功")){
//登录成功,关闭登录界面
jf.dispose();
MyDialog st = new MyDialog(input, output, userName);
st.mgUI();
}else if(msg.equals("账号已经登录")){
JOptionPane.showMessageDialog(null, msg);
}else{
//登录不成功,弹出一个对话框
System.out.println(msg);
JOptionPane.showMessageDialog(null, msg);
}
}
if("注册".equals(cmd)){
//往服务器发一条消息
output.write(("注册"+"#").getBytes());
ZhuCe zhuce = new ZhuCe(input,output);
zhuce.cell();
}
} catch (Exception e1) {
e1.printStackTrace();
//运行报错时关闭Socket和输入输出流
try {
if(client != null){
client.close();
}
if(input != null){
input.close();
}
if(output != null){
output.close();
}
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
/**
* 每次读取一行
* @param input 输入流对象
* @return
* @throws IOException
*/
public String readLine(InputStream input) throws IOException{
//创建一个队列
ByteArrayOutputStream intent = new ByteArrayOutputStream();
while(true){
//每次读一个字节
int n = input.read();
if(n == '#'){
break;
}
//把每次读取的字节存到队列中
intent.write(n);
}
//把队列转换成字节数组
byte[] bytes = intent.toByteArray();
//把字节转换成字符串
String str = new String(bytes, "GB2312");
return str;
}
}
注册界面: ZhuCe
package 注册;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.io.InputStream;
import java.io.OutputStream;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
public class ZhuCe {
private InputStream input;
private OutputStream output;
public ZhuCe(InputStream input, OutputStream output) {
this.input = input;
this.output = output;
}
public void cell(){
JFrame jf = new JFrame();
jf.setTitle("注册界面");
jf.setLayout(new FlowLayout());
JLabel jb1 = new JLabel("账号");
jf.add(jb1);
//实例化一个JTextField类的对象
JTextField jcbName = new JTextField();
//设置jcbName的大小
jcbName.setPreferredSize(new Dimension(220,25));
jf.add(jcbName);
JLabel jb2 = new JLabel("密码");
jf.add(jb2);
//实例化一个JPasswordField类的对象
JPasswordField jpaPwd = new JPasswordField();
//设置jpaPwd的大小
jpaPwd.setPreferredSize(new Dimension(220,25));
jf.add(jpaPwd);
JButton jb3 = new JButton("确定");
jf.add(jb3);
jf.setSize(300,290); //给窗体设置大小
jf.setLocationRelativeTo(null); //居中
jf.setDefaultCloseOperation(3); //关闭窗体时,关闭程序
jf.setVisible(true); //显示窗体
//创建事件监听器对象,给按钮添加监听器
ZhuCeAction dat = new ZhuCeAction(input, output, jf, jcbName, jpaPwd);
jb3.addActionListener(dat);
}
}
注册界面的事件监听器类:
package 注册;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
public class ZhuCeAction implements ActionListener{
private JTextField jcbName;
private JPasswordField jpaPwd;
private JFrame jf;
private InputStream input;
private OutputStream output;
public ZhuCeAction(InputStream input, OutputStream output, JFrame jf, JTextField jcbName, JPasswordField jpaPwd){
this.input = input;
this.output = output;
this.jf = jf;
this.jcbName = jcbName;
this.jpaPwd = jpaPwd;
}
/**
*
*/
public void actionPerformed(ActionEvent e) {
String userName = jcbName.getText(); //得到文本框输入的内容
char[] ch = jpaPwd.getPassword(); //得到密码框输入的内容
String passWord = new String(ch); //把从密码框得到的内容转换成字符串
try {
output.write((userName+"#").getBytes()); // 把账号发给服务器
output.write((passWord+"#").getBytes()); // 把密码发给服务器
String msg = readLine(input); //读取从服务器发送过来的信息
System.out.println(msg);
if("此账号已存在,请重新注册".equals(msg)){
//注册不成功,弹出一个对话框
System.out.println(msg);
JOptionPane.showMessageDialog(null, msg);
}
if("注册成功".equals(msg)){
// String ss = "注册窗口已经关闭#";
// output.write(ss.getBytes());
jf.dispose();
}
} catch (IOException e2) {
e2.printStackTrace();
}
}
/**
* 每次读取一行
* @param input 输入流对象
* @return
* @throws IOException
*/
public String readLine(InputStream input) throws IOException{
//创建一个队列
ByteArrayOutputStream intent = new ByteArrayOutputStream();
while(true){
//每次读一个字节
int n = input.read();
if(n == '#'){
break;
}
//把每次读取的字节存到队列中
intent.write(n);
}
//把队列转换成字节数组
byte[] bytes = intent.toByteArray();
//把字节转换成字符串
String str = new String(bytes, "GB2312");
return str;
}
}
聊天界面:MyDialog
package 对话界面;
import java.awt.FlowLayout;
import java.io.InputStream;
import java.io.OutputStream;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class MyDialog {
private InputStream input ;
private OutputStream output ;
private String userName;
public MyDialog (InputStream input, OutputStream output,String userName){
this.input = input;
this.output = output;
this.userName = userName;
}
/**
* 对话框界面
*/
public void mgUI(){
JFrame jf = new JFrame(userName+"--对话界面");
jf.setLayout(new FlowLayout());
//设置一个文本区设置行和列,然后添加到窗体上
JTextArea jtf = new JTextArea(17,50);
jtf.setEditable(false); //文本区不可直接往上面写内容
//jtf.setPreferredSize(new Dimension(550,350));
JScrollPane jspf = new JScrollPane(jtf); //内容超过文本区设置的行和列时,自动生成滚动条
jf.add(jspf);
JButton jb1 = new JButton("群聊");
jf.add(jb1);
JButton jb2 = new JButton("点对点");
jf.add(jb2);
JLabel jl = new JLabel("对话输入框:");
jf.add(jl);
JTextArea jtf1 = new JTextArea(10,50);
//jtf1.setPreferredSize(new Dimension(550,100));
JScrollPane jspf1 = new JScrollPane(jtf1);
jf.add(jspf1);
JButton jb3 = new JButton("发送");
jf.add(jb3);
jf.setSize(620,600); //给窗体设置大小
jf.setLocationRelativeTo(null); //居中
jf.setDefaultCloseOperation(3); //关闭窗体时,关闭程序
jf.setVisible(true); //显示窗体
DialogThread dt = new DialogThread(input, jtf);
dt.start();
//创建事件监听器对象,给按钮添加监听器
DialogAction dat = new DialogAction(output,jtf1);
jb1.addActionListener(dat);
jb2.addActionListener(dat);
jb3.addActionListener(dat);
}
}
聊天界面接受消息的线程类:DialogThread
package 对话界面;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
public class DialogThread extends Thread{
private InputStream input ;
private JTextArea jtf;
public DialogThread(InputStream input, JTextArea jtf) {
this.input = input;
this.jtf = jtf;
}
public void run(){
while(true){
try{
//从服务器读取一条信息
String msg = readLine(input);
//如果没有点击是群聊还是点对点,弹出一个提示框
if("请点击群聊还是点对点".equals(msg)){
JOptionPane.showConfirmDialog(null, msg);
continue;
}
//将读取的信息在第一个文本框中显示出来
jtf.append(msg+"\n");
}catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 每次读取一行
* @param input 输入流对象
* @return
* @throws IOException
*/
public String readLine(InputStream input) throws IOException{
//创建一个队列
ByteArrayOutputStream intent = new ByteArrayOutputStream();
while(true){
//每次读一个字节
int n = input.read();
if(n == '#'){
break;
}
//把每次读取的字节存到队列中
intent.write(n);
}
//把队列转换成字节数组
byte[] bytes = intent.toByteArray();
//把字节转换成字符串
String str = new String(bytes, "GB2312");
return str;
}
}
聊天界面的事件监听器类:
package 对话界面;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.OutputStream;
import javax.swing.JTextArea;
public class DialogAction implements ActionListener{
private OutputStream output ;
private JTextArea jtf1;
public DialogAction(OutputStream output, JTextArea jtf1) {
this.output = output;
this.jtf1 = jtf1;
}
public void actionPerformed(ActionEvent e) {
//获取按钮上的字符串
String str = e.getActionCommand();
//得到在第二个文本框中输入的内容
String cmd = jtf1.getText();
try {
if("群聊".equals(str)){
String a = 1+"#";
output.write(a.getBytes()); //往服务器发送一条信息
}
if("点对点".equals(str)){
String b = 2+"#";
output.write(b.getBytes()); //往服务器发送一条信息
}
if("发送".equals(str)){
output.write((cmd+"#").getBytes()); //往服务器发送一条信息
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}