基于socket---简单聊天室的实现

Socket简介:

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

Socket连接过程:

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

(1)服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

(2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

(3)连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

Java Socket编程:

对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信。这样就有两个Socket了,客户端和服务端各一个。

​ 对于Socket之间的通信其实很简单,服务端往Socket的输出流里面写东西,客户端就可以通过Socket的输入流读取对应的内容。Socket与Socket之间是双向连通的,所以客户端也可以往对应的Socket输出流里面写东西,然后服务端对应的Socket的输入流就可以读出对应的内容。

两种常用的Socket类型:

  1. 流式Socket(STREAM):
    是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低
  2. 数据报式Socket(DATAGRAM):
    是一种无连接的Socket,对应于无连接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.

Socket在网络层中的位置:

socket位置

图:socket在网络模型中的位置


Socket聊天室的简介:

通过Socket(套接字)的通信原理,可以制作socket服务器和socket客户端,一个服务器又可以接入多个客户端,服务器和客户端通过输入流输出流实现数据读取写入,从而实现聊天的功能。

Socket通信过程:

  1. 服务器端:通过申请一个socket,绑定到一个IP地址和一个端口上,开启侦听,等待接收连接。
  2. 客户端:通过申请一个socket,连接服务器(通过指定IP地址和端口号),服务器端接到连接请求后,产生一个新的Socket(端口号大于1024)与客户端建立连接并进行通信,原端口继续监听。

聊天室功能简介:

通过学习socket原理,建立了简单的聊天室,实现的功能是通过一个客户端发送消息,其他客户端能同时收到这个消息。

在这个聊天室中涉及的知识有:socket原理,添加鼠标事件,键盘事件,界面布局,输入流输出流的操作等。

Socket服务器代码解释:


服务器监听代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package socket服务器;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.JOptionPane;

public class ServerListening extends Thread {
@Override
public void run() {
try {
@SuppressWarnings("resource")
//创建一个socket监听端口12345,端口取值范围1-65535
ServerSocket serverSocket = new ServerSocket(12345) ;
//通过循环实现一直监听12345端口
while (true) {
Socket socket = serverSocket.accept();
//当有客户端连接到端口是,弹出消息提示框提示
JOptionPane.showMessageDialog(null, "有客户端连接到本机12345端口");
//把连接的客户端传到新的线程
ChatSocket csChatSocket = new ChatSocket(socket);
csChatSocket.start();
//将连接的通信添加到服务器管理集合
ServerManager.getServetManager().add(csChatSocket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

ChatSocket服务器数据处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package socket服务器;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
//创建一个聊天socket继承自Thread类
public class ChatSocket extends Thread {
Socket socket;
//构造函数
public ChatSocket(Socket s) {
this.socket = s;
}
//用于得到输出流,写数据
public void out(String out) {
try {
socket.getOutputStream().write((out+"\n").getBytes("UTF-8"));
} catch (IOException e) {
ServerManager.getServetManager().remove(this);
e.printStackTrace();
};
}
@Override
public void run() {
//当客户端连接到服务器后,服务器给客户端提示
out("你已连接到本服务器");
try {
//获取socket的输入流
BufferedReader br = new BufferedReader(
new InputStreamReader(
socket.getInputStream(),"UTF-8"));
String line = null;
//循环读取数据,当输入流的数据不为空时,把数据写发送到每一个客户端
while ((line = br.readLine()) != null) {
ServerManager.getServetManager().publish(this, line);
}
br.close();//没有数据后,关闭输入流
ServerManager.getServetManager().remove(this);
} catch (IOException e) {
e.printStackTrace();
}
}
}

ServerManager服务器管理模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package socket服务器;

import java.util.Vector;

public class ServerManager {
//单例化处理
private ServerManager() {}
private static final ServerManager sm = new ServerManager();
public static ServerManager getServetManager() {
return sm;
}
//定义一个集合,用来存放不同的客户端
Vector<ChatSocket> vector = new Vector<ChatSocket>();
//实现往Vector中添加个体
public void add(ChatSocket cs) {
vector.add(cs);
}
//实现删除vector中断开连接的线程
public void remove(ChatSocket cs) {
vector.remove(cs);
}
//把获取的消息发布给除自己以外的其他客户端
public void publish(ChatSocket cs,String out) {
for (int i = 0; i < vector.size(); i++) {
ChatSocket csChatSocket = vector.get(i);
//把vector中的每一个个体与传进来的线程进行比较,如果不是自己则发送
if (!cs.equals(csChatSocket)) {
csChatSocket.out(out);
}
}
}
}

服务器启动模块:

1
2
3
4
5
6
package socket服务器;

public static void main(String[] args) {
//启动监听线程
new ServerListening().start();
}

socket客户端代码解释:


启动客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Socket客户端;

import java.awt.EventQueue;
public class StartClient {

public static void main(String[] args) {
//将MainWiondow中的代码复制到这,实现通过主方法启动程序
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow frame = new MainWindow();
frame.setVisible(true);
ChatManager.getCM().setWindow(frame);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

ChatManager聊天管理代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package Socket客户端;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class ChatManager {
private ChatManager() {}
private static final ChatManager instance = new ChatManager();
public static ChatManager getCM() {
return instance;
}

MainWindow window;//定义窗体变量
Socket socket;
String IP;//定义IP变量,用来存放传进来的IP地址
BufferedReader reader;//定义读取数据的输入流
PrintWriter writer;//定义写数据的输出流
//设置给窗体中添加文字的方法,实现消息的显示
public void setWindow(MainWindow window){
this.window = window;
window.appendText("文本框已经和ChatManager绑定了");
}
public void connect(String ip) {
//把传进来的ip赋值给IP
this.IP = ip;
//定义一个线程的执行
new Thread() {
public void run() {
try {
//用于接收ip地址,传到socket中
socket = new Socket(IP, 12345);
writer = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream(),"UTF-8"));
reader = new BufferedReader(
new InputStreamReader(
socket.getInputStream(), "UTF-8"));

String line;
//点读取到的数据不为空时,把读取的数据输出到窗口文本区中
while ((line = reader.readLine()) != null) {
window.appendText("收到: "+line);
}
writer.close();//关闭输入输出流
reader.close();
writer = null;
reader = null;
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

};
}.start();

}
//这是聊天输入框的后台处理,当按了发送后,会把输入框中的数据发送出去
public void send(String out) {
if(writer != null) {
writer.write(out+"\n");
writer.flush();//写完数据后必须刷新缓存区,才能发出去
}else {
window.appendText("当前连接已断开");
}
}
}

客户端主窗体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package Socket客户端;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.border.EmptyBorder;

public class MainWindow extends JFrame {

private JPanel contentPane;
private JTextField ip;
private JTextField send;
private JTextArea txt;
//这是主窗体的定义
public MainWindow() {
setTitle("聊天室");//窗体的标题
setAlwaysOnTop(true);//设置窗体的属性,总是在最上面显示
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 638, 460);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
//这是放置聊天内容的地方。文本区组件
ip = new JTextField();
ip.setText("127.0.0.1");
ip.setColumns(10);

send = new JTextField();
//键盘事件,实现当输完要发送的内容后,直接按回车键,实现发送
send.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
ChatManager.getCM().send(send.getText());
appendText("我说:"+send.getText());
send.setText("");
}
}
});
send.setText("\u4F60\u597D");
send.setColumns(10);
//发送按钮的定义,功能
JButton button = new JButton("发送");
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent arg0) {
//鼠标单击发送按钮后,调用聊天管理的send方法,把输入框中的数据发出去
ChatManager.getCM().send(send.getText());
//向聊天窗体区域添加自己发送的消息
appendText("我说:"+send.getText());
//发送玩后,是输入框中内容为空
send.setText("");
}
});
//定义一个连接到服务器的按钮
JButton button_1 = new JButton("连接到服务器");
button_1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent arg0) {
//单击按钮后,把ip地址传递到socket进程中,实现连接
ChatManager.getCM().connect(ip.getText());
}
});

