网络聊天程序的设计与实现——计网课设

Charlie Brown1 2024-07-06 10:37:01 阅读 64

实验题目

网络聊天程序的设计与实现

实验目的

使用socket编程,了解socket编程的通信原理,会使用socket进行简单的网络编程,在此基础上编写一聊天程序,能够运行程序,运行客户端和服务器端,实现两个客户端通过服务器端进行通信。

总体设计

1、背景知识

1.1 Tcp协议与Win Sock网络编程接口:

Tcp协议: 是面向连接的运输层协议,提供可靠的、有序的、双向的、面向连接的运输服务。

Win Sock编程: 是一种网络编程接口,实际上是作为TCP/IP协议的一种封装。可以通过调用WinSock的接口函数来调用TCP/IP的各种功能。

WinSock 编程简单流程:WinSock编程分为服务器端和客户端两部分。

2、实现的功能

1、显示上线下线时间  

2、服务端监听上线的客户端的IP和端口号

3、一个客户端发消息后(注:客户端在java编译器的终端输入消息)服务器能接收并转发消息给其他客户端     

4、清屏

如何在局域网中使用:  修改客户端代码中的  private final String HOST = "127.0.0.1" 改为接入的局域网服务器的IP即可

3、代码

<code>package GroupChat;

import javax.swing.*;

import javax.swing.table.DefaultTableModel;

import javax.swing.table.TableModel;

import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.event.MouseEvent;

import java.awt.event.MouseListener;

import java.io.OutputStream;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Date;

class ClientFrame extends JFrame {

//时间显示格式

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

//窗口宽度

final int WIDTH = 700;

//窗口高度

final int HEIGHT = 600;

//创建发送按钮

JButton btnSend = new JButton("发送");

//创建清除按钮

JButton btnClear = new JButton("清屏");

//创建退出按钮

JButton btnExit = new JButton("退出");

//创建聊天消息框

JTextArea jtaChat = new JTextArea();

//当前在线列表的列标题

String[] colTitles = {"IP", "端口"};

//当前在线列表的数据,暂存在动态数组中

String[][] rowDatatrue;

public void setRowData(String address,String port){

String[] newaddress = {address,port};

jtaChat.append(sdf.format(new Date()) + "\n: " + address+":"+port + "上线了\n");

DefaultTableModel model = (DefaultTableModel)jtbOnline.getModel();

model.addRow(new Object[]{newaddress[0],newaddress[1]});

jtbOnline.setModel(model);

}

public void addinfo(String msg){

jtaChat.append(sdf.format(new Date()) + "\n: " + msg.trim() +"\n");

}

public void delRowData(String address,String port){

String[] deladdress = {address,port};

jtaChat.append(sdf.format(new Date()) + "\n: " + address+":"+port + "离线了\n");

DefaultTableModel model = (DefaultTableModel)jtbOnline.getModel();

int rowcount= model.getRowCount();

for(int i=0;i<rowcount;i++){

if(model.getValueAt(i,1).equals(deladdress[1])){

model.removeRow(i);

break;

}

}

model.fireTableDataChanged();

}

//创建当前在线列表

JTable jtbOnline = new JTable

(

new DefaultTableModel(rowDatatrue, colTitles) {

//表格不可编辑,只可显示

@Override

public boolean isCellEditable(int row, int column) {

return false;

}

}

);

//创建聊天消息框的滚动窗

JScrollPane jspChat = new JScrollPane(jtaChat);

//创建当前在线列表的滚动窗

JScrollPane jspOnline = new JScrollPane(jtbOnline);

//设置默认窗口属性,连接窗口组件

public ClientFrame() {

//标题

setTitle("聊天室");

//大小

setSize(WIDTH, HEIGHT);

//不可缩放

setResizable(false);

//设置布局:不适用默认布局,完全自定义

setLayout(null);

//设置按钮大小和位置

btnClear.setBounds(140, 420, 100, 60);

//设置按钮文本的字体

btnClear.setFont(new Font("宋体", Font.BOLD, 18));

//添加按钮

this.add(btnClear);

//聊天消息框自动换行

jtaChat.setLineWrap(true);

//聊天框不可编辑,只用来显示

jtaChat.setEditable(false);

//设置聊天框字体

jtaChat.setFont(new Font("楷体", Font.BOLD, 16));

//设置滚动窗的水平滚动条属性:不出现

jspChat.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

//设置滚动窗的垂直滚动条属性:需要时自动出现

jspChat.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

//设置滚动窗大小和位置

jspChat.setBounds(20, 20, 360, 400);

//添加聊天窗口的滚动窗

this.add(jspChat);

//设置滚动窗的水平滚动条属性:不出现

jspOnline.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

//设置滚动窗的垂直滚动条属性:需要时自动出现

jspOnline.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);

//设置当前在线列表滚动窗大小和位置

jspOnline.setBounds(420, 20, 250, 400);

//添加当前在线列表

this.add(jspOnline);

//添加清屏按钮的响应事件

btnClear.addActionListener

(

new ActionListener() {

@Override

public void actionPerformed(ActionEvent event) {

//聊天框清屏

jtaChat.setText("");

}

}

);

this.show();

}

}

客户端(想要几个客户就创建几个客户端) 

package GroupChat;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

import java.util.Scanner;

public class GroupChatClient {

private final String HOST = "127.0.0.1";//服务器的ip

private final int PORT = 6667;//服务器端口

private Selector selector;

private SocketChannel socketChannel;

private String username;

//构造器,完成初始化工作

public GroupChatClient() throws IOException {

selector = Selector.open();

//连接服务器

socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));

