【JAVA课设】-- Socket实现实时通信

最近学期末了,Java要求做一个课程设计,需要用Java网络编程、JDBC、SWT进行设计。研究了 很久之后终于在各路大佬的指引下成功完工。

根据在计网学习的知识,通信大致分为两种,用Socket,ServerSocket类实现的TCP服务,和用DatagramSocket类实现的UDP通信服务。

详细代码使用可以参考这位大佬的文章。Java 网络编程_ZaynFox的博客-CSDN博客_java网络编程

一、TCP 与Socket ServerSocket

首先来说一下如何使用Socket,ServerSocket类实现的TCP服务。实现原理如下面这位大佬的图。

Socket实现即时通讯服务(一)_一只菜鸟.....的博客-CSDN博客_socket实时通信

 从这里我们可以很清晰的看出来分为两个步骤。

第一步是监听,也就是accept()之前的内容

第二步是收发数据。是在建立连接之后的内容。

1.1 Socket 类建立通信的原理

首先我们来讲第一步——监听,Socket类是如何建立通信的。

Socket的通信方式类似于传统的C/S模式,首先Client客户端要创建一个Socket类的对象,通过构造函数Socket(String 发送IP,int 发送进程占用的端口号)生成一个Socket对象。

然后客户机创建一个ServerSocket类的对象,通过构造函数ServerSocket(int 接收占用的端口号)。并且使用serverSocket.accept()方法进入“接听阻塞”状态。

Socket在使用构造函数创建对象的同时,会占用发送端口和ServerSocket尝试进行TCP连接。如果创建Socket时使用的端口,恰好有ServerSocket在监听,就可以建立连接。

建立连接的方式是,通过serverSocket.accept()返回一个Socket对象。

//Client 端
Socket socket = new Socket("127.0.0.1",1314);

//Server 端
ServerSocket serverSocket = new ServerSocket(1314);//与上面的端口要一致。
Socket socket2 = serverSocket.accept();

到此连接部分就成功了,接下来我们就进入了第二步。如何通过serverSocket.accept()返回的Socket对象进行消息的接收发送。

1.2 Socket 收发消息— —输入输出流

1.2.1 输出数据 — — socket.getOutputStream()

//输出数据 拿过话筒

/*
 * 连接好的socket获取输出流获取的是一个话筒, 返回值是OutputStream 
 * 但是DataOutputStream用的更多,所以类型转化一下
 */
OutputStream outToServer = null;
System.out.println("接收输出流,此时" + socket);
outToServer = socket.getOutputStream();

DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF(message); // 向流中写入数据
out.flush(); // 清空流

看过一个形象的说法,通过socket.getOutputStream()输出数据就像拿过了话筒一样。

socket.getOutputStream()返回的是一个OutputStream类型的输出流,通过实例化一个对象 outToServer接收Socket通信中的输出流,就可以得到“发言权”。就是直接在outToServer里面写数据就可以实现通信发数据的功能了。

非常的简单和神奇,这个流就好像C中指针一样,指向通信中的通信信道。

1.2.2 读入数据 — — socket.getInputStream()

String receiveMsg = "";
InputStream inFromServer = socket.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
receiveMsg = in.readUTF(); // 从输入流中读取数据

和接收数据一样,通过socket.getInputStream()可以得到一个输入流,通过输入流的读取函数我们就可以读出其中的字符,实现接收信息的功能。

二、线程类Thread 实现实时通信

看到这里你肯定觉得实现实时通信,至少是实现通信是一件非常简单的事情了。下面给你一串代码,我们来一起试试会有什么样的问题。

//Client 端
public class Client{


}
//Server 端
public class Server{



}
//主函数

public class TCPCommunication{

public void main(){
}
}

可以看出来,这个只能实现一对一的C/S通信(也就是只能一个人一直说话,另一个人只能听不能说)。并且还不能断开连接,毕竟我们确实没有设置让while(true)停止的语句。

此时比较聪明的读者就发下了,只要在同一台电脑上同时运行两种代码不就好了吗?他就可以实现发送和接收两种功能了。

上面的想法很好也是我一开始的想法,因此我们再放一段代码我们来一次看看。问题远不止于此。

可以看出来有 xxx 问题。

接下来我们加入我们的主角Thread类。

这样就解决了连接不能多次监听的问题。但是这样依然没有解决 ServerSocket.accept()阻塞的进程 以及 GetInputStraem带来的阻塞问题。

