用Java实现基于Socket的网络编程

前言

这几天做计算机网络和数据库的课设,有一段时间没写博客了。今天写一篇用Java实现的聊天室,是之前做的计算机网络的作业,使用TCP协议,可以进行一对一、一对多的聊天,不过比较简陋,没有用户界面,输入输出都是在控制台。

什么是Socket

既然需要使用到Socket,那么首先得明白Socket是个什么东西。我们先扯远一点说,从计算机网络体系结构开始。
计算机网络体系结构
我们都知道大名鼎鼎的OSI七层协议体系并不实用,真正被广泛使用的是TCP/IP的四层协议,而五层协议是OSI和TCP/IP的综合体,只是为方便介绍计算机网络的原理才使用的。不管是几层的协议,在经过一通操作到达运输层时,数据已经可以从源主机传到目的主机了。

但是实际上,真正需要进行通信的是两台主机中的某两个进程。现在虽然已经将数据送到了目标主机,却无法确定需要交付给主机内的哪个进程。就像是信已经寄到小区了,却不知道送到哪一家。这也就是运输层协议要搞定的问题——怎样确定是哪个进程、以怎样的方式来传输(TCP/UDP)。

为了解决第一个问题,在运输层中使用协议端口号来标志本计算机中各个进程同运输层交互时的接口。通俗地说,就是给每个家(进程)分配一个门牌号(端口号)。现在通过端口号就可以找到对应进程了。这说明,当我们需要给某台计算机中的某个进程发送数据时,除了要知道对方的IP地址,还需要知道对方的端口号。
运输层的功能

现在再问:什么是Socket?在课本的定义中是:端口号拼接到IP地址即构成了套接字(Socket)。Socket的表示方法就是,在点分十进制的IP地址后面写上端口号,例如(127.0.0.1:8888)。

当然,Socket并不是只有这一个意思。现在我们说“利用Socket实现网络编程”,不是说用(127.0.0.1:8888)这么一串数字就可以了,而是在说使用系统提供的 socket API来进行网络编程。这个socket API是应用层与TCP/IP协议族通信时的中间软件抽象层。它是一组接口,把TCP/TP协议族隐藏在接口之后,对于我们用户而言,只需要按需求去调用接口就行了。
Socket抽象层所在的位置

为什么要使用Socket

我们先看没有Socket时的状态
各协议所处层次
对比上一张图,可以发现在没有Socket时,用户进程想使用不同的协议来通信,就要用到不同的接口,自己来实现各个协议的细节,这样无疑很麻烦。当有了Socket之后,我们可以不用管这些细节,直接调用Socket提供的接口就行了。

这有点像Java给我们提供了一个sort方法进行排序,我们就可以不用管什么快速排序、插入排序、归并排序等等等这些怎么实现,也不用管什么时候用哪个排序更高效,只需要调用方法就好了。

所以说,使用Socket是为了编程更方便。

Socket的系统调用(以C/S模式使用TCP协议为例)

C/S模式有客户端和服务器端之分,不同端所调用的方法是不一样的,大致流程如下:
Socket的系统调用

服务器端

  • 使用socket()函数创建一个socket描述符,用于唯一标识一个socket
    (此时,这个socket的IP地址和端口号都是空的)
  • 使用bind()函数指明该socket的本地地址(包括IP地址和端口号)
  • 使用listen()函数监听是否有客户端传来的connect()请求
  • 服务器监听到客户端传来的connect()请求时,调用accept()函数接收请求
  • 调用网络I/O来进行读写操作,用于读取、发送通信数据。网络I/O有很多组:read()/write()、
    recv()/send()、readv()/writev()、recvmsg()/sendmsg()、recvfrom()/sendto(),不同的在实现上有差别,任选一组即可。
  • 当操作完成后,使用close()函数来关闭这个socket

客户端

  • 使用socket()函数创建一个socket描述符,用于唯一标识一个socket
  • 客户端可以不调用bind()函数,这时候操作系统会自动分配一个动态端口号,通信结束后收回
  • 使用connect()函数,指明服务器的socket,然后发起与服务器的连接请求
    (所谓三次握手就是在这个过程中进行的)
  • 调用网络I/O来进行读写操作,用于读取、发送通信数据
  • 当操作完成后,使用close()函数来关闭这个socket
    (所谓四次握手就是在这个过程中进行的)

Java中Socket的使用

在JAVA中,有基于TCP协议的服务器端基础类ServerSocket和客户端基础类Socket。也有基于UDP协议的DatagramSocket类。这里主要讲TCP协议。

ServerSocket类是java执行服务器端操作的基础类,该类运行于服务器,监听入栈TCP连接,每个socket服务器监听服务器的某个端口,当远程主机的客户端尝试连接此端口时,服务器就被唤醒,并返回一个表示两台主机之间socket的正常的Socket对象。

Socket类是java执行客户端TCP操作的基础类,这个类本身使用代码通过主机操作系统的本地TCP栈进行通信。Socket类的方法会建立和销毁连接,设置各种Socket选项。

Java中的Socket调用
之前说了一大堆,实际上要使用JAVA进行基本的网络通信很简单,只需要调用几个方法而已。可以发现,在整个过程中关于Socket的操作代码非常少,就几行。剩下的都是字节流、字符流的操作和业务逻辑的处理。

程序实现思路

程序使用的是TCP协议,TCP是面向连接的协议,也就是说,在通信开始之前,服务器与客户端需要先建立连接,通信过程中需要一直保持连接。如果说一对一的聊天指的是服务器与客户端的聊天,那无疑已经实现了。但是现在我希望客户端与客户端之间进行一对一的聊天,甚至一对多的聊天,又该怎么办呢?TCP不同于UDP,UDP支持一对一、一对多、多对一与多对多的交互通信,但TCP只能一对一;而客户端与客户端之间再单独建立连接显然很不可取。

好消息是,有服务器端的存在,可以让服务器来执行转发的任务:所有的客户端都向服务器端发送数据,服务器端解析到数据的目标客户端,然后再转发给这个目标客户端,无论目标端是一个还是多个。

这样,我们可以通过服务器来实现一对一、一对多的通信了。但新的问题是:客户端向服务器端发送的都是数据流,服务器怎么知道哪个是对全体的消息,哪个是私聊的消息,消息正文是什么,目标客户端又是谁呢?

所以说,我们还需要自己规定一套协议,来控制不同类型的格式。例如,在消息正文之前添加一段“+public+”来表示这是对全体的消息,服务器端进行解析之后,对不同类型的消息执行不同的操作。

现在,主要问题都解决了,只剩下具体的实现。下面是程序的流程图
流程图-服务器端
流程图-客户端
需要注意的是,由于服务器端需要一直处于阻塞状态等待客户端连接,每当有客户端来连接时,只能开启子线程来处理与这个客户端的连接(每来一个客户端,就会开启一个子线程)。而用户端需要不断从终端(也就是控制台)接收输入,然后发送给服务器端,也需要一直处于阻塞状态;所以也要开启一个子线程来处理服务器端发送的数据(当连接成功后,就开启这个子线程)

源代码

由于类有点多,就不一个个复制粘贴了,上链接
CSDN下载
腾讯微云
运行截图:
服务器端
客户端-1
客户端-2
客户端-3

参考文献

socket工作原理深入分析
socket通讯原理及例程(一看就懂)
《计算机网络(第七版)谢希仁著》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章