Window与Android的socket通信_图片
这是我学习Android的第一个小项目,稍作记录,也希望大家看了能多帮我提出宝贵意见,共同交流进步。^_^
原型是基于C++的,只提供了最基本的socket连接功能。在此基础上,增加了以二进制流收发文件(.jpg)的方法,并且在client端显示收到的图片。
一. SOCKET连接
SERVER端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int CppServer::initialize() 2 { 3 int iResult = 0; 4 5 ListenSocket = INVALID_SOCKET; 6 ClientSocket = INVALID_SOCKET; 7 result = NULL; 8 rcvbuflen = DATA_BUFLEN - 1; 9 sendbuflen = DATA_BUFLEN - 1; 10 quit = false; 11 initialized = false; 12 13 memset(rcvbuf, 0x00, sizeof(rcvbuf)); 14 memset(sendbuf, 0x00, sizeof(sendbuf)); 15 16 // Initialize Winsock 17 iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 18 if (iResult != 0) { 19 printf("WSAStartup failed with error: %d\n", iResult); 20 return -1; 21 } 22 23 ZeroMemory(&hints, sizeof(hints)); 24 hints.ai_family = AF_INET; 25 hints.ai_socktype = SOCK_STREAM; 26 hints.ai_protocol = IPPROTO_TCP; 27 hints.ai_flags = AI_PASSIVE; 28 29 // Resolve the server address and port 30 iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); 31 if ( iResult != 0 ) { 32 printf("getaddrinfo failed with error: %d\n", iResult); 33 WSACleanup(); 34 return -1; 35 } 36 37 // Create a SOCKET for connecting to server 38 ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); 39 if (ListenSocket == INVALID_SOCKET) { 40 printf("socket failed with error: %ld\n", WSAGetLastError()); 41 freeaddrinfo(result); 42 WSACleanup(); 43 return -1; 44 } 45 46 // Setup the TCP listening socket 47 iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen); 48 if (iResult == SOCKET_ERROR) { 49 printf("bind failed with error: %d\n", WSAGetLastError()); 50 freeaddrinfo(result); 51 closesocket(ListenSocket); 52 WSACleanup(); 53 return -1; 54 } 55 56 freeaddrinfo(result); 57 58 iResult = listen(ListenSocket, SOMAXCONN); 59 if (iResult == SOCKET_ERROR) { 60 printf("listen failed with error: %d\n", WSAGetLastError()); 61 closesocket(ListenSocket); 62 WSACleanup(); 63 return -1; 64 } 65 66 // Accept a client socket 67 ClientSocket = accept(ListenSocket, NULL, NULL); 68 if (ClientSocket == INVALID_SOCKET) { 69 printf("accept failed with error: %d\n", WSAGetLastError()); 70 closesocket(ListenSocket); 71 WSACleanup(); 72 return -1; 73 } 74 printf("Client have connected.\n"); 75 76 initialized = true; 77 return 0; 78 }
这部分代码是C/C++ socket编程中流程化的部分,可以直接借鉴。其中的accept函数是阻塞的,当有client发来连接请求时,才往下执行。
CLIENT端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class SocketClient extends Activity { 2 private final String HOST = "10.0.2.2"; // Android虚拟机访问本地,不用127.0.0.1 3 private final int PORT = 9527; // 访问Server使用的端口号 4 protected void onCreate(Bundle savedInstanceState) { 5 new Thread() { 6 public void run() { 7 Socket s = null; 8 try { 9 // Connect to server 10 s = new Socket(HOST, PORT); 11 // 更多处理:接收发送图片,显示图片 12 } catch (UnknownHostException e) { 13 e.printStackTrace(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 } finally { 17 try { 18 s.close(); // 断开连接 19 } catch (IOException e) { 20 e.printStackTrace(); 21 } 22 } 23 } 24 }.start(); 25 } 26 27 // 更多私有方法:提供显示图片用到的Handler,判断收发终了标志等 28 }
以上是client端连接server socket的代码,核心只有
s = new Socket(HOST, PORT);
其他部分说明程序的结构。
※在Android 4 之后的版本中,不能在UI线程中使用连接socket这种阻塞式的方法,这将导致画面失去响应。因此这里在一个新启动的线程中发起socket连接。
二. SERVER端向CLIENT端发送图片
SERVER端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int comm::sendData(char *file) 2 { 3 int iResult = 0; 4 5 if(0 > _access(file, 0)) { 6 // If resource file don't exist, send its name to client. 7 iResult = send(ClientSocket, file, strlen(file), 0); 8 printf("--File %s is not accessable, check please.\n", file); 9 return -1; 10 } else { 11 FILE *rcFile = NULL; 12 13 Beep(500, 350); 14 // Open the resource file. 15 rcFile = fopen(file, "rb+"); 16 if(!rcFile) { 17 printf("Failed to open resource file: %s\n", file); 18 return -1; 19 } 20 21 // Send resource file with 1024 byte per piece. 22 memset(sendbuf, 0x00, sizeof(sendbuf)); 23 while(fread(sendbuf, sendbuflen, 1, rcFile) >= 0) { 24 if(SOCKET_ERROR == send(ClientSocket, sendbuf, sendbuflen, 0)) { 25 closesocket(ClientSocket); 26 WSACleanup(); 27 return -1; 28 } 29 if(feof(rcFile)) { 30 break; 31 } 32 } 33 fclose(rcFile); 34 35 // Send end mark to client. 36 memset(sendbuf, 0x00, sizeof(sendbuf)); 37 strncpy_s(sendbuf, ENDMARK, sendbuflen); 38 if(SOCKET_ERROR == send(ClientSocket, sendbuf, sendbuflen, 0)) { 39 closesocket(ClientSocket); 40 WSACleanup(); 41 return -1; 42 } 43 44 } 45 46 printf("--File %s have been sended successfully.\n", file); 47 return 0; 48 }
fread函数的原型是:
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ; Params: buffer // 存放从文件读取的内容,这里是char*类型 size // 读取内容元素的单位大小,单位是字节 count // 读取内容元素的数目(最大值) // 个人理解前三个参数的关系为:buffer.size >= size * count stream // 数据来源文件 Returns: // 实际读取内容元素的数目,返回值小于count说明到达文件末尾或者发生错误。我们的count==1,所以小于等于0时,为这种情况。
这个函数多次调用可以顺序读取stream指向的文件,直到文件末尾。使用feof函数判断是否到达文件结尾。
对于第一个参数void*到底选用什么类型,看到网上有朋友在这里定义了结构体,用结构体封装文件的每个片段,这种做法的好处是添加一些标记字段。这种做法虽然很标准,但是并不适合这个project。
在SERVER端有封装操作,CLIENT端必须有对应的解析操作。而我们都知道C/C++中,即使是基本数据类型,也有宽度和编译器有关系的,比如long,这无疑增加了用java解析数据包的难度,还要考虑到内存是否对齐等等问题,相当复杂且容易出错。因此这里使用简单的char来作为发送的buffer类型,它与java的byte类型具有相同且固定的宽度,方便数据解析(事实上这种情况已经简单到不需要解析了),对于一些必要的标记,比如终了标记OVER,单独发送一次,在client端做判断即可。
CLIENT端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // Receive picture 2 private void rcvData() { 3 try { 4 // DataInputStream提供的read方法很适合读取byte[]类型数据 5 sis = new DataInputStream(new BufferedInputStream(s.getInputStream())); 6 // 打开Android内部存储空间的文件,必须使用openFileInput/openFileOutput 7 // 可以直接返回FileInputStream/FileOutputStream,它们提供的read/write方法都对byte[]很友好 8 fos = openFileOutput(tempFileName, MODE_PRIVATE); 9 10 while (true) { 11 Arrays.fill(buf,(byte) 0); 12 int readnum = sis.read(buf, 0, buflen); 13 if (ifEnd(buf) || 0>readnum) { 14 break; 15 } 16 fos.write(buf);// write byte[] into file 17 fos.flush(); 18 } 19 } catch (UnknownHostException e) { 20 e.printStackTrace(); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } finally { 24 try { 25 sis.close(); 26 fos.close(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 32 // getFilesDir() + "/" 可以用来读取程序内部存储空间的files目录 33 // 取到的路径为:/data/data/$appname/files 34 // ※最后是不带斜线的 35 bitmap = BitmapFactory.decodeFile(getFilesDir()+"/"+tempFileName, null); 36 handler.sendEmptyMessage(0); 37 } 38 39 // Check received data for whether it's end mark. 40 private boolean ifEnd(byte[] data) { 41 String rcvData; 42 try { 43 // 这里把取到的byte[]按照ISO-8859-1转换为String 44 // byte[]每个单位1字节,String每个单位2字节 45 rcvData = new String(data, "ISO-8859-1"); 46 // buffer可能不为空,在ENDMARK后还有其他数据,因此不用equals,改用startsWith 47 if(rcvData.startsWith(ENDMARK)) { 48 Log.i("DOCK", "ENDMARK found."); 49 return true; 50 } 51 } catch (UnsupportedEncodingException e) { 52 e.printStackTrace(); 53 } 54 55 return false; 56 } 57 58 // 在socket线程之外使用handler,来改变activity上的内容 59 private Handler handler = new Handler() { 60 public void handleMessage(Message msg) { 61 // 一般的用法是switch XXX,我偷懒了~ 62 super.handleMessage(msg); 63 showField.setImageBitmap(bitmap); 64 } 65 };
这部分是整个过程中耗时最多的部分,这次学到的东西都写在注释里面了。
需要交代的是Android的文件存放方式,请参考(感谢经验提供者)
http://www.open-open.com/lib/view/open1330957864280.html
三. CLIENT端向SERVER端发送图片
SERVER端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int CppServer::recvData(char *file) 2 { 3 int returnValue=0; 4 int iResult = 1; 5 FILE *rcvFile = NULL; 6 7 if(0 == _access(file, 0)) { 8 // If resource file already exist, print warning message. 9 printf("----File %s have already existed. It will be clear.\n", file); 10 } 11 12 rcvFile=fopen(file, "wb+"); 13 if(!rcvFile) { 14 printf("Failed to open rcvFile. %s\n", file); 15 return -1; 16 } 17 18 while(iResult > 0) { 19 memset(rcvbuf, 0x00, DATA_BUFLEN); 20 iResult = recv(ClientSocket, rcvbuf, rcvbuflen, 0); 21 22 if(0 == strncmp(rcvbuf, "over", sizeof(rcvbuf))) { 23 //printf("Receive end signal from client.\n"); 24 returnValue = 1; 25 break; 26 } 27 28 if (iResult > 0) { 29 fwrite(rcvbuf, rcvbuflen, 1, rcvFile); 30 } else if (iResult == 0) { 31 printf("Connection closed response...\n"); 32 returnValue = 0; 33 break; 34 } else { 35 printf("recv failed with error: %d\n", WSAGetLastError()); 36 closesocket(ClientSocket); 37 WSACleanup(); 38 returnValue = -1; 39 initialized = false; 40 break; 41 } 42 } 43 44 fclose(rcvFile); 45 printf("--Data have been received successfully. Write data in file %s\n", file); 46 return returnValue; 47 }
fwrite可以类比fread函数,参数与返回值的意义是类似的。
CLIENT端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // Send picture. 2 private void rcvData() { 3 int readNum = 0; 4 fis = openFileInput(tempFileName); 5 sos = new DataOutputStream(s.getOutputStream()); 6 while (true) { 7 Arrays.fill(buf, (byte)0); 8 readNum = fis.read(buf); 9 if(0 > readNum) { 10 break; 11 } 12 sos.write(buf, 0, readNum); 13 } 14 sendEndMark(sos); 15 }
整个client向server发送的过程,与server向client发送数据是类似的,只不过是read、write,inputstram、outputstream的变化,这里就不再分析。