//设置非阻塞

socketChannel.configureBlocking(false);

//将 channel 注册到selector

socketChannel.register(selector, SelectionKey.OP_READ);

//得到 username

username = socketChannel.getLocalAddress().toString().substring(1);

System.out.println(username + " is ok...");

}

//向服务器发送消息

public void sendInfo(String info) {

info = username + " 说:" + info;

try {

socketChannel.write(ByteBuffer.wrap(info.getBytes()));

} catch (IOException e) {

e.printStackTrace();

}

}

//读取从服务器端回复的消息

public void readInfo() {

try {

int readChannels = selector.select();

if (readChannels > 0) {//有可以用的通道

Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

while (iterator.hasNext()) {

SelectionKey key = iterator.next();

if (key.isReadable()) {

//得到相关的通道

SocketChannel sc = (SocketChannel) key.channel();

//得到一个 Buffer

ByteBuffer buffer = ByteBuffer.allocate(1024);

//读取

sc.read(buffer);

//把读到的缓冲区的数据转成字符串

String msg = new String(buffer.array());

System.out.println(msg.trim());

}

}

iterator.remove(); //删除当前的 selectionKey,防止重复操作

} else {

//System.out.println("没有可以用的通道...");

}

} catch (Exception e) {

e.printStackTrace();

}

}

public static void main(String[] args) throws Exception {

//启动我们客户端

GroupChatClient chatClient = new GroupChatClient();

//启动一个线程,每个 3 秒,读取从服务器发送数据

new Thread() {

public void run() {

while (true) {

chatClient.readInfo();

try {

Thread.currentThread().sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}.start();

//发送数据给服务器端

Scanner scanner = new Scanner(System.in);

while (scanner.hasNextLine()) {

String s = scanner.nextLine();

chatClient.sendInfo(s);

}

}

}

 服务器端

package GroupChat;

// 服务端:

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.Channel;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

public class GroupChatServer {

//定义属性

private Selector selector;

private ServerSocketChannel listenChannel;

private ClientFrame clientFrame;

private static final int PORT = 6667;

//构造器

//初始化工作

public GroupChatServer() {

clientFrame = new ClientFrame();

try {

//得到选择器

selector = Selector.open();

//ServerSocketChannel

listenChannel = ServerSocketChannel.open();

//绑定端口

listenChannel.socket().bind(new InetSocketAddress(PORT));

//设置非阻塞模式

listenChannel.configureBlocking(false);

//将该 listenChannel 注册到 selector

listenChannel.register(selector, SelectionKey.OP_ACCEPT);

} catch (IOException e) {

e.printStackTrace();

}

}

public void listen() {

try {

//循环处理

while (true) {

int count = selector.select();

if (count > 0) { //有事件处理

// 遍历得到 selectionKey 集合

Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

while (iterator.hasNext()) {

//取出 selectionkey

SelectionKey key = iterator.next();

//监听到 accept

if (key.isAcceptable()) {

SocketChannel sc = listenChannel.accept();

sc.configureBlocking(false);

//将该 sc 注册到 seletor

sc.register(selector, SelectionKey.OP_READ);

//提示

// System.out.println(sc.getRemoteAddress() + " 上线 ");

String remoteaddress = sc.getRemoteAddress().toString();

String[] split = remoteaddress.split(":");

clientFrame.setRowData(split[0],split[1]);

}

if (key.isReadable()) {//通道发送read事件,即通道是可读的状态

// 处理读(专门写方法..)

readData(key);

}

//当前的 key 删除,防止重复处理

iterator.remove();

}

} else {

System.out.println("等待....");

}

}

} catch (Exception e) {

e.printStackTrace();

} finally {

//发生异常处理....

}

}

//读取客户端消息

public void readData(SelectionKey key) {

SocketChannel channel = null;

try {

//得到 channel

channel = (SocketChannel) key.channel();

//创建 buffer

ByteBuffer buffer = ByteBuffer.allocate(1024);

int count = channel.read(buffer);

//根据 count 的值做处理

if (count > 0) {

//把缓存区的数据转成字符串

String msg = new String(buffer.array());

//输出该消息

System.out.println("form客户端:" + msg);

clientFrame.addinfo(msg);

//向其它的客户端转发消息(去掉自己),专门写一个方法来处理

sendInfoToOtherClients(msg, channel);

}

} catch (IOException e) {

try {

System.out.println(channel.getRemoteAddress() + "离线了..");

String remoteaddress = channel.getRemoteAddress().toString();

String[] split = remoteaddress.split(":");

clientFrame.delRowData(split[0],split[1]);

//取消注册

key.cancel();

//关闭通道

channel.close();

} catch (IOException e2) {

e2.printStackTrace();

}

}

}

//转发消息给其它客户(通道)

private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {

System.out.println("服务器转发消息中...");

//遍历所有注册到 selector 上的 SocketChannel,并排除 self

for (SelectionKey key : selector.keys()) {

//通过 key 取出对应的 SocketChannel

Channel targetChannel = key.channel();

//排除自己

if (targetChannel instanceof SocketChannel && targetChannel != self) {

//转型

SocketChannel dest = (SocketChannel) targetChannel;

//将 msg 存储到 buffer

ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());

//将 buffer 的数据写入通道

dest.write(buffer);

}

}

}

public static void main(String[] args) {

//创建服务器对象

GroupChatServer groupChatServer = new GroupChatServer();

groupChatServer.listen();

}

}

运行结果 

客户端命令行:  

 

                        

 客户端1:

  

客户端2:                                 

客户端3:

 



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。