想要设计实现通信功能。需要掌握以下知识:
网络通信(计网),多线程。
需要实现以下功能:
- 将传输的数据(文字,数字,声音,图片,视频……)编码,转换成可以通信传输的数据。
- 实现A和B(至少是双方)的通信。
<AB实质是两个进程,可以是通过公网ip相互访问连接的两个机器运行的两个进程;可以是同一局域网下的两台机器运行的两个进程;可以是一台机器跑在本机和虚拟机的两个进程;可以是同一个机器运行的两个进程>
通信模式(功能2)
CS结构(目前只学习这一个结构,后面再补充同级的结构)
我认为这是一种有关通信的模式结构,但是查资料讲是一种软件系统的体系结构。先不管外部逻辑,先关注本身细节。
- 定义:cs代表的是client-server,客户端一般认为是发出请求的一端,而server服务器端是满足客户端请求的一端,二者可以互相通信,互发数据。
- 目前使用cs结构的典型场景:网站(浏览器),游戏,数据库,qq微信等通讯工具。
- 实现cs结构:
认识一个结构:
套接字(socket)
1.将通信双方连接在一起的一个结构,存在方式为一对儿。
2.一对包含:
ServerSocket:适用于服务器端的socket 。该结构对象创建的时候只需指明端口号,ip为运行代码的机器的ip。(ip和端口常常是固定不变的方便多个客户端进行访问)
Socket:适用于客户端的socket。该结构创建对象时候需要指明其申请访问的服务器的ip和端口,运行这个程序的进程的端口是机器自行分配的。
代码实现:
需要分别为客户端和服务器端分别写一个类,然后实现对应功能,当两个机器分别跑这个两个程序的时候就实现了进程之间通信。
- 服务器端(server):
- 创建服务器端的socket对象:serverSocket。
- 实现其连接客户端的方法:
- 为刚刚创建的对象申请空间。调用构造函数需要传递服务器的端口号。(服务器端的端口是自己决定的)
- 通过serverSocket的accept()方法,连接到申请访问该服务器的客户端。并且创建一个客户端socket并返回。
- 利用这个返回的客户端socket获取输入输出io,实现服务器端向客户端发送或者接收数据。
读写数据:
首先拿到读写io流 ,服务器端通过监听返回的客户端socket的getInputStream或者getOutputStream方法来获取。
客户端通过 获取。
InputStream:输入流,对应读方法,对应接收数据。
is.read()返回读出的数据,每次读取一个字节,读完之后的内容就没有了紧接着读取下一个字节。当彻底读完之后返回-1。
因为每次只能读出一部分,所以要循环读取,然后判断是否读空,再结束。
OutputStream:输出流,对应写方法,对应发送数据。
os.write(写入缓冲区的数据,byte类型,一次写入一个字节)。
ps:整个写入读出的过程遵循队列特点先进先出。
- 客户端(client):
- 创建客户端的socket对象。
- 实现其访问服务器端的方法:
- 为客户端socket开辟空间,用构造函数需要传入要申请访问的服务器的ip和端口port。
- 发送或接收数据。
代码:见末尾工程链接。
代码遇到的问题:
可以尝试实现一个客户端,一个服务器端交流;多个客户端,一个服务器端。交流分为单次交流或者可以持续不断的交流。
-
当在实现多个客户端对一个服务器端,只发生一次交流的时候会出现一个特殊的问题:
现在指定客户端读取数据,服务器端写数据。
server:
服务器端先与多个客户端进行连接,然后向多个客户端发送数据。
client:
每个客户端与服务器端建立连接,然后接收服务器端发送的数据。(并向其回信。)在客户端的执行main函数中循环创建多个客户端,每一个执行上面的方法。
下面让server先执行,然后其执行到accept,因为没有client请求访问,因此其检测不到进入休眠,下面开始运行client进程,请求访问,因为要读取server发送的数据,但是server还没有发送,因此进入休眠;于是server进程又开始执行,这次检测到了第一个client的访问,然后想要检测第二个client的访问,检测不到就休眠;第一个client一直在等server给他发消息,但是因为server在等下一个client的连接,没有发送数据,于是他也等着,堵着一直轮不到第二个client执行,于是双方僵持。
解决办法:将建立连接和发送数据写到一个循环内,只要建立了连接就发送数据。让第一个client执行完。然后顺利执行第二个client。
改完之后可以发现client可以读到消息了,但是第二个client并没有按照预期建立。
因为server发送完数据之后没有关闭数据流,client读取的时候就无法检测到读取完不会检测到返回-1,一直陷入while循环中,因此第一个client的方法永远无法执行完。
这时就会造成只能创建一个client的情况。但是当有多个server进程执行的时候可以做到分别接收一个client然后向其分别发送信息,client接收信息。
于是在写操作的执行完毕之后加上io流的刷新和关闭。就可以成功实现一个server,多个client交流一次。
-
紧接着我还尝试了多个client收到消息后向server回复消息的情况:
发现因为之前server写完之后关闭了io流,client想要再次向server发送数据的时候,server那边就会报Socket is closed。
应该需要多线程解决。待解决。
-
当只有一个server,一个client的时候就可以实现进行多次交流。
传输数据包装
传输的时候以字节为单位,每次传输1个字节。当想要传输int,string,图片,直线,视频……等等特殊大小的结构时,需要自行单独设计传输类来实现不同特殊数据类型的读写。
无论哪一种结构,基本思路都是:先将写入的数据转换成字节数组写进去,读出的时候,将字节数组读出转换成需要的数据类型。
需要注意的是注意读写顺序以及每个数据类型与byte转换大小关系。(等下填写具体实现方法)
-
int(4byte)
send:
将要发送的数据拆成4个byte发送。从高到低依次发送,分别右移24,16,8,0位,然后&0xff,就取到了4个字节。分别os.write(int x0)。因为只想单纯的取到这8位的数字,不考虑正负等,即没有符号位,所以用int类型保存取出的一个字节,相当于只用int的低8位。
receive:
连续取出4个字节,先取出的是高位,取出后分别左移24,16,8位,然后加起来就是之前要传输的int。
-
str(每个字符2个byte)
send:
约定第一个字节发送str的长度。后面是string转换成对应的byte数组。先发长度,再发byte数组。
receive:
先读出长度,根据长度建立一个对应大小的byte数组。然后直接用is.read(byte[])方法,将数据一次性读到byte数组里。
-
Line
send:
设计Line类,包含4个整数,x1,y1,x2,y2。然后分别用传入的Line对象的get方法得到4个坐标,分别调用int的读写方法,发送信息。
receive:
用int的收信息方法收四次之后,利用构造方法创建Line对象,返回。
-
Color
send:
传入Color对象,获取其rgb值,然后调用int方法传输rgb的数字。
receive:
用int的的读数据方法,读出rgb,然后根据读出的数字构造颜色对象并返回。
-
Picture(还没尝试过,待测试)
send:
传入picture对象,通过imagebuffer将其转换成二维数组,每个像素点是一个color对象。调用传输color方法依次传输。
receive:
用color的的读数据方法,读出二维矩阵,然后根据构造方法将其转化为imagebuffer对象。