我是靠谱客的博主 执着星星,最近开发中收集的这篇文章主要介绍day4 TCP客户端&服务器实现(仿QQ),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

​ 这是基于TCP通信的客户端与服务器的实现。以简单的聊天窗作为模板,实现多个客户端之间的通信

一、客户端

分析

我们的客户端分为2个模块,界面显示模块Client通信模块Connect

本文中,首字母大写的Client和Connect表示,首字母小写的client和connect表示所创建的对象

发送数据

​是通过点击按钮button来发送数据,于是我们可以对button创建一个用于发送信息的监听器,当我们点击button时,调用**通信模块的发送函数connect.send(String msg)**来发送消息。可以发现,这个模块通过监听器实现起来比较轻松,因为只有点击按钮时,才会发送消息。这意味着我们只需要把发送函数写好就行。

接收数据

由于我们无法判断对方什么时候给我们发送数据,所以我们需要一个while(true)循环来接收信息。这个循环应该在connect的初始化函数的最后

但是,由于我们要先构造Connect的对象connect,之后才能根据connect构建按钮的监听器,所以,如果我们直接在那添加循环,我们的程序就会卡在这,影响我们构建监听器,从而影响我们发送信息。为了解决这个问题,我们可以构建一个线程,在线程中进行while(true)循环,这样就不会影响我们接下来的程序运行了。

解决了这个问题,我们还面临着一个问题,就是如何把在通信模块connect接收到的消息传送个界面模块client

模块Client 与 通信模块Connect 之间的消息传输

​ 我们需要一个something来在这2个模块之间实现数据传输。

​ 发送数据好说,在client里创建一个connect对象,把信息String msg当成参数传递给connect里的发送函数,形如 connect.send(msg);

接收数据就有点难度了。最简单的方法就是在Connect对象里创建一个Client对象,当在Client对象client里创建一个Connect对象connect时,把client自身传给connect。这样做也不是不想,但不够秀。

我们可以这样做:

  1. 我们可以通过创建interface接口RecvInterface,里面只有一个处理数据的空函数 public void handleData(String msg);
  2. 然后让Client继承RrcvInterface,改写 handleData函数。
  3. 之后只在Connect里创建一个RecvInterface对象recvInterface,并将client传给connect里的recvInterface

​ 这样当我们处理接收信息时,只需要在connect里 recvInterface.handleData(line);即可。

客户端界面

结果:

样式

大框显示接收到的数据

小框显示要发送的数据

(一) 构建接口RecvInterface

/*接口很简单,只有一个处理数据的函数就行*/
public interface RecvInterface {
	public void handleData(String msg);
}

(二) 界面Client

就是一个简单的UI设计,在初始化完成后,将该界面this作为一个 接口 传入到通信模块

/*Client继承接口RecvInterface,方面处理接收到的消息*/	
public class Client implements RecvInterface {
         private Connect connect = new Connect();
         private JTextArea log;
/*初始化模块*/
         public void initUI() {
         /*界面设计*/
             JFrame jf = new JFrame();
             jf.setDefaultCloseOperation(3);
             jf.setLayout(new FlowLayout());

     		//JTextPane可以实现字体颜色和图片插入
             log = new JTextArea(12, 40);

             //滚动面板
             JScrollPane sp = new JScrollPane(log);
		jf.add(sp);

             JTextField input = new JTextField(20);
             	jf.add(input);

             JButton btn = new JButton("Send");
             	jf.add(btn);

             jf.setSize(600, 500);
             jf.setVisible(true);
/*
  创建通信模块connect
  初始化connect
  将自己this传入connect
*/
             connect.init();
             connect.addRecvInterface(this);

             btn.addActionListener(new ActionListener() {

                 @Override
                 public void actionPerformed(ActionEvent e) {
                     String sendMsg = input.getText();
                     connect.send(sendMsg);
                 }
             });
         }
/*重写RecvInterface里的处理数据函数*/
         public void handleData(String msg) {
             log.setText(log.getText()+"rn"+msg);
         }
/*主函数用来启动客户端client*/
         public static void main(String[] args) {
             Client client = new Client();
             client.initUI();
         }
     }

(二) 构建通信模块Connect

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
import java.util.Random;