我们可以通过调用线程池来解决,通过最终解决了问题。

最终代码如下

github 链接:

gitee 链接:

百度云链接:

package NetCommunication;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import DATABASE.database;

public class Net {
    public Socket socket = null; // 全局的socket变量 用于表示当前对象所建立的唯一的连接
    ServerSocket serverSocket;
    /*
     * 流程就是首先打开页面的时候就创建一个ServerSocket 尝试接收
     * 返回一个Socket后就成功建立连接了 接收成功就断开ServerSocket连接
     */

    // 连接相关的各种标志
    public boolean LinkSuccessFlag = false;// 没连接成功的时候就是0

    // 端口和IP参数
    public int ReceivePort = 12101; // 所有主机的接收端口和发送端口用一样的。
    public int SendPort = 12101;

    // 存入数据库的相关信息
    public String myUserName;
    public String toUserName;

    // 设置一个set函数
    public void setmyUserName(String myUserName) {
        this.myUserName = myUserName;
    }

    public void settoUserName(String toUserName) {
        this.toUserName = toUserName;
    }


    // 创建一个发送方 发送只需要一个单独的函数方法不需要线程类
    public int createClient(String SendIP) {
        // 主动连接他人。连接成功时候将成功连接的Flag赋值为1
        // 参数都用外界传入
        if (LinkSuccessFlag == false) {// 还没连接成功
            System.out.println("还没被被动连接,现在开始主动连接" + SendIP);
            try {
                socket = new Socket(SendIP, SendPort);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
            LinkSuccessFlag = true;
            System.out.println("主动连接成功");
            // 创建成功返回1
            return 1;
        } else {

            System.out.println("已经被动连接成功了,不需要主动连接了");
            System.out.println("被动连接到:" + socket);
            return 1;
        }


    }

    // 创建一个接收方 监听需要放在线程里面,因为会线程阻塞。
    Thread TServer = new Server();

    public class Server extends Thread {
        public void run() {
            createServer();
        }
    }

    public void startServer() {
        TServer.start();
    }

    public void createServer() {
        if (LinkSuccessFlag == false) {// 还没连接成功 但是本项目使用过程中其实就直接在每个窗口打开的时候就进行连接了。
            if (serverSocket != null) {
                System.out.println("已经开始监听了,不需要在创建一个");
            } else {
                System.out.println("还没连接,现在开始被动监听,等待别人主动连接。监听端口是" + ReceivePort);
                try {
                    serverSocket = new ServerSocket(ReceivePort);
                    serverSocket.setSoTimeout(0);//
                    System.out.println("开始accept监听");
                    socket = serverSocket.accept();
                    System.out.println("被连上了" + socket);
                    LinkSuccessFlag = true;
                } catch (IOException e) {
                    // TODO 自动生成的 catch 块
                    e.printStackTrace();
                }

            }
        } else {
            System.out.println("已经连接成功了,不需要被动监听了");
            System.out.println("已经被动连接到了 socket:" + socket);
        }

    }

    // 关闭接收方
    public void endServer() {
        try {
            if (serverSocket != null) {
                serverSocket.close(); // 关闭监听

                System.out.println("进入endServer了,关闭的效果" + serverSocket);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*--------------------收发信息阶段 已经获取连接用的socket了-----------------------------------*/
    /*
     * 实现思路是通过连接的输入输出流进行通信。 断开通信的方式是使用标签不在允许通信
     */

    // 收发相关的标签
    public boolean AllowSRFlag = true;//允许收和发送信息

    public void sendString(String message) {
        if (AllowSRFlag == true) {

            try {
                /*
                 * 连接好的socket获取输出流获取的是一个话筒, 返回值是OutputStream
                 * 但是DataOutputStream用的更多,所以类型转化一下
                 */
                OutputStream outToServer = null;
                System.out.println("接收输出流,此时" + socket);
                outToServer = socket.getOutputStream();

                DataOutputStream out = new DataOutputStream(outToServer);
                out.writeUTF(message); // 向流中写入数据
                out.flush(); // 清空流

            } catch (IOException ioException) {
                ioException.printStackTrace();

            }
        } else {
            System.out.println("收发已经不被允许了,已经点过断开连接按钮辽");
        }
    }

    /* 终止函数 */
    public void endSendandReceive() {
        AllowSRFlag = false;
        TReceiveStr.interrupt();
    }

    // 接收需要用线程,因为有监听和接收socket的原因一样
    Thread TReceiveStr = new ReceiveString();

    public class ReceiveString extends Thread {
        public void run() {
            receiveString();
        }
    }

    /* 调用函数 */
    public void startReceiveString() {
        TReceiveStr.start();
    }

    public void receiveString() {
        /* 接收线程里面开一个定时任务 循环接收 */
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {

                System.out.println("调用到接收监听函数");
                if (AllowSRFlag == true) {
                    String receiveMsg = "没接收到";
                    try {
                        InputStream inFromServer = socket.getInputStream();
                        DataInputStream in = new DataInputStream(inFromServer);
                        receiveMsg = in.readUTF(); // 从输入流中读取数据
                        System.out.println("接收到对方发来" + receiveMsg);
                        /* 聊天记录插入数据库 */
                        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd  HH:mm:ss ");
                        Date date = new Date(System.currentTimeMillis());

                        String time = formatter.format(date);

                        System.out.println("输入数据库的内容为" + myUserName + toUserName + time + receiveMsg);
                        database insert = new database();
                        insert.InsertTalkRecord(myUserName, toUserName, time, receiveMsg);

                        // result = ""; //结果清空 便于下一次取新的数据
                    } catch (IOException e) {
                        receiveMsg = "接收出现异常,没接收到";
                        e.printStackTrace();
                    }
                } else {
                    return;
                }
            }

        };
        // 在指定延迟0毫秒后开始,随后地执行以2000毫秒间隔执行timerTask
        new Timer().schedule(timerTask, 0L, 2000L);
        // return receiveMsg;

    }
}

看完此文不知道你有没有一点收获呢?如果有的话,希望可以收获你一个免费的点赞~~。你的点赞和支持是我创作的最大动力。

 

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在C语言实现Socket通信需要使用Socket API,具体实现步骤如下: 1. 创建Socket 在客户端和服务器端都需要创建Socket。可以使用`socket()`函数创建Socket,该函数返回Socket的描述符(整数类型)。 ```c #include <sys/socket.h> int socket(int domain, int type, int protocol); ``` 其中,`domain`参数指定协议族,常用的有AF_INET(IPv4)和AF_INET6(IPv6);`type`参数指定Socket的类型,常用的有SOCK_STREAM(面向连接的Socket)和SOCK_DGRAM(无连接的Socket);`protocol`参数指定协议,常用的有IPPROTO_TCP(TCP协议)和IPPROTO_UDP(UDP协议)。 2. 绑定Socket 在服务器端需要将Socket与IP地址和端口绑定,使用`bind()`函数实现。 ```c #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ``` 其中,`sockfd`参数为Socket的描述符;`addr`参数为指向`sockaddr`结构体的指针,结构体中包含IP地址和端口信息;`addrlen`参数为`sockaddr`结构体的长度。 3. 监听连接请求 在服务器端需要监听客户端的连接请求,使用`listen()`函数实现。 ```c #include <sys/socket.h> int listen(int sockfd, int backlog); ``` 其中,`sockfd`参数为Socket的描述符;`backlog`参数为等待连接队列的最大长度。 4. 接受连接请求 在服务器端需要接受客户端的连接请求,使用`accept()`函数实现。 ```c #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); ``` 其中,`sockfd`参数为Socket的描述符;`addr`参数为指向`sockaddr`结构体的指针,用于存储客户端的IP地址和端口信息;`addrlen`参数为`sockaddr`结构体的长度。 5. 连接服务器 在客户端需要连接服务器,使用`connect()`函数实现。 ```c #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ``` 其中,`sockfd`参数为Socket的描述符;`addr`参数为指向`sockaddr`结构体的指针,结构体中包含服务器的IP地址和端口信息;`addrlen`参数为`sockaddr`结构体的长度。 6. 发送和接收数据 在客户端和服务器端都需要发送和接收数据,使用`send()`和`recv()`函数实现。 ```c #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ``` 其中,`sockfd`参数为Socket的描述符;`buf`参数为指向数据缓冲区的指针;`len`参数为数据缓冲区的长度;`flags`参数为标志位,常用的有MSG_WAITALL(等待所有数据到达)和MSG_DONTWAIT(非阻塞模式)。 7. 关闭Socket 在客户端和服务器端都需要关闭Socket,使用`close()`函数实现。 ```c #include <unistd.h> int close(int fd); ``` 其中,`fd`参数为Socket的描述符。 以上是Socket通信在C语言中的实现步骤。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值