JScrollPane scrollPane = new JScrollPane();
GroupLayout gl_contentPane = new GroupLayout(contentPane);
gl_contentPane.setHorizontalGroup(
gl_contentPane.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_contentPane.createSequentialGroup()
.addGroup(gl_contentPane.createParallelGroup(Alignment.TRAILING)
.addComponent(scrollPane, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 612, Short.MAX_VALUE)
.addGroup(gl_contentPane.createSequentialGroup()
.addComponent(ip, GroupLayout.DEFAULT_SIZE, 372, Short.MAX_VALUE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(button_1, GroupLayout.DEFAULT_SIZE, 234, Short.MAX_VALUE))
.addGroup(gl_contentPane.createSequentialGroup()
.addComponent(send, GroupLayout.DEFAULT_SIZE, 425, Short.MAX_VALUE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(button, GroupLayout.DEFAULT_SIZE, 181, Short.MAX_VALUE)))
.addGap(0))
);
gl_contentPane.setVerticalGroup(
gl_contentPane.createParallelGroup(Alignment.LEADING)
.addGroup(gl_contentPane.createSequentialGroup()
.addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)
.addComponent(ip, GroupLayout.PREFERRED_SIZE, 36, GroupLayout.PREFERRED_SIZE)
.addComponent(button_1, GroupLayout.PREFERRED_SIZE, 36, GroupLayout.PREFERRED_SIZE))
.addGap(2)
.addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 328, Short.MAX_VALUE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)
.addComponent(send, GroupLayout.PREFERRED_SIZE, 41, GroupLayout.PREFERRED_SIZE)
.addComponent(button, GroupLayout.PREFERRED_SIZE, 36, GroupLayout.PREFERRED_SIZE)))
);

txt = new JTextArea();
scrollPane.setViewportView(txt);
txt.setText("Ready...");
contentPane.setLayout(gl_contentPane);

}
//定义一个追加文本的方法
public void appendText(String in) {
txt.append("\n"+in);
}
}

程序实现截图:

客户端界面

图:客户端截图


连接服务器

图:向服务器请求连接后,服务器会弹出消息框,提示


已经连接到服务器

图:提示成功连接到服务器


聊天室图

图:实现多人聊天功能


坚持原创技术分享,您的支持将鼓励我继续创作!