TCP是一种基于连接的通信协议,必须要进行连接才能通信。
本人虽然只是一个大一的学生,这个学期刚刚学完Java,但是本人对Java有着极大的学习兴趣和热情,各种寻找资源提升自己的实力。这不,刚刚看完毕向东老师的网络编程教学视频,自己动脑想了这个项目,并动手实现了这个项目,还是感到挺开心的。
现在,我来展示一个我自己写的一个基于TCP协议的Java服务器与多个客户端聊天程序。(各位放心,我注释写的非常详细,但愿不要嫌我啰嗦就好)
客户端代码如下:
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import java.net.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author flying duck
*/
/**
*服务器与一个或多个客户端聊天,客户端设计思路:
* 1、创建客户端Socket对象,指定要连接的服务器IP和端口号。
* 2、建立连接后,通过Socket的方法获取网络IO流。
* 3、通过事件监听机制把文本框中的消息打包成字节数组,通过网络输出流写到网络中,由服务器读入。
* 4、事先开启一个线程,通过网络输入流,接收来自服务器的消息,并显示在聊天文本区域。
* 5、当聊天窗口关闭时,断开与服务器的连接。
*
*/
public class ClientFrame extends JFrame implements ActionListener,Runnable{ //客户端聊天窗口,实现两个接口,作为动作事件侦听器和线程任务类。
Socket soc; //客户端套接字。
JTextField jf; //文本框。
JTextArea jta; //文本区域。
JButton jb; //按钮。
JScrollPane jsp; //滚动面板。
InputStream in; //输入流,用来指向Socket方法获取的网络输入流。
OutputStream out; //输出流,用来指向Socket方法获取的网络输出流。
public ClientFrame() throws IOException{ //构造方法,用来初始化对象以及做一些设置。
super("客户端聊天框"); //调用超类JFrame的构造方法设置聊天框的标题。
soc=new Socket("127.0.0.1",10000); //实例化客户端套接字,指定要连接的服务器程序的IP和端口。
in=soc.getInputStream(); //获取Socket的输入流。
out=soc.getOutputStream(); //获取Socket的输出流。
jf=new JTextField(20); //初始化化文本框,设置容量为20个字符。
jta=new JTextArea(20,20); //初始化文本区域并设置其为20行和20列(一个字符代表一列)。
jb=new JButton("发送"); //初始化按钮。
jsp=new JScrollPane(jta); //初始化滚动面板,并把文本区域放置在滚动面板中。
this.setLayout(new FlowLayout()); //设置窗体布局为流式布局(此布局为从左向右一次添加组件,一行放不下了转到第二行)。
this.add(jf); //将文本框加到窗体中。
this.add(jb); //将按钮加到窗体中。
this.add(jsp); //把滚动面板加到窗体中。
jb.addActionListener(this); //为按钮注册动作事件侦听器(当点击按钮时触发动作事件),因为该类实现了动作事件侦听器接口,所以该类对象就是侦听器。
jf.addActionListener(this); //为文本框注册动作事件侦听器,当按下回车触发动作事件。
this.setBounds(300,300,400,400); //设置窗体边界和大小。
this.setVisible(true); //设置窗体可见(窗体默认是不可见的)。
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置窗体默认关闭操作。
}
public void actionPerformed(ActionEvent e){ //ActionListener接口里的方法,必须实现,用来处理当点击按钮或者在文本框按下回车后的动作事件。
String jfText=jf.getText(); //获取文本框中的内容。
if(jfText.length()>0){ //当文本框里面字符串长度大于零时(如果长度为0,则没有意义)执行下面语句。
byte[] by=jfText.getBytes(); //将字符串变为字节数组。
try {
out.write(by); //将字节数组写入网络输出流中,由服务器来接收。
jta.append("你说:"+jfText+"\n"); //将客户端的消息显示在文本区内。
jf.setText(""); //发送完消息后,清空文本框(以便下次输入)。
} catch (IOException ex) {
Logger.getLogger(ClientFrame.class.getName()).log(Level.SEVERE, null, ex);//一种异常处理,不必深究。
}
}
}
public void run(){ //Runnable接口中的方法(必须实现),线程开启后执行的代码。
while(true){ //因为要不断地接收消息,所以线程要一直运行,所以用while(true)。
byte[] b=new byte[1024]; //用来接收服务器发来的消息。
try {
int count=in.read(b); //用网络输入流读取来自服务器的消息,返回读取的有效字节个数。
jta.append("服务器说:"+new String(b,0,count)+"\n"); //将服务器发来的消息显示在文本区中。
} catch (IOException ex) {
Logger.getLogger(ClientFrame.class.getName()).log(Level.SEVERE, null, ex); //一种异常处理,不必深究。
}
}
}
}
import java.io.IOException;
/**
*
* @author flying duck
*/
public class Client {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws IOException {
// TODO code application logic here
ClientFrame cf=new ClientFrame(); //实例化窗口对象,同时其也是线程任务对象。
Thread reciver=new Thread(cf); //由线程任务对象创建一个线程。
reciver.start(); //开启线程,接收来自服务器的消息。
}
}
服务器端代码如下:
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.swing.*;
import java.net.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author fly duck
*/
/**
* 服务器与一个或多个客户端聊天,服务器端设计思路:
* 1、创建服务器ServerSocket对象(指定端口),服务器在正常运行时处于永久开启状态(while(true))。
* 2、调用ServerSocket的方法接受客户端的连接请求。
* 3、创建一个线程类,每当服务器接收到了一个客户端连接请求并成功连接时,为这个客户端打开一个聊天窗口。
* 4、通过获取的客户端对象,获取与客户端进行数据通信的网络IO流。
* 5、在处理客户端的线程run方法里开启另一种线程,用来接收客户端发来的信息。
* 6、通过事件监听机制把文本框中的消息打包成字节数组,通过网络输出流写到网络中,由客户端读入。
* 7、当客户端断开连接时,服务器断开与客户端的连接。
*
*/
public class ServerFrame extends JFrame implements ActionListener,Runnable{ //服务器聊天窗口,实现两个接口,作为动作事件侦听器和线程任务类。
Socket soc; //客户端对象,服务器接收到的客户端。
JTextField jf; //文本框。
JTextArea jta; //文本区域。
JButton jb; //按钮。
JScrollPane jsp; //滚动面板。
InputStream in; //网络字节输入流。
OutputStream out; //网络字节输出流。
public ServerFrame(Socket soc) throws IOException{ //构造方法,用来初始化对象以及做一些设置。
super("服务器端聊天"); //调用超类JFrame的构造方法设置聊天框的标题。
this.soc=soc; //传递服务器接收到的客户端套接字。
in=soc.getInputStream(); //服务器的输入流,拿的是客户端的输入流。这样,服务器与客户端进行数据传输便知道是与哪个客户端。
out=soc.getOutputStream(); //服务器的输出流,拿的是客户端的输出流。
jf=new JTextField(20); //初始化化文本框,设置容量为20个字符。
jta=new JTextArea(20,20); //初始化文本区域并设置其为20行和20列(一个字符代表一列)。
jb=new JButton("发送"); //初始化按钮。
jsp=new JScrollPane(jta); //初始化滚动面板,并把文本区域放置在滚动面板中。
this.setLayout(new FlowLayout()); //设置窗体布局为流式布局(此布局为从左向右一次添加组件,一行放不下了转到第二行)。
this.add(jf); //将文本框加到窗体中。
this.add(jb); //将按钮加到窗体中。
this.add(jsp); //把滚动面板加到窗体中。
jb.addActionListener(this); //为按钮注册动作事件侦听器(当点击按钮时触发动作事件),因为该类实现了动作事件侦听器接口,所以该类对象就是侦听器。
jf.addActionListener(this); //为文本框注册动作事件侦听器,当按下回车触发动作事件。
this.setBounds(300,300,400,400); //设置窗体边界和大小。
this.setVisible(true); //设置窗体可见(窗体默认是不可见的)。
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置窗体默认关闭操作。
}
public void actionPerformed(ActionEvent e){ //ActionListener接口里的方法,必须实现,用来处理当点击按钮或者在文本框按下回车后的动作事件。
String jfText=jf.getText(); //获取文本框中的内容。
if(jfText.length()>0){ //当文本框里面字符串长度大于零时(如果长度为0,则没有意义)执行下面语句。
byte[] by=jfText.getBytes(); //将字符串变为字节数组。
try {
out.write(by); //将字节数组写入网络输出流中,由客户端来接收。
jta.append("你说:"+jfText+"\n"); //将服务器的消息显示在文本区内。
jf.setText(""); //发送完消息后,清空文本框(以便下次输入)。
} catch (IOException ex) {
Logger.getLogger(ServerFrame.class.getName()).log(Level.SEVERE, null, ex); //一种异常处理,不必深究。
}
}
}
public void run(){ //Runnable接口中的方法(必须实现),线程开启后执行的代码。
while(true){ //因为要不断地接收消息,所以线程要一直运行,所以用while(true)。
byte[] by=new byte[1024]; //用来接收客户端发来的消息。
try {
int count=in.read(by); //用网络输入流读取来自客户端的消息,返回读取的有效字节个数。
jta.setText("客户端说:"+new String(by,0,count)+"\n"); //将客户端发来的消息显示在文本区中。
} catch (IOException ex) {
Logger.getLogger(ServerFrame.class.getName()).log(Level.SEVERE, null, ex); //一种异常处理,不必深究。
}
}
}
}
import java.io.IOException;
import java.net.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author flying duck
*/
/**
* 这个线程类是为了每当有一个客户端连接时,为这个客户端开启一个聊天窗口。
*/
public class DealWithEveryClient extends Thread{
Socket soc; //客户端对象,服务器接收到的客户端。
public DealWithEveryClient(Socket soc){ //构造方法。
this.soc=soc; //传递服务器接收到的客户端套接字。
}
public void run(){ //Thread类中的方法(必须重写),线程开启后执行的代码。
try {
ServerFrame sf=new ServerFrame(soc); //创建一个服务器聊天窗口
Thread thread=new Thread(sf); //实例化接收客户端消息的线程对象。
thread.start(); //开启该线程。
} catch (IOException ex) {
Logger.getLogger(DealWithEveryClient.class.getName()).log(Level.SEVERE, null, ex); //一种异常处理,不必深究。
}
}
}
import java.io.IOException;
import java.net.*;
/**
*
* @author flying duck
*/
public class Server {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws IOException {
// TODO code application logic here
ServerSocket ss=new ServerSocket(10000); //创建服务器端套接字,指定该服务器程序的端口。
while(true){ //正常情况下,服务器程序要一直运行,以便随时都能接收客户端的连接。
Socket soc=null; //客户端套接字对象,初始化为空。
soc=ss.accept(); //服务器接收到客户端连接,返回该客户端对象,此方法会一直阻塞,直到接收到客户端的连接。
if(soc!=null){ //如果客户端连接了,执行下面代码。
DealWithEveryClient dec=new DealWithEveryClient(soc); //为每个客户端开启一个聊天窗口。
dec.start(); //开启此线程。
}
}
}
}