java 基于TCP/IP协议的局域网聊天小程序

Posted 乘着风破万浪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 基于TCP/IP协议的局域网聊天小程序相关的知识,希望对你有一定的参考价值。

有6个模块

1.用户登录

2.两两私聊

3.群发消息

4.退出登录

5.关闭服务器

6.文件传输

 

一、用户登录
客户端:
1、发送登录信息:LOGIN|Username
处理USERLISTS命令:所有在线用户的用户名
2、处理新上线用户信息:ADD|username

服务器端:
1、得到所有在线用户信息名称,发回给客户端:USERLISTS|user1_user2_user3
2、将当前登录的Socket信息放入Arraylist中
3、将当前登录用户的信息(用户名),发送给已经在线的其他用户:ADD|userName

二、两两私聊
客户端:
1、选择用户
2、组织一个交互协议,发送到服务器:MSG|SenderName|RecName|MSGInfo
3、接收服务器转发回的消息
服务器端:
1、通过RecName用户名查找,找到目标SocketChat
2、向目标对象发送消息:MSG|SenderName|MSGInfo

三、群聊
MSG|Sendername|ALL|MSGInfo

四、用户退出连接
客户端:
1、向服务器发送下线消息:OFFLINE|username
2、关闭ClientChat,清空在线用户列表

3、处理下线用户信息:DEL|username
删除用户列表中的username

服务器端:
1、处理OFFLINE
2、向所有的其他在线用户发送用户下线消息:DEL|username
3、清除ArrayList中的当前用户信息(socketChat)

五、服务器关闭
  客户端:
1、处理CLOSE命令:界面显示服务器关闭

2、关闭ClientChat
3、清空在线用户列表


服务器端:
1、向所有在线用户发送关闭服务器的信息:CLOSE

2、遍历Arraylist将其中的每一个SocketChat关闭
3、ArrayList要清空
4、关闭SokcetListener
5、关闭ServerSocket

 六、文件传输

客户端:
准备工作:1,2,3
//1从本地选中一个文件,获取其文件相关信息(文件名,文件长度)

//2创建文件传输的服务器,与服务器接通

//3组织文件传输交互协议进行传输

//FILETRANS|sendername|recname|文件名|文件长度|IP|Port

处理服务器转发的内容,处理该内容确认是接收还是拒收,如果接收,建立连接通道接收文件(即开启fileRec线程)
如果拒收,组织拒收交互协议(FILECANCEL|拒收者|被拒收者)发送给服务器

服务器端:

接收发来的消息串,提取发送者+接收者+发送内容+传送文件的IP和Port 进行转发

服务器收到交互协议:FILECANCEL|拒收者|被拒收者 重新组织交互协议FILECANCELReturn|拒收者 要通知被拒收者,被拒收的消息

 

 

群发的流程图比较简单就没有画

 

六、文件传输

 

 

 


以下代码只需修改以下IP地址和端口号即可运行,后期准备对发送消息的内容进行加密用Base64加密方法加密

客户端代码:
package rjxy.lkl.Client;

import java.io.*;
import java.net.*;

public class ClientChat extends Thread {

    Socket socket;
    BufferedReader br = null;
    PrintWriter pw = null;
    String UserName;

