TCP网络编程实现聊天室功能代码详解

测试驱动开发
1,开发服务端
(1),创建一个服务端程序,开启监听端口
(2),开始不断等待客户端的请求,一旦来一个客户端请求,就开启一个线程为其服务
(3),服务端线程-run
------接收客户端注册的用户名,并且回馈“欢迎×××进入聊天室”
------改进代码更简洁
------后续客户端给我发什么,我就给他回什么
------客户端A给我发什么,我就给其他的客户端(B,C)发送“A说:××××”
------新增一个集合来存储我们客户端的信息(记录哪些客户端连接着服务端)
2,开发客户端
(1),创建一个跟服务端的连接
------测试目标:测服务端是否可以处理多个客户端请求
(2),向服务端注册用户信息
------“请输入用户名:”
------方式一:服务端收到客户端连接请求后,将这个信息发给客户端
------方式二:这个信息在客户端实现
(3),成功接收服务端的消息
(4),引入多线程来改进,一个负责读,一个负责写
------ReadThread,WriteThread 划分好工作范围
------把通用对象的创建移到构造方法里面,run就更加可以专注自己的业务
3,私聊 @姓名#内容

 

分析:
1.用tcp编程:客户端和服务器端
2.服务器来了个客户就开启一个线程与客户聊天,将多个客户的线程存在集合中
3.客户端先发用户名,再才能发消息聊天
4.一个客户端向服务器发送消息,服务要将这个消息发给其他所有客户
5.客户要用两个线程,一个读的线程,一个写的线程
6.整个项目功能:群聊和私聊

群聊:客户端->发消息->服务器将客户端的消息转发->给所有其他客户端,线程集合
私聊:客户端->发消息->服务器将客户端的消息转发->指定私聊的用户

package com.qf.day35.zy1;

import java.io.IOException;
import java.net.Socket;

/**
 * @Author LXM
 * @Date 2020/3/27 0027
 */
public class ChatClient {
    public static void main(String[] args) throws IOException {
        //创建客户端socket,第一个参数服务端ip,第二个参数服务端的端口号
        Socket socket1=new Socket("127.0.0.1",9999);

        //创建写入线程对象和读取的线程对象与服务器间进行通信
        WriteThread wt=new WriteThread(socket1);
        ReadThread rt=new ReadThread(socket1);

        //启动线程
        wt.start();
        rt.start();
    }
}
package com.qf.day35.zy1;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * @Author LXM
 * @Date 2020/3/27 0027
 */
public class ReadThread extends Thread {
    /**
    *声明一个读取的网络字符输入流
    */
    BufferedReader r;