public class Connect {
/*
  端口socket用来选定要发送的服务器地址
  inputstream & outputstream 用来处理发送和接收的数据
  RecvInterface 用来把信息msg传给界面client
*/
	private Socket socket;
	private InputStream inputStream;
	private OutputStream outputStream;
	private RecvInterface recvInterface;
    
/*
  用来将client界面传给connect
  以此来实现将接收到的消息传给client
*/
	public void addRecvInterface(RecvInterface recvInterface) {
		this.recvInterface = recvInterface;
	}    
    
/*
  初始化函数
*/
	public boolean init() {
    //如果失败(出现异常)返回失败
		try {
        //实例化对象
			socket = new Socket("127.0.0.1", 8000);
			inputStream = socket.getInputStream();
			outputStream = socket.getOutputStream();
		/*
		  由于是仿qq,每个客户端都有自己的ID/编号/昵称
		  于是在此随机生成
		*/
			Random random = new Random();
			outputStream.write(("张三"+random.nextInt(100)+"n").getBytes("gbk"));
			outputStream.flush();
            
		//启动接受接收消息的线程,用函数实现
			startRecvThread();
            
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}
/*
  创建线程
  实现循环接收信息
*/
	Thread recvThread;
	public void startRecvThread() {
		recvThread = new Thread() {
            //开始:
			public void run() {
                //如果线程没有中断
				while(!Thread.interrupted()) {
					try {
                        //如果得到了信息
						String line = readLine(inputStream);
						//收到信息后要传递给界面,监听器设计模式
						if(recvInterface != null) {
							recvInterface.handleData(line);
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		};
		recvThread.start();
	}
/*
  发送函数
*/
	public boolean send(String msg) {
		try {
          //选择发送的编码gbk,发送的信息加上换行
			outputStream.write((msg+"n").getBytes("gbk"));
          //通过flush()强制刷新,发送出去
			outputStream.flush();
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}
/*
  从输入流读取一行字符串
*/
	private String readLine(InputStream inputStream) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		int c = inputStream.read();
		while(c != 'n') {
			bos.write(c);
			c = inputStream.read();
		}
		byte[] bytes = bos.toByteArray();
		return new String(bytes,"gbk");
	}
/*
  这个关闭是拓展内容:
  当你"有需求"要关闭接收的端口就可以调用这个函数
  本次样例里没有这个函数的使用样例
*/    
    public void close() {
		if(recvThread != null) {
			recvThread.interrupt();
		}
        
		try {
			inputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
        
		try {
			outputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
        
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

二、服务器

分析

服务器也是2个模块:

Server

用来启动服务器,打开端口Socket,接收来自客户端Client的请求,并把请求的Client存入一个ClientUser队列,然后单独开一个属于该client的线程,用来处理消息

ClientUer

存入所有请求该服务器的信息,作为一个线程来处理来自该客户端的信息

(一) Server

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws IOException {		
    //创建一个服务端套接字,绑定8000端口
		ServerSocket ss = new ServerSocket(8000);
    //接受客户端访问
		while(true) {
			Socket socket = ss.accept();
			System.out.println("收到一个客户端访问");
          //把该客户端user加入ClientUser列表里
			ClientUser user = new ClientUser(socket);
          //启动user的线程
			user.start();
		}
	}
}

(二) ClientUser

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;

public class ClientUser extends Thread {
/*
  把 客户端的请求处理 抽象成一个ClientUser类
  创建一个队列
  用来存放 客户端的请求处理 user
*/
	private static ArrayList<ClientUser> userList = new ArrayList<ClientUser>();
/*
  每一个客户端的请求处理对象都有
  端口 socket
  各自的名字 nickname
  输入输出流 inputStream & outputStream
*/	
	private Socket socket;
    private String nickname;
	private InputStream inputStream;
	private OutputStream outputStream;
/*
  构造函数
*/
	public ClientUser(Socket socket) {
      /*实例化各种对象*/
		super();
		this.socket = socket;
		try {
			this.inputStream = socket.getInputStream();
			this.outputStream = socket.getOutputStream();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
       /*
         把这个对象加入到userlist队列里
         方便发送消息
       */
		userList.add(this);
	}
/*
  运行函数:为每个ClientUser对象创建一个线程
  用来处理各自的信息发送
  (信息的接收由客户端自己处理)
*/
	public void run() {
      //读取每个客户端client的用户名
		try {
			this.nickname = readLine(inputStream);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
      //启动线程,向发送所有的其他客户端发送消息
		while(!Thread.interrupted()) {
			try {
                
				String line = readLine(inputStream);
			//发送向所有的其他客户端发送消息
				for(int i=0; i<userList.size(); i++) {
					ClientUser user = userList.get(i);
					user.sendMsg(nickname+"说"+line);
				}
				
			} catch (SocketException e) {
            /*
              如果某一客户端关闭(断开连接)
              打印消息
              将其从用户列表中删除
            */
				System.out.println("客户端: "+this.nickname+" close");
				userList.remove(this);
				break;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
/*发送消息的函数*/	
	public void sendMsg(String msg) {
		try {
			outputStream.write((msg+"n").getBytes("gbk"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
/*从输入流读取字符串的函数*/	
	public String readLine(InputStream inputStream) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		int c = inputStream.read();
		while(c != 'n') {
			bos.write(c);
			c = inputStream.read();
		}
		byte[] bytes = bos.toByteArray();
		return new String(bytes,"gbk");
	}
}

最后

以上就是执着星星为你收集整理的day4 TCP客户端&服务器实现(仿QQ)的全部内容,希望文章能够帮你解决day4 TCP客户端&服务器实现(仿QQ)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部