项目名称:双向多线程通信(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();
	}

}

这里的代码示例只是最基本的实现了通信功能,可以润色和加强的地方还有很多,为了便于理解,就不增加过多细节要素了,关键在于自己把知识点总结吃透,提高代码实现能力,而不是只纠结于技术本身。