    /**
    *通过构造方法将用户socket对象伟过来,用socket创建网络读取流,来读取服务器发送消息
    */
    public ReadThread(Socket socket1) {
        try {
            r=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
       try {
          while (true){
              //读取服务器发送的消息
              String receiveMess=r.readLine();
              //将接收消息输出
              System.out.println(receiveMess);
          }
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}
package com.qf.day35.zy1;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * @Author LXM
 * @Date 2020/3/27 0027
 */
public class WriteThread extends Thread {
    /**
    *声明一个网络字符输出流,向服务器发消息
    */
    BufferedWriter w;

    /**
    *通过构造方法将当前用户socket对象传过来,用socket对象创建网络字符输入流,向服务器发消息
    *@param socket1
    */
    public WriteThread(Socket socket1) {
        try {
            w=new BufferedWriter(new OutputStreamWriter(socket1.getOutputStream()));
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        Scanner input=new Scanner(System.in);

        try {
            //1.第一次要将客户端用户名发送服务器
            System.out.println("请输入用户名:");
            String cname=input.nextLine();
            //将用户名发送给服务器
            w.write(cname);
            //换行
            w.newLine();
            //刷新
            w.flush();

            //2.循环向服务器发消息
            while (true) {
                //从控制台接收要发送的消息
                String sendMess = input.nextLine();
                //将消息写入网络中,通过socket发给服务器
                w.write(sendMess);
                //换行
                w.newLine();
                //刷新
                w.flush();
            }
        } catch (IOException e) {
                e.printStackTrace();
        }
    }
}
package com.qf.day35.zy1;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author LXM
 * @Date 2020/3/27 0027
 */
public class ChatServer {
    /**
    *声明一个集合存所有客户端对应线程对象
    */
    public static List<ServerThread> clientThreads=new ArrayList();

    public static void main(String[] args) throws IOException {
        //创建服务端socket
        ServerSocket ss=new ServerSocket(9999);
        System.out.println("客户端与服务器正在连接中...");
        while (true){
            //监听客户端,并接收客户端socket
            Socket socket1=ss.accept();
            System.out.println("来了一个客户");
            //来了一个客户端,开启一个服务端线程,专门用来与当前客户端聊天
            ServerThread st=new ServerThread(socket1);
            //将当前客户对应线程对象存在集合中
            clientThreads.add(st);
            //启动线程
            st.start();
        }

    }
}
package com.qf.day35.zy1;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.SocketException;

/**
 * @Author LXM
 * @Date 2020/3/27 0027
 */
public class ServerThread extends Thread {
    /**
    *声明读客户端消息的网络字符输入流
    */
    BufferedReader r;
    /**
     *声明写客户端消息的网络字符输出流
     */
    BufferedWriter w;

    /**
    *通过构造方法将当前服务端线程对应客户端的socket传过来,用socket创建网络读写流与对应客户端聊天
    *@param socket1
    */
    public ServerThread(Socket socket1) {
        try {
            r=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
            w=new BufferedWriter(new OutputStreamWriter(socket1.getOutputStream()));
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            /*1.欢迎信息*/
            //接收客户端的用户名
            String cname=r.readLine();
            //准备欢迎信息
            String welcomeMess="欢迎"+cname+"进入聊天室";
            //将客户端姓名存在当前客户端对应服务器的线程中,作为服务端线程名称
            Thread.currentThread().setName(cname);

            //遍历所有客户端对应线程集合,将当前用户的欢迎信息转发给所有用户
            for(ServerThread t1:ChatServer.clientThreads){
                //将消息写入当前t1线程对应客户端
                t1.w.write(welcomeMess);
                //换行
                t1.w.newLine();
                //刷新
                t1.w.flush();
            }

            //群聊,循环聊天
            while (true){
                /*1.接收当前客户端发的消息*/
                String recieveMess=r.readLine();
                //判断客户是群聊还是私聊
                if (recieveMess.startsWith("@")){//私聊,@姓名#内容
                    //将私聊的内容进行拆分
                    String[] s=recieveMess.substring(1).split("#");
                    //获得拆分后私聊对象姓名
                    String receiveName=s[0];
                    //获得私聊的内容
                    String receiveWord=s[1];
                    //处理私聊内容
                    String word2=Thread.currentThread().getName()+"悄悄对你说:"+receiveWord;
                    //遍历线程集合,找到私聊的用户,将私聊的内容转发给指定用户
                    for (ServerThread t1:ChatServer.clientThreads){
                        if (t1.getName().equals(receiveName)){
                            //将消息写入当前t1线程对应客户端
                            t1.w.write(word2);
                            //换行
                            t1.w.newLine();
                            //刷新
                            t1.w.flush();
                            break;
                        }
                    }
                }else{//群聊
                    //将当前客户端的消息进行处理
                    String word=Thread.currentThread().getName()+"说:"+recieveMess;

                    /*2.再将当前客户端发的消息转发给其他所有客户端*/
                    //遍历所有客户端对应线程集合来将消息转发给每个客户
                    for (ServerThread t1:ChatServer.clientThreads){
                        if (!t1.getName().equals(Thread.currentThread().getName())){
                            //将消息写入当前t1线程对应客户端
                            t1.w.write(word);
                            //换行
                            t1.w.newLine();
                            //刷新
                            t1.w.flush();
                        }
                    }
                }
            }

        } catch (SocketException e1){//有用户退出了就会抛出这个异常
            //1.获得退出的用户信息
            String exitMess=Thread.currentThread().getName()+"退出了聊天室!";

            /*2.将退出的用户所对应服务器端的线程从线程集合中删除*/
            //遍历当前线程集合,找到当前要退出线程对象,然后再删除
            for (int i=0;i<ChatServer.clientThreads.size();i++){
                if (ChatServer.clientThreads.get(i).getName().equals(Thread.currentThread().getName())){
                   //根据找到要删除的线程对象的索引,将当前线程对象从集合中删除
                    ChatServer.clientThreads.remove(i);
                    break;
                }
            }

            /*3.将当前用户退出的消息转发给所有其他用户*/
            //遍历集合,将线程退出消息发给所有用户
            try {
                for (ServerThread t1 : ChatServer.clientThreads) {
                    //将消息写入当前t1线程对应客户端
                    t1.w.write(exitMess);
                    //换行
                    t1.w.newLine();
                    //刷新
                    t1.w.flush();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
一、实验目的 1.掌握通信规范的制定及实现。 2.练习较复杂的网络编程,能够把协议设计思想应用到现实应用中。 二、实验内容和要求 1.进一步熟悉VC++6编程环境; 2.利用VC++6进行较复杂的网络编程,完成网络聊天的设计及编写; 三、实验(设计)仪器设备和材料 1.计算机及操作系统:PC机,Windows; 2.网络环境:可以访问互联网; 四、 TCP/IP程序设计基础 基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。设计思路(VC6.0下): 第一部分 服务器端 一、创建服务器套接字(create)。 二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。 三、接受来自用户端的连接请求(accept)。 四、开始数据传输(send/receive)。 五、关闭套接字(closesocket)。 第二部分 客户端 一、创建客户套接字(create)。 二、与远程服务器进行连接(connect),如被接受则创建接收进程。 三、开始数据传输(send/receive)。 四、关闭套接字(closesocket)。 CSocket的编程步骤:(注意我们一定要在创建MFC程序第二步的时候选上Windows Socket选项,其中ServerSocket是服务器端用到的,ClientSocket是客户端用的。) (1)构造CSocket对象,如下例: CSocket ServerSocket; CSocket ClientSocket; (2)CSocket对象的Create函数用来创建Windows Socket,Create()函数会自行调用Bind()函数将此Socket绑定到指定的地址上面。如下例: ServerSocket.Create(823); //服务器端需要指定一个端口号,我们用823。 ClientSocket.Create(); //客户端不用指定端口号。 (3)现在已经创建完基本的Socket对象了,现在我们来启动它,对于服务器端,我们需要这个Socket不停的监听是否有来自于网络上的连接请求,如下例: ServerSocket.Listen(5);//参数5是表示我们的待处理Socket队列中最多能有几个Socket。 (4)对于客户端我们就要实行连接了,具体实现如下例: ClientSocket.Connect(CString SerAddress,Unsinged int SerPort);//其中SerAddress是服务器的IP地址,SerPort是端口号。 (5)服务器是怎么来接受这份连接的呢?它会进一步调用Accept(ReceiveSocket)来接收它,而此时服务器端还须建立一个新的CSocket对象,用它来和客户端进行交流。如下例: CSocket ReceiveSocket; ServerSocket.Accept(ReceiveSocket); (6)如果想在两个程序之间接收或发送信息,MFC也提供了相应的函数。如下例: ServerSocket.Receive(String,Buffer); //String是你要发送的字符串,Buffer是发送字符串的缓冲区大小。ServerSocket.Send(String,Butter);//String是你要接收的字符串,Buffer是接收字符串的缓冲区大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值