实验题目
网络聊天程序的设计与实现
实验目的
使用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、代码
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:
版权归原作者 Charlie Brown1 所有, 如有侵权,请联系我们删除。