    public ClientChat(Socket socket, String userName) {
        this.socket = socket;
        UserName = userName;
        try {
            br = new BufferedReader(new InputStreamReader(socket.getInputStream(),
                    "UTF-8"));
            pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
                    socket.getOutputStream(), "UTF-8")));
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        //约定一个登录的交互协议Login|username
        sendMSG("Login|"+UserName);
    }
    public void sendMSG(String str){
        pw.println(str);
        pw.flush();
    }
    //断开socket连接
    public void closeChat(){
        try {
            if(socket!=null) socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //处理服务器发过来的在线用户List ,交互协议为:USERLISTS|user1_user2_user3
    public void run() {
        try {
            String str = "";
            while((str = br.readLine())!=null){
                System.out.println("---"+str+"---");
                String comms[] = str.split("[|]");
                if(comms[0].equals("USERLISTS")){
                    String users[] = comms[1].split("_");
                    ClientMG.getClientMG().addItems(users);
                } else if(comms[0].equals("ADD")){
                    ClientMG.getClientMG().addItem(comms[1]);
                } else if(comms[0].equals("MsgReturn")){
                    //"MsgReturn|"+sender+"|"+msg
                    String sender = comms[1];
                    String msg = comms[2];
                    ClientMG.getClientMG().setLog("【"+sender+"】:");
                    ClientMG.getClientMG().setLog(msg);
                } else if(comms[0].equals("DEL")){
                    //交互协议为:"DEL|"+UserName
                    String sUser = comms[1];
                    ClientMG.getClientMG().removeItem(sUser);
                    ClientMG.getClientMG().setLog(sUser+"已下线");
                } else if(comms[0].equals("CLOSE")){
                    ClientMG.getClientMG().setLog("服务器已关闭");
                    //关闭ClientChat
                    ClientMG.getClientMG().getClientChat().closeChat();
                    //清空在线用户列表
                    ClientMG.getClientMG().clearItems();
                } else if(comms[0].equals("FILETRANS")){
                    //"FILETRANS|"+sender+"|"+sFname+"|"+finfo.length()+"|"+IP+"|"+Port
                    String sender = comms[1];
                    String fileName = comms[2];
                    int filelen = Integer.parseInt(comms[3]);
                    String sIP = comms[4];
                    int port = Integer.parseInt(comms[5]);
                    //调用ClientMG中的接收文件的方法
                    ClientMG.getClientMG().recFile(sender, fileName, filelen, sIP, port);
                    
                } else if(comms[0].equals("FILECANCEL")){
                    ClientMG.getClientMG().cancelFileTrans();
                } else if (comms[0].equals("FILECANCELReturn")){
                    //FILECANCELReturn|拒收者A
                    ClientMG.getClientMG().setLog(comms[1]+"取消了传输。。。");
                }
                
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if (br != null)
                    br.close();
                if (pw != null)
                    pw.close();
                if (socket != null)
                    socket.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
    }
}

 

 

package rjxy.lkl.Client;

import java.awt.BorderLayout;

public class ClientForm extends JFrame {

    private JPanel contentPane;
    public JPanel panel;
    public JLabel lblIp;
    public JTextField txtIP;
    public JLabel label_1;
    public JTextField txtPort;
    public JButton btnLogin;
    public JButton btnExit;
    public JPanel panel_1;
    public JButton btnSend;

    Socket socket;    
    BufferedReader br=null;
    PrintWriter pw=null;
    public JScrollPane scrollPane;
    public JTextArea txtLog;
    public JScrollPane scrollPane_1;
    public JTextArea txtSend;
    public JLabel label;
    public JTextField txtUser;
    public JScrollPane scrollPane_2;
    public JList lOnlines;
    
    DefaultListModel<String> items=new DefaultListModel<String>();
    public JButton sendToAll;
    public JButton FileTranbutton;
    public JProgressBar FileprogressBar;
    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    ClientForm frame = new ClientForm();
                    frame.setVisible(true);
                    ClientMG.getClientMG().setClientForm(frame);
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public ClientForm() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 513, 583);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        
        panel = new JPanel();
        panel.setLayout(null);
        panel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "\\u767B\\u5F55\\u4FE1\\u606F", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panel.setBounds(11, 10, 476, 64);
        contentPane.add(panel);
        
        lblIp = new JLabel("IP:");
        lblIp.setHorizontalAlignment(SwingConstants.RIGHT);
        lblIp.setBounds(10, 19, 35, 31);
        panel.add(lblIp);
        
        txtIP = new JTextField();
        txtIP.setText("10.12.50.10");
        txtIP.setColumns(10);
        txtIP.setBounds(55, 22, 97, 24);
        panel.add(txtIP);
        
        label_1 = new JLabel("\\u7AEF\\u53E3:");
        label_1.setHorizontalAlignment(SwingConstants.RIGHT);
        label_1.setBounds(154, 19, 35, 31);
        panel.add(label_1);
        
        txtPort = new JTextField();
        txtPort.setText("8899");
        txtPort.setColumns(10);
        txtPort.setBounds(190, 21, 35, 26);
        panel.add(txtPort);
        
        btnLogin = new JButton("\\u767B\\u5F55");
        btnLogin.addActionListener(new BtnLoginActionListener());
        btnLogin.setBounds(337, 22, 65, 25);
        panel.add(btnLogin);
        
        btnExit = new JButton("\\u9000\\u51FA");
        btnExit.addActionListener(new BtnExitActionListener());
        btnExit.setBounds(401, 22, 65, 25);
        panel.add(btnExit);
        
        label = new JLabel("\\u7528\\u6237\\u540D:");
        label.setHorizontalAlignment(SwingConstants.RIGHT);
        label.setBounds(228, 19, 50, 31);
        panel.add(label);
        
        txtUser = new JTextField();
        txtUser.setText("visiter");
        txtUser.setColumns(10);
        txtUser.setBounds(281, 22, 50, 26);
        panel.add(txtUser);
        
        panel_1 = new JPanel();
        panel_1.setLayout(null);
        panel_1.setBorder(new TitledBorder(new LineBorder(new Color(184, 207, 229)), "\\u64CD\\u4F5C", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panel_1.setBounds(10, 414, 477, 121);
        contentPane.add(panel_1);
        
        btnSend = new JButton("\\u53D1\\u9001\\u6D88\\u606F");
        btnSend.addActionListener(new BtnSendActionListener());
        btnSend.setBounds(357, 82, 110, 23);
        panel_1.add(btnSend);
        
        scrollPane_1 = new JScrollPane();
        scrollPane_1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane_1.setBounds(10, 24, 457, 48);
        panel_1.add(scrollPane_1);
        
        txtSend = new JTextArea();
        scrollPane_1.setViewportView(txtSend);
        
        sendToAll = new JButton("\\u7FA4\\u53D1\\u6D88\\u606F");
        sendToAll.addActionListener(new ButtonActionListener());
        sendToAll.setBounds(254, 82, 93, 23);
        panel_1.add(sendToAll);
        
        FileTranbutton = new JButton("\\u4F20\\u8F93\\u6587\\u4EF6");
        FileTranbutton.addActionListener(new ButtonActionListener_1());
        FileTranbutton.setBounds(10, 82, 81, 23);
        panel_1.add(FileTranbutton);
        
        FileprogressBar = new JProgressBar();
        FileprogressBar.setBounds(98, 92, 146, 8);
        panel_1.add(FileprogressBar);
        
        scrollPane = new JScrollPane();
        scrollPane.setBorder(new TitledBorder(null, "\\u804A\\u5929\\u8BB0\\u5F55", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        scrollPane.setBounds(11, 84, 309, 332);
        contentPane.add(scrollPane);
        
        txtLog = new JTextArea();
        scrollPane.setViewportView(txtLog);
        
        scrollPane_2 = new JScrollPane();
        scrollPane_2.setBorder(new TitledBorder(null, "\\u5728\\u7EBF\\u7528\\u6237", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        scrollPane_2.setBounds(323, 83, 164, 332);
        contentPane.add(scrollPane_2);
        
        lOnlines = new JList(items);
        scrollPane_2.setViewportView(lOnlines);
    }
    //登录
    private class BtnLoginActionListener implements ActionListener {
        public void actionPerformed(ActionEvent arg0) {
            String ip = txtIP.getText().trim();
            int port = Integer.parseInt(txtPort.getText().trim());
            String socketname = txtUser.getText().trim();
            ClientMG.getClientMG().connect(ip, port, socketname);
        }
    }
    //发送信息
    private class BtnSendActionListener implements ActionListener {
        public void actionPerformed(ActionEvent arg0) {
            //选中要聊天的用户
            String sendUsername = null;
            if(lOnlines.getSelectedIndex()>=0){
                //得到用户选择的名称
                String targetUsername = lOnlines.getSelectedValue().toString();
                System.out.println(targetUsername);
                //发送者的名称
                sendUsername = txtUser.getText().trim();
                //消息体
                String sMSG = txtSend.getText();
                //交互协议 "MSG|"+sendUsername+"|"+targetUsername+"|"+sMSG
                //包装后的消息发送出去
                String strSend = "MSG|"+sendUsername+"|"+targetUsername+"|"+sMSG;
                System.out.println(strSend);
                ClientMG.getClientMG().getClientChat().sendMSG(strSend);
                ClientMG.getClientMG().setLog("I send To "+targetUsername+":");
                ClientMG.getClientMG().setLog(sMSG);
                //清空发送消息框
                txtSend.setText("");
            }
            
        }
    }
    //推出操作
    private class BtnExitActionListener implements ActionListener {
        public void actionPerformed(ActionEvent arg0) {
            //组织推出交互协议"OFFLINE|"+username
            String sendMSG = "OFFLINE|"+txtUser.getText().trim();
            ClientMG.getClientMG().getClientChat().sendMSG(sendMSG);
            //断开与服务器的socket连接
            ClientMG.getClientMG().getClientChat().closeChat();
            ClientMG.getClientMG().clearItems();  //清空列表
            
        }
    }
    private class ButtonActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            //发送者的名称
            String sendUsername = txtUser.getText().trim();
            //消息体
            String sMSG = txtSend.getText();
            //交互协议 "MSG|"+sendUsername+"|"+targetUsername+"|"+sMSG
            //包装后的消息发送出去
            String strSend = "MSG|"+sendUsername+"|ALL|"+sMSG;
            System.out.println(strSend);
            //协议为:"MSG|"+sendUsername+"|ALL|"+msg
            ClientMG.getClientMG().getClientChat().sendMSG(strSend);
            txtSend.setText("");
        }
    }
    //传输文件
    private class ButtonActionListener_1 implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            //1从本地选中一个文件,获取其文件相关信息(文件名,文件长度)
            //2创建文件传输的服务器,与服务器接通
            //3组织文件传输交互协议进行传输 
            //FILETRANS|sendername|recname|文件名|文件长度|IP|Port
            
            File finfo = null;
            //用于文件选择的对象jFileChooser
            JFileChooser jFileChooser = new JFileChooser();
            int result = jFileChooser.showOpenDialog(null);
            if(result==JFileChooser.APPROVE_OPTION){
                finfo = new File(jFileChooser.getSelectedFile().getAbsolutePath());
                String sFname = finfo.getName();
                //在用户列表中选择目标用户
                if(lOnlines.getSelectedIndex()!=-1){
                    String sTarget = lOnlines.getSelectedValue().toString();//得到目标用户
                    String sender = txtUser.getText().trim();
                    //得到新开服务器的IP+Port
                    String IPandPort = ClientMG.getClientMG().CreateFileTranServer(finfo);
                    //组织交互协议串,然后发送    FILETRANS+发送者+目标用户+文件名+文件的长度+IP和端口号
                    String strSend = "FILETRANS|"+sender+"|"+sTarget+"|"+sFname+"|"+finfo.length()+"|"+IPandPort;
                    ClientMG.getClientMG().getClientChat().sendMSG(strSend);
                }
            }
            
        }
    }
}
package rjxy.lkl.Client;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JOptionPane;

public class ClientMG {
    

    //实现管理类的单例化
    private static final ClientMG clientMG = new ClientMG();
    public ClientMG() {
    }
    public static ClientMG getClientMG(){
        return clientMG;
    }
    //操作图形化界面
    ClientForm cWin;
    ClientChat cChat;
    public void setClientForm(ClientForm cf){
        cWin = cf;
    }
//    设置界面中的消息记录
    public void setLog(String str){
        cWin.txtLog.append(str+"\\r\\n");
    }
    public ClientChat getClientChat(){
        return cChat;
    }
    //新上线的用户添加到JList中
    public void addItem(String username){
        cWin.items.addElement(username);
    }
    public void addItems(String [] sItems){
        for (String username : sItems) {
            addItem(username);
        }
    }
    //一旦断开连接,清空JList中的用户
    //清空单个
    public void removeItem(String str){
        cWin.items.removeElement(str);
    }
    //清空所有
    public void clearItems(){
        cWin.items.clear();
    }
    public void connect(String IP,int port,String Username){
        try {
            Socket socket = new Socket(IP,port);
            ClientMG.getClientMG().setLog("已连接到服务器 ");
            cChat = new ClientChat(socket,Username);
            cChat.start();
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
    FileListener filelistener;
    //创建文件传输的服务器  
    public String CreateFileTranServer(File file){
        try {
            ServerSocket server = new ServerSocket(8881);
            String IP = InetAddress.getLocalHost().getHostAddress();
            filelistener = new FileListener(server,file);
            filelistener.start();
            return IP+"|"+8881;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
        
    }
    //接收文件
    public void recFile(String sender,String fName,int len,String IP,int port){
        //文件名|文件长度|IP|Port
        //4判断是否要接收?  JOptionPane->confirm
        //5同意接收,连接服务器进行文件传输
        //6拒绝接收,向服务器发回拒接接收的消息:FILECANCEL|sendername|recname
        
        JOptionPane msg = new JOptionPane();
        int result = msg.showConfirmDialog(null, sender+"发送文件【"+fName+"】,\\r\\n是否接收?", "文件传输确认", JOptionPane.YES_NO_OPTION);
        if(result == JOptionPane.YES_NO_OPTION){
            //确认接收
            try {
                Socket socket = new Socket(IP,port);
                new fileRec(socket, fName, len).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
            
        } else{
            //如果拒收   FILECANCEL|FromUser|toUser,sender给我发消息,某某某拒绝了,就通知sender,某某某拒绝了你的文件传输
            String strSend = "FILECANCEL|"+this.getClientChat().UserName+"|"+sender;
            this.getClientChat().sendMSG(strSend);
            this.setLog("您已拒接了"+sender+"的文件传输");
        }
        
    }
    //对方取消文件传递
    public void cancelFileTrans(){
        filelistener.cancelFileTrans();
//        this.setLog("对方取消了文件传输!!!");
    }
    
}
package rjxy.lkl.Client;

import java.io.*;
import java.net.*;

public class FileListener extends Thread {
    ServerSocket fserver;
    File filetrans;
    public FileListener(ServerSocket fserver, File filetrans) {
        this.fserver = fserver;
        this.filetrans = filetrans;
    }
    //取消传输
    public void cancelFileTrans(){
        if(fserver!=null){
            try {
                fserver.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    public void run() {
        Socket sfile = null;
        DataInputStream dis = null;
        DataOutputStream dos = null;
        try {
            sfile = fserver.accept();
            dis = new DataInputStream(new FileInputStream(filetrans));
            dos = new DataOutputStream(sfile.getOutputStream());
            
            //有关进度条属性的设置
            ClientMG.getClientMG().cWin.FileprogressBar.setValue(0);
            ClientMG.getClientMG().cWin.FileprogressBar.setVisible(true);
            ClientMG.getClientMG().cWin.FileprogressBar.setMaximum((int)filetrans.length());
            
            
            byte[] buff = new byte[1024];
            int iread = 0;
            int len =0;
            while((iread=dis.read(buff, 0, 1024))!=-1){
                dos.write(buff, 0, iread);  //写文件是,是根据读出的大小进行写入的
                len += iread;
                dos.flush();
                
//                写入流的同时,设置进度条的进度
                ClientMG.getClientMG().cWin.FileprogressBar.setValue(len);
            }
            ClientMG.getClientMG().setLog("文件传输完毕!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                dos.close();
                dis.close();
                if(sfile!=null){sfile.close();}
                if(fserver!=null){fserver.close();}
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package rjxy.lkl.Client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.*;

public class fileRec extends Thread{
    Socket socket;
    String sRecFilePath="D:\\\\";
    String fileName;
    int fileLen;
    public fileRec(Socket socket, String fileName, int fileLen) {
        this.socket = socket;
        this.fileName = fileName;
        this.fileLen = fileLen;
    }
    
    public void run() {
        DataInputStream dis = null;
        DataOutputStream dos = null;
        
        //设置进度条属性
        ClientMG.getClientMG().cWin.FileprogressBar.setValue(0);
        ClientMG.getClientMG().cWin.FileprogressBar.setVisible(true);
        ClientMG.getClientMG().cWin.FileprogressBar.setMaximum(fileLen);
        try {
            dis = new DataInputStream(socket.getInputStream());
            dos = new DataOutputStream(new FileOutputStream(sRecFilePath+fileName));
            
            byte[] buff = new byte[1024];
            int iread = 0;
            int len = 0;
            while((iread=dis.read(buff, 0, 1024))!=-1){
                dos.write(buff, 0, iread);
                len +=iread;
                ClientMG.getClientMG().cWin.FileprogressBar.setValue(len);
                dos.flush();
            }
            ClientMG.getClientMG().setLog("文件接收完毕!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                dis.close();
                dos.close();
                if(socket!=null){ socket.close();}
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
}

服务器端代码:

package rjxy.lkl.Server;

import java.awt.BorderLayout;

public class ServerForm extends JFrame {

    private JPanel contentPane;
    public JPanel panel;
    public JLabel label;
    public JTextField txtPort;
    public JButton btnStart;
    public JButton btnStop;
    public JScrollPane scrollPane;
    public JTextArea txtLog;
    
    
    ServerSocket server;
    ServerListener listener;
    volatile boolean serverFlag;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    ServerForm frame = new ServerForm();
                    frame.setVisible(true);
                    SocketMG.getsocketMG().setServerForm(frame); //窗体对象传入SocketMG中
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public ServerForm() {
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 422, 520);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        
        panel = new JPanel();
        panel.setBorder(new TitledBorder(null, "\\u914D\\u7F6E\\u4FE1\\u606F", TitledBorder.LEADING, TitledBorder.TOP, C++语言实现网络聊天程序的设计与实现(基于TCP/IP协议的SOCKET编程)超详细(代码+解析)

仿QQ局域网聊天软件

hello/hi的简单的网络聊天程序

Java中的UDP应用

使用python实现一个hello/hi的简单的网络聊天程序

网络协议TFTP