TCP协议,一个服务器(ServerSocket)只服务于一个客户端(Socket),那么可以通过ServerSocket+Thread的方式,实现一个服务器服务于多个客户端。

多线程服务器实现原理——多线程并发机制
1、创建服务器ServerSocket,开启监听.accept(),当有客户端连接,返回一个Socket对象。
2、把Socket对象需要处理的事务,交给Thread,此线程会到一边默默地执行,那么服务器监听就会空闲出来,等待另外一个客户端连接。
3、另一个客户端连接成功,新的Socket对象又交给另一个Thread,以此类推。

【多线程聊天室---群聊】先运行服务器,在运行客户端(2个客户端则运行两次)

                               

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet; //获取客户端发来的消息,并把消息发送到所有客户端
public class Server {
private HashSet<Socket> allSocket;//保存所有服务器的套接字集合
private ServerSocket server;//服务器套接字 public Server() {//构造方法
try {
server = new ServerSocket(4567);//实例化服务器套接字,设定端口4567
} catch (IOException e) {
e.printStackTrace();
}
allSocket = new HashSet<>();//实例化服务器套接字集合
} public void startServer() {//启动服务器,循环获取服务器监听
while (true) {
Socket socket = null;
try {
socket = server.accept();//服务器监听是否有客户端接入
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("用户进入聊天室");//监听成功(客户端连接成功),输出此句代码
allSocket.add(socket);//连接成功的客户端放入集合中
ServerThread t = new ServerThread(socket);//将成功连接的客户端交给线程
t.start();
}
} private class ServerThread extends Thread {//发送一个客户端信息给所有客户端
Socket socket;//客户端套接字 public ServerThread(Socket socket) {//带参数(客户端套接字)的构造方法
this.socket = socket;//参数赋值给客户端套接字
} public void run() {//发送一个客户端信息给所有客户端
BufferedReader br = null;//读取客户端发来的消息
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//获得套接字的字节流
while (true) {//把客户端发来的消息,发给所有客户端
String str = br.readLine();//读取字符串
//若字符串中有EXIT内容(有用户退出聊天室),提醒其他人
if (str.contains("EXIT")) {//用户退出聊天室,给服务器发送"%EXIT%:用户名"(自定义规则)
String tmp = str.split(":")[1] + "用户退出聊天室";//依据:分隔成两个元素,取第2个元素(用户名)
sendMessageToAllClient(tmp);//告诉所有人,他退出了
allSocket.remove(socket);//移除离开的客户端
socket.close();//关闭客户端
return;//停止线程
}
sendMessageToAllClient(str);//如果没有EXIT内容,直接发送给所有客户端。调用自定义方法
}
} catch (IOException e) {
e.printStackTrace();
}
}
} private void sendMessageToAllClient(String str) {//发给所有人,参数str是发送的内容
for (Socket s : allSocket) {//遍历所有接入的客户端
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//客户端输出流
bw.write(str);//输出str
bw.newLine();//换行
bw.flush();//强制将输出流缓冲区的数据送出,清空缓冲区(一般用在IO中)
} catch (IOException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
Server s = new Server();
s.startServer();
}
}
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//登陆界面、获取用户名与IP
public class LinkServerFrame extends JFrame {
private JPanel contentPane;
private JLabel lblIP;
private JLabel lblUserName;
private JTextField tfIP;
private JTextField tfUserName;
private JButton btnLink; public LinkServerFrame(){//构造方法
setTitle("连接服务器");
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100,100,390,150); contentPane=new JPanel();
contentPane.setBorder(new EmptyBorder(5,5,5,5));
contentPane.setLayout(null);
setContentPane(contentPane); lblIP=new JLabel("服务器IP地址:");
lblIP.setFont(new Font("微软雅黑",Font.PLAIN,14));
lblIP.setBounds(20,15,100,15);
contentPane.add(lblIP); tfIP=new JTextField("127.0.0.1");//指定IP内容,本地服务器
tfIP.setBounds(121,13,242,21);
contentPane.add(tfIP);
tfIP.setColumns(10); lblUserName=new JLabel("用户名");
lblUserName.setFont(new Font("微软雅黑",Font.PLAIN,14));
lblUserName.setBounds(60,40,60,15);
contentPane.add(lblUserName); tfUserName=new JTextField();
tfUserName.setBounds(121,42,242,21);
contentPane.add(tfUserName);
tfUserName.setColumns(10); btnLink=new JButton("连接服务器");//登录按钮
btnLink.addActionListener(new ActionListener() {//实现登录,显示客户端窗体,关闭登录窗体
public void actionPerformed(ActionEvent e) {
do_btnLink_actionPerformed(e);
}
});
btnLink.setFont(new Font("微软雅黑",Font.PLAIN,14));
btnLink.setBounds(140,80,120,23);
contentPane.add(btnLink);
} public static void main(String[] args) {
LinkServerFrame linkServerFrame=new LinkServerFrame();//创建窗体对象
linkServerFrame.setVisible(true);//显示窗体
} protected void do_btnLink_actionPerformed(ActionEvent e){
if(!tfIP.getText().equals("")&&!tfUserName.getText().equals("")){//文本框内容不为空
dispose();//关闭窗体,释放资源
//获取文本并除去空格,.trim()的作用是:去掉字符串左右的空格
ClientFrame clientFrame=new ClientFrame(tfIP.getText().trim(),tfUserName.getText().trim());//实例化登录窗口,并赋值IP、用户名
clientFrame.setVisible(true);//显示客户端窗体
}else {
JOptionPane.showMessageDialog(null,"内容不能为空!","警告",JOptionPane.WARNING_MESSAGE);
}
}
}
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.event.*;
import java.text.SimpleDateFormat;
import java.util.Date;
//客户端窗体、共享消息在文本域中、用户退出提醒
public class ClientFrame extends JFrame {
private JPanel contentPane;
private JLabel lblUserName;//显示用户名
private JTextField tfMessage;//信息输入文本框
private JButton btnSend;//发送按钮
private JTextArea textArea;//信息接收文本域
private String userName;//用户名称
Client client;//调用自定义类(在Client.java中) public ClientFrame(String ip,String userName){//ip与userName由登录窗体获得(LinkServerFrame.java中)
this.userName=userName;
init();//窗体初始化,自定义方法
addListener();//调用发送按钮监听,自定义方法 client=new Client(ip,4567);//赋值形参。实例化自定义类(在Client.java中)
ReadMessageThread t=new ReadMessageThread();//显示消息在文本域中
t.start();
} private void init(){//窗体属性(显示与否,由LinkServerFrame.java控制)
setTitle("客户端");
setResizable(false);
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setBounds(100,100,450,300); contentPane=new JPanel();
contentPane.setBorder(new EmptyBorder(5,5,5,5));
contentPane.setLayout(null);
setContentPane(contentPane); JScrollPane scrollPane=new JScrollPane();
scrollPane.setBounds(5,5,434,229);
contentPane.add(scrollPane); textArea=new JTextArea();
//添加滚动条,或JScrollPane scrollPane=new JScrollPane(textArea);
scrollPane.setViewportView(textArea);
textArea.setEditable(false);//文本域不可编辑 JPanel panel=new JPanel();
panel.setBounds(5,235,434,32);
contentPane.add(panel);
panel.setLayout(null); lblUserName=new JLabel(userName);
lblUserName.setHorizontalAlignment(SwingConstants.TRAILING);
lblUserName.setBounds(2,4,55,22);
panel.add(lblUserName); tfMessage=new JTextField();
tfMessage.setBounds(62,5,274,22);
tfMessage.setColumns(10);
panel.add(tfMessage); btnSend=new JButton("发送");
btnSend.setBounds(336,4,93,23);
panel.add(btnSend); tfMessage.validate();//验证容器中的组件,即刷新。
}
//显示消息在文本域中
private class ReadMessageThread extends Thread{//内部类,收消息线程类
public void run() {
while (true) {
String str=client.receiveMessage();//调用自定义方法(在Client.java中)
textArea.append(str+"\n");//将消息显示在文本域中
}
}
} private void addListener(){
btnSend.addActionListener(new ActionListener() {//发送按钮的动作监听
public void actionPerformed(ActionEvent e) {
Date date=new Date();
SimpleDateFormat sf=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
client.sendMessage(userName+" "+sf.format(date)+":\n"+tfMessage.getText());
tfMessage.setText("");//消息发送后,文本框清空
}
});
//开启窗口监听,单击窗口“X”,弹出确认对话框。“是”则关闭客户端窗体
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent atg0){//窗口关闭时
int op=JOptionPane.showConfirmDialog(ClientFrame.this,"确定要退出聊天室吗?","确定",JOptionPane.YES_NO_OPTION);
if(op==JOptionPane.YES_OPTION){//如果选择“是”
client.sendMessage("%EXIT%:"+userName);//如果退出,发送"%EXIT%:用户名"给服务器
try {
Thread.sleep(200);//200ms后,关闭客户端
} catch (InterruptedException e) {
e.printStackTrace();
}
client.close();//关闭客户端套接字,调用Client类中的close方法(Client.java中)
System.exit(0);//关闭程序
}
}
});
}
}
import java.io.*;
import java.net.Socket;
//供ClientFrame.java调用的各种方法,消息输入、输出,关闭套接字
public class Client {
Socket socket;//客户端套接字
BufferedWriter bw;//发数据
BufferedReader br;//收数据 public Client(String ip,int port){//带参数构造方法,参数值由ClientFrame.java赋予
try {
socket=new Socket(ip,port);//实例化客户端套接字,连接服务器
bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//客户端输出流,输出到服务器
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//客户端输入流,服务器反馈给客户端
} catch (IOException e) {
e.printStackTrace();
}
} public void sendMessage(String message){//发消息
try {
bw.write(message);//发消息
bw.newLine();//换行
bw.flush();//强制将输出流缓冲区的数据送出,清空缓冲区(一般用在IO中)
} catch (IOException e) {
e.printStackTrace();
}
} public String receiveMessage(){//收消息
String message=null;
try {
message=br.readLine();//收消息
} catch (IOException e) {
e.printStackTrace();
}
return message;//返回消息结果
} public void close(){//关闭客户端套接字
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

TCP多线程聊天室的更多相关文章

  1. Linux以下基于TCP多线程聊天室(server)

    接上篇博文,本文是server端的实现,主要实现的功能,就是现实client的连接.转发client发送的消息.以及client掉线提示等功能,同一时候能够在这这上面扩展和TCP以及线程相关的功能木块 ...

  2. Linux以下基于TCP多线程聊天室(client)

    不怎么会弄这个博客的排版,就直接将代码附上: 主要是使用多线程去等待接受数据和发送数据.以下是client的代码: tcpsed.h文件 1 2 3 4 5 6 7 8 9 10 11 12 13 1 ...

  3. Linux下c语言TCP多线程聊天室

    开发环境:Linux,GCC 相关知识:TCP(博客:传送门),线程 附加:项目可能还有写不足之处,有些bug没调出来(如:对在线人数的控制),希望大佬赐教. 那么话不多说,放码过来: 码云:传送门, ...

  4. 基于Linux的TCP网络聊天室

    1.实验项目名称:基于Linux的TCP网络聊天室 2.实验目的:通过TCP完成多用户群聊和私聊功能. 3.实验过程: 通过socket建立用户连接并传送用户输入的信息,分别来写客户端和服务器端,利用 ...

  5. 【C++】基于socket的多线程聊天室(控制台版)

    以前学习socket网络编程和多线程编程的时候写的一个练手程序 聊天室基本功能: 1.用户管理:登录,注册,登出,修改用户名,修改密码 2.聊天室功能:群聊,私聊,获取在线用户列表,获取所有用户列表 ...

  6. 网络编程TCP协议-聊天室

    网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...

  7. Linux下c++11多线程聊天室

    刚看的c++11多线程,写个聊天室试试编译的时候加上 c++11 和 多线程库g++ -Wall -std=c++0x -pthread -o server server.cppserver 和cli ...

  8. Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊

    分析: 聊天室需要多个客户端和一个服务端. 服务端负责转发消息. 客户端可以发送消息.接收消息. 消息分类: 群聊消息:发送除自己外所有人 私聊消息:只发送@的人 系统消息:根据情况分只发送个人和其他 ...

  9. 三种TCP协议聊天室实现

    一 概述 使用Java的IO实现聊天室 使用Java的NIO实现聊天室 使用Netty实现聊天室 二 IO聊天室 1 服务器 public class IOServer { public static ...

随机推荐

  1. SpringBoot之修改单个文件后立刻生效

    问题: 在使用SpringBoot进行开发时,如果修改了某个文件比如前端页面html,不能立刻起效. 解决: 在idea中打开修改后的文件,使用快捷键Ctrl+Shift+F9 进行重新编译,然后刷新 ...

  2. Python——FTP上传和下载

    一.FTP对象方法说明 login(user='anonymous',passwd='', acct='') 登录 FTP 服务器,所有参数都是可选的 pwd() 获得当前工作目录 cwd(path) ...

  3. How to flash Havoc on enchilada

    update fastboot and adb fastboot oem unlock adb debug enchilada reboot to fastboot fastboot devices ...

  4. 安装.Net Standard 2.0, Impressive

    此版本的.NET Standard现在支持大约33K的API,与.NET Standard 1.x支持的14K API相比.好的是大部分API来自.NET Framework.这使得生活更容易将代码移 ...

  5. Python getattr() 函数

    Python getattr() 函数  Python 内置函数 描述 getattr() 函数用于返回一个对象属性值. 语法 getattr 语法: getattr(object, name[, d ...

  6. Idea中JavaWeb项目部署

    1. 添加应用服务器tomcat 2. 将tomcat配置添加到项目中 artifacts配置:添加deploy, 添加artifacts,选择Web Application: Exploded &g ...

  7. thymeleaf手动映射根路径映射

    到/src/java/resources/templates/index.html下 @RequestMapping("/") public String index(){ ret ...

  8. kubernetes 每个node上只能运行一个副本DaemonSet

    每个node上只能运行一个副本: apiVersion: extensions/v1beta1 kind: DaemonSet #使用DaemonSet的方式运行 metadata: name: ku ...

  9. [WC2018]即时战略——动态点分治(替罪羊式点分树)

    题目链接: [WC2018]即时战略 题目大意:给一棵结构未知的树,初始时除1号点其他点都是黑色,1号点是白色,每次你可以询问一条起点为白色终点任意的路径,交互库会自动返回给你这条路径上与起点相邻的节 ...

  10. Codeforces997D Cycles in product 【FFT】【树形DP】

    题目大意: 给两个树,求环的个数. 题目分析: 出题人摆错题号系列. 通过画图很容易就能想到把新图拆在两个树上,在树上游走成环. 考虑DP状态F,G,T.F表示最终答案,T表示儿子不考虑父亲,G表示父 ...