Context
这个故事从三年前开始说起.
那时老师让我搞Android的一个防伪项目(精益防伪),就是那个用Android的NFC功能来鉴别一个酒是否是假冒伪劣的,也是那个项目让我认识了卢师兄(在华为可厉害了呢).
那时的我只用过http来进行网络请求,没有用过socket来进行数据交互.
由于项目需要,那时候就临时在Android上搞了一波socket通信.
第一次做Android就要碰Socket还是蛮困难的(虽然我已经搞了两年iOS,但是iOS和Android毕竟还是不一样的,而且我还是个孩子),因为界面没做过,java也不是很熟悉,甚至连Android的手机都没有用过. 由于时间比较紧张,所以界面就请望神帮我画了,我去写了数据处理和Socket通信. 那也是第一次我知道了Android上面有很严格的线程限制. 在iOS上的主线程中调用网络,会堵塞,卡在那儿,等到请求完了,就会恢复;不过在android上面,主线程碰一下网络竟然就异常了~(真凶)
Socket
Socket其实不应该和http分开说,因为http只是socket的一种扩展罢了,要进行网络通信,总是离不开Socket的.http只不过是在socket上面进行了很多复杂的封装罢了(很复杂).
socket,翻译过来就是,插座.打个比方就是,我要和你收发数据,就把数据线插到你的插孔里面,然后我们就在这根线里面交流.一个网络节点可以有很多的插座孔,所以也就可以同时和很多很多人一起交流信息.而http是插上了插座,发一句话,收一句话,然后就把插头拔掉了(连接结束了)好处就是服务器和客户端不用一直维护一个连接,缺点嘛,这是一个短连接,下一次想要进行网络请求还需要把TCP的握手流程走一遍,而且是单向操作(只能客户端找服务器麻烦,服务器永远无法主动找客户端麻烦)
Now
三年前的旧事讲完了,该说说这次的故事了
这次我们的产品中会涉及用到socket传输文件,这比三年前那个要求更高了一些,因为文件还是蛮大的,之前每次的数据也就十几个字节(128位)(NFC标签儿本来也存不了多少内容)
Server
先从服务器开始说起吧!由于这个socket传输只会一对一的传输,所以这个服务器根根本本不用考虑并发和异步什么的东西,所以
就用Python做个简单的socket服务器吧!
code
import threading
import SocketServer
import datetime
import time
BUFFER_SIZE = 4096
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
t = None
wantDisconnect = False
def handle(self):
if self.t is None:
self.t = datetime.datetime.now()
time_interval = 0
else:
time_interval = (datetime.datetime.now() - self.t).seconds
while time_interval < 60 and not self.wantDisconnect:
data = self.request.recv(BUFFER_SIZE)
if data:
self.t = datetime.datetime.now()
print "prepare to send file"
f = open(str(data), 'rb')
l = f.read(BUFFER_SIZE)
while l:
self.request.send(l)
print("sending (%x)", l)
l = f.read(BUFFER_SIZE)
time.sleep(1/20)
time.sleep(1)
self.request.sendall("\nsending ok")
f.close()
else:
print threading.currentThread().name, " no data"
break
def setup(self):
print threading.currentThread(), " start"
self.t = datetime.datetime.now()
def finish(self):
self.wantDisconnect = True
print threading.currentThread(), " end"
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
HOST, PORT = "0.0.0.0", 55555
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
server.serve_forever()
我直接在服务器的文件目录下放了几个图片文件用来做测试的~
这差不多基本上是直接从Python的SocketServer文档中抄来的
一个多线程的socket服务器就是这么方便
其中setup()
和finish()
没什么太大的意思,反正就是又一个socket连接进来了和对方主动断开后会做的事情
关键的操作都在handle中!
先贴一下官方的解释:
handle()
This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.
The type of self.request is different for datagram or stream services. For stream services, self.request is a socket object; for datagram services, self.request is a pair of string and socket.
就是说有请求进来了,就会在这里中断
,aka “回调”(但我觉得用中断好像更加形象生动)
self.request将获取到socket实例,有了这个socket实例我们就可以读读写写了
这里的代码是传入的数据代表的是文件,服务器根据文件名,把数据回传回去,就是这么简单,看看效果图
密集恐惧症了吧?哈哈哈~在服务器把发送的数据打印出来,可以方便调试,(肉眼)检查数据有没有收完收对……
等下,time.sleep(1/20)
这个东西有啥子用处?
我担心发送的太快接收端粘包,所以就稍微给一点点儿延迟,缓解一下压力,不过实测下来貌似是我多虑了~
iOS
以我的尿性,客户端必然是从iOS开始搞的~
目标很简单,连接服务器,发个文件名,一个劲的收数据,最后显示效果
略~
Doing
多年前在CocoaPods上面看到了CocoaAsyncSocket,今天终于要动手搞一搞了!好激动
一般大家在github的README.md里面都要把自己的库的用法好好的解释一遍,让访问者眼前一亮,然后送出Star,不过这个库真牛逼,README.md里面装了整整一页的逼!
然后我去wiki里面看,竟然还给了好多个链接,一时半会儿还找不到他的使用方法,不过我看了CommonPitfalls这个板块,我觉得写的很屌,如果我前面的那些关于socket的废话你没看懂的话,可以去这个链接里面再看看.里面讲了不少误区,坑,雷……
好吧,其实它真正的用法在Intro_GCDAsyncSocket里面
教了连接动作,连接结果,读操作,写操作
贴代码
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
socket=[[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error;
buf=[NSMutableData data];
[socket connectToHost:@"192.168.199.131" onPort:55555 error:&error];
}
-(IBAction)send_action:(id)sender{
[socket writeData:[self.commandField.text dataUsingEncoding:NSASCIIStringEncoding] withTimeout:5.f tag:12345];
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
NSLog(@"socket is connected");
[self.sendButton setEnabled:YES];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
NSLog(@"[%ld]Did receive Completely %@,(%ld)",tag,data,data.length);
self.console.text=[NSString stringWithFormat:@"%@\n%@",data,self.console.text];
UIImage *image=[UIImage imageWithData:data];
self.imageView.image=image;
}
- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag{
NSLog(@"[%ld]receive (%lu)*1024 bytes",tag,partialLength);
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
if (tag==12345){
[sock readDataToData:[@"\nsending ok" dataUsingEncoding:NSASCIIStringEncoding] withTimeout:60.f tag:119];
}
}
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock{
NSLog(@"socket closed");
[self.sendButton setEnabled:NO];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err
{
NSLog(@"socket disconnect");
NSLog(@"buf is %@",buf);
[self.sendButton setEnabled:NO];
}
这代码好像蛮长的~
其中有partial的回调方法都是接收不完整的时候会做的事儿
Socket本来就是全双工的,就是可以一边儿发送一边儿接收,不过这现在给我做成了个半双工的了,所以我在读取的时候设置了一个结束条件\nsending ok
,接收到了这个的时候,就会停止.
Android
android上面我想装逼,不用框架,直接用socket来撸,写的还是蛮痛苦的
先只贴代码,先不做解释了,因为,写的不是特别好……等到优化好了,再写一篇新的,好好解释一番~
先搞个center
public class SocketCenter {
SocketEntity socket;
Handler handler = new Handler();
Thread socket_thread;
Handler network_handler;
public void setup() {
EventBus.getDefault().register(this);
socket_thread=new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
network_handler=new Handler();
Looper.loop();
}
});
socket_thread.start();
}
public void connect_server() {
network_handler.post(new Runnable() {
@Override
public void run() {
socket = new SocketEntity("192.168.199.131", 55555, handler);
socket.connect();
}
});
}
public void disconnect() {
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(SocketEvent event) {
if (event.getName().equalsIgnoreCase(EventConstant.Socket_Connected)) {
network_handler.post(new Runnable() {
@Override
public void run() {
socket.ask_for_file("img1.jpg", "\nsending ok");
}
});
}
}
}
再把实例搞出来
public class SocketEntity {
private Socket socket;
String address;
int port;
BufferedReader reader;
BufferedWriter writer;
byte[] temp_data;
ArrayList<byte[]> received_queue;
WeakReference<Handler> handler_reference;
Thread async_thread;
SocketEntity(String address, int port, Handler handler) {
socket = new Socket();
handler_reference = new WeakReference<Handler>(handler);
this.address = address;
this.port = port;
received_queue = new ArrayList<>();
async_thread = new Thread();
}
Socket getSocket() {
return socket;
}
void connect() {
Log.e("SocketUtil", "Start Connect");
if (socket != null) {
if (!socket.isConnected()) {
try {
socket.connect(new InetSocketAddress(address, port), 5000);
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
SocketEvent event = SocketEvent.connectEventInstance();
EventBus.getDefault().post(event);
} catch (IOException e) {
e.printStackTrace();
SocketEvent event = SocketEvent.failBaseEventInstance(-200);
EventBus.getDefault().post(event);
writer = null;
reader = null;
}
}
}
}
void disconnect() {
if (socket != null) {
if (socket.isConnected()) {
try {
socket.close();
writer.close();
reader.close();
writer = null;
reader = null;
} catch (IOException e) {
e.printStackTrace();
SocketEvent event = SocketEvent.failBaseEventInstance(-400);
EventBus.getDefault().post(event);
if (!socket.isConnected()) {
writer = null;
reader = null;
}
}
}
}
}
void ask_for_file(String filename, String endPoint) {
send_command(filename);
// receive_data_until(endPoint.getBytes());
receive_data(endPoint.getBytes());
}
private void receive_data(byte[] endpoint) {
try {
char[] buf = new char[1024];
int total = 0;
boolean isEnd = false;
String s = "";
while (!isEnd) {
int length = reader.read(buf, 0, 1024);
isEnd = (length == -1);
if (length > 0) {
char[] valid_data = Arrays.copyOfRange(buf, 0, length);
String string = String.valueOf(valid_data);
s=s+string;
isEnd = string.contains(new String(endpoint));
if (!isEnd) {
total += length;
} else {
total += length;
total -= endpoint.length;
}
Log.e("Socket_Received", String.valueOf(HexUtil.encodeHex(string.getBytes())));
Log.e("String is ", string);
}
}
Log.e("Socket_Received", "Total Size is " + total + " bytes");
// temp_data=s.getBytes();
temp_data=Arrays.copyOfRange(s.getBytes(),0,total);
Log.e("Socket_Received", "Total binary Size is " + temp_data.length + " bytes");
SocketEvent event= SocketEvent.fileReceivedEventInstance(temp_data);
EventBus.getDefault().post(event);
} catch (IOException e) {
e.printStackTrace();
}
}
private void receive_data_until(byte[] ends) {
try {
byte[] temp = null;
byte[] temp_buffer = null;
while (ends != temp) {
String line = reader.readLine();
byte[] bytes = line.getBytes();
Log.e("Socket_Received", String.valueOf(HexUtil.encodeHex(bytes)));
int length = ends.length;
if (bytes.length >= length) {
temp = Arrays.copyOfRange(bytes, (bytes.length - length), bytes.length);
temp_buffer = line.getBytes();
} else if (temp_buffer != null) {
int rear = bytes.length;
int front = ends.length - rear;
if (temp_buffer.length > front) {
temp = new byte[ends.length];
for (int i = 0; i < front; i++) {
temp[i] = temp_buffer[temp_buffer.length - front + i];
}
for (int i = 0; i < rear; i++) {
temp[front + i] = bytes[i];
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void send_command(byte[] command) {
String cmd = Arrays.toString(command);
send_command(cmd);
}
private void send_command(String command) {
if (socket != null && socket.isConnected()) {
try {
Log.e("SocketUtil", "Send " + command);
writer.write(command);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
SocketEvent event = SocketEvent.failBaseEventInstance(-1);
EventBus.getDefault().post(event);
}
}
}
}
这里面有很多的问题,不过还是成功的做到了连接,发送指令,接收数据,由于最后BitmapFactory.decodeByteArray
存在一些问题,估计和接收的数据有关系,所以没有效果图……(难过)
End
先写这么多了,等我下次优化完了,说不定就会有更深入的理解,更好的代码,更加出色的性能,到时候再贴出来留作纪念!