项目名称:双向多线程通信(Java实现)
基本思路
在前面我们简单介绍了Java实现客户端和服务器通信(详见Java实现服务器与客户端通信),现在我们以此为基础建立一个通信项目,目的在实现双向多线程通信。
实现该项目需要以下几个要点:
客户端方面:
1、利用socket套接字建立客户端,获取客户端的输入输出流;
2、编写自己的协议,定义如何发送和收取数据;
3、为了直观的项目实现需要构建一个界面,在界面加上一些示例(这里用的是文本框,我们需要一行文本框用于发送数据,多行文本框用于接收数据)
服务器方面:
1、利用socket套接字建立服务器,获取服务器的输入输出流;
2、编写自己的协议,定义如何发送和收取数据;
3、构建一个界面,与客户端类似;
4、加上多线程,构建一个Tools类存储每一个客户端对象,以便服务器可以与多个客户端连接,而互不影响。
代码实现
我们以客户端和服务器双向发送字符串为例
客户端方面:
//构建一个客户端,使用boolean类型方面到时候用来判断是否连接成功
public boolean conn2Server(){
try {
java.net.Socket client = new java.net.Socket("127.0.0.1",9998);
System.out.println("连接成功~!");
InputStream ins = ins = client.getInputStream();
OutputStream out = client.getOutputStream();
//取出等下待操作的数据输入输出流
dins = new DataInputStream(ins);
dous = new DataOutputStream(out);
//此时连接成功
return true;
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
得到输入输出流后,就方便我们对他们进行操作
//发送字符串的函数
public void sendText(String msg){
try {
//此处为协议部分,如果发送的数据内容是字符串,我们就发送一个3号信号,
//同理我们定义1信号为发送坐标数据(实现你画我猜),2信号为视频通信
dous.writeByte(3);
byte[] data = msg.getBytes();
int len = data.length;
dous.writeInt(len);
dous.write(data);
dous.flush();
System.out.println("客户机发送字符串len "+len+" msg "+msg);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("*****客户机发送字符串失败 "+msg);
}
}
//这里我用了线程中的run函数装载服务器发过来的数据信息
public void run(){
try {
while(true){
byte type = dins.readByte();
//此为协议定义的判断内容
if(type==3){
int len = dins.readInt();
byte[] data = new byte[len];
dins.read(data);
String inMsg = new String(data);
this.jta.append(inMsg+"\r\n");
System.out.println("收到服务器发来的消息 "+inMsg);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("客户机读消息的线程出错,退出~!");
}
}
以下为界面和主函数代码
package com.Client4;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class Tongxin_project extends JFrame{
public void initUI(){
this.setSize(400,300);
this.setTitle("通信项目客户端");
JButton busend = new JButton("发送");
this.add(busend);
final JTextField jtField = new JTextField(20);
this.add(jtField);
//接受显示:多行文本框
JTextArea jta = new JTextArea(10,30);
this.add(jta);
this.setLayout(new FlowLayout());
this.setDefaultCloseOperation(3);
this.setVisible(true);
final NetConn nc = new NetConn(jta);
if(!nc.conn2Server()){
return ;
}
nc.start();
busend.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
String inMsg = jtField.getText();
System.out.println("客户机己发送: "+inMsg);
nc.sendText(inMsg);
jtField.setText(""); //清空
}
});
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Tongxin_project ClientQq = new Tongxin_project();
ClientQq.initUI();
}
}
实现收发信息NetConn类的完整代码
package com.Client4;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import javax.swing.JTextArea;
public class NetConn extends Thread{
private JTextArea jta;
public NetConn(JTextArea jta){
this.jta = jta;
}
private DataInputStream dins;
private DataOutputStream dous;
public void sendText(String msg){
try {
dous.writeByte(3);
byte[] data = msg.getBytes();
int len = data.length;
dous.writeInt(len);
dous.write(data);
dous.flush();
System.out.println("客户机发送字符串len "+len+" msg "+msg);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("*****客户机发送字符串失败 "+msg);
}
}
public void run(){
try {
while(true){
byte type = dins.readByte();
if(type==3){
int len = dins.readInt();
byte[] data = new byte[len];
dins.read(data);
String inMsg = new String(data);
this.jta.append(inMsg+"\r\n");
System.out.println("收到服务器发来的消息 "+inMsg);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("客户机读消息的线程出错,退出~!");
}
}
public boolean conn2Server(){
try {
java.net.Socket client = new java.net.Socket("127.0.0.1",9998);
System.out.println("连接成功~!");
InputStream ins = ins = client.getInputStream();
OutputStream out = client.getOutputStream();
dins = new DataInputStream(ins);
dous = new DataOutputStream(out);
return true;
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
}
服务器方面:
与客户端类似,但我们为了将每个客户端加到一个单独线程中去,所以要将每个客户端单独打包成一个类来处理他们的输入输出数据,数据的收发函数与客户端类似,但利用Tools类整合服务器对象,以下为代码。
首先是服务器构建
package com.DrawServer4;
import java.awt.Color;
import java.awt.Graphics;
import java.io.DataInputStream;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JTextArea;
public class DrawServer extends Thread{
private JTextArea jta;
public DrawServer(JTextArea jta){
this.jta = jta;
}
public void run(){
setServer(9998);
}
public void setServer(int port){
try {
//服务器建立
java.net.ServerSocket ss = new java.net.ServerSocket(port);
System.out.println("服务器建立成功~! "+port);
while(true){
java.net.Socket client = ss.accept();
System.out.println("有个客户机进来了。。。。");
//在这个类中处理输入输出数据,用上多线程
NetConn nc = new NetConn(client,jta);
nc.start();
//在这里把客户端对象加入Tools存储的对象队列中
Tools.als.add(nc);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
实现客户端后,实现服务器的NetConn类就不难了
package com.DrawServer4;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import javax.swing.JTextArea;
public class NetConn extends Thread{
private Socket client;
private JTextArea jta;
DataInputStream dins;
DataOutputStream dous;
public NetConn(Socket client,JTextArea jta){
this.jta = jta;
try {
//在这里取得客户端对象,输入输出流
this.client = client;
InputStream ins = client.getInputStream();
OutputStream out = client.getOutputStream();
dins = new DataInputStream(ins);
dous = new DataOutputStream(out);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//与客户端类似的操作函数
public void sendText(String msg){
try {
dous.writeByte(3);
byte[] data = msg.getBytes();
int len = data.length;
dous.writeInt(len);
dous.write(data);
dous.flush();
System.out.println("服务器发送字符串len "+len+" msg "+msg);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("*****服务器发送字符串失败 "+msg);
}
}
public void run(){
try {
while(true){
byte type = dins.readByte();
if(type==3){
int len = dins.readInt();
byte[] data = new byte[len];
dins.read(data);
String inMsg = new String(data);
this.jta.append(inMsg+"\r\n");
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("客户机读消息的线程出错,退出~!");
}
}
}
编写一个Tools类存储NetConn对象,同时调用发送函数发送数据。
package com.DrawServer4;
import java.util.ArrayList;
public class Tools {
//此处构建一个私有函数提前占用,防止外人意外构建Tools类,形成混乱
private Tools(){}
public static ArrayList<NetConn> als = new ArrayList();
//这里用static关键字用于自动发送
public static void caseMsg(String msg){
for(int i=0; i<als.size(); i++){
NetConn nc = als.get(i);
nc.sendText(msg);
}
}
}
最后是界面和主函数
package com.DrawServer4;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
public class TongXin_Server extends JFrame{
public void initUI(){
this.setSize(400, 300);
this.setTitle("通信项目 服务器端 v0.1");
JButton buSend=new JButton("发送");
this.add(buSend);
final JTextField jtfField=new JTextField(20);
this.add(jtfField);
//接收显示:多行文本框
javax.swing.JTextArea jta=new javax.swing.JTextArea(10,30);
this.add(jta);
this.setLayout(new FlowLayout());
this.setDefaultCloseOperation(3);
this.setVisible(true);
DrawServer ds=new DrawServer(jta);
ds.start();
//给发送按钮加监听器
buSend.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//1.取得输入框输入的内容
String inMsg=jtfField.getText();
System.out.println("客户机己发送: "+inMsg);
//2.调用网络模块发送,是发给所有的客户机对象了
Tools.caseMsg(inMsg); //static 不是没用,是一定要用到,才有用
jtfField.setText(""); //清空
}
});
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TongXin_Server sm = new TongXin_Server();
sm.initUI();
}
}
这里的代码示例只是最基本的实现了通信功能,可以润色和加强的地方还有很多,为了便于理解,就不增加过多细节要素了,关键在于自己把知识点总结吃透,提高代码实现能力,而不是只纠结于技